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:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-09-20 14:18:08 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-09-20 14:18:08 +0300
commit5afcbe03ead9ada87621888a31a62652b10a7e4f (patch)
tree9918b67a0d0f0bafa6542e839a8be37adf73102d /spec/support/shared_examples
parentc97c0201564848c1f53226fe19d71fdcc472f7d0 (diff)
Add latest changes from gitlab-org/gitlab@16-4-stable-eev16.4.0-rc42
Diffstat (limited to 'spec/support/shared_examples')
-rw-r--r--spec/support/shared_examples/channels/noteable/notes_channel_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/ci/create_pipeline_service_environment_shared_examples.rb166
-rw-r--r--spec/support/shared_examples/ci/deployable_shared_examples.rb56
-rw-r--r--spec/support/shared_examples/config/metrics/every_metric_definition_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/controllers/concerns/web_hooks/integrations_hook_log_actions_shared_examples.rb9
-rw-r--r--spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb17
-rw-r--r--spec/support/shared_examples/controllers/labels_controller_shared_examples.rb38
-rw-r--r--spec/support/shared_examples/controllers/search_rate_limit_shared_examples.rb21
-rw-r--r--spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb2
-rw-r--r--spec/support/shared_examples/features/2fa_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/features/content_editor_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb21
-rw-r--r--spec/support/shared_examples/features/protected_tags_with_deploy_keys_examples.rb4
-rw-r--r--spec/support/shared_examples/features/runners_shared_examples.rb18
-rw-r--r--spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb9
-rw-r--r--spec/support/shared_examples/features/snippets_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/variable_list_pagination_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/features/variable_list_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/features/work_items_shared_examples.rb39
-rw-r--r--spec/support/shared_examples/finders/issues_finder_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/graphql/mutations/update_time_estimate_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/harbor/artifacts_controller_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/harbor/repositories_controller_shared_examples.rb24
-rw-r--r--spec/support/shared_examples/harbor/tags_controller_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/lib/api/ai_workhorse_shared_examples.rb45
-rw-r--r--spec/support/shared_examples/lib/gitlab/bitbucket_import/object_import_shared_examples.rb100
-rw-r--r--spec/support/shared_examples/lib/gitlab/bitbucket_import/stage_methods_shared_examples.rb29
-rw-r--r--spec/support/shared_examples/lib/gitlab/bitbucket_server_import/object_import_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/lib/gitlab/database/cte_materialized_shared_examples.rb37
-rw-r--r--spec/support/shared_examples/lib/gitlab/import/advance_stage_shared_examples.rb109
-rw-r--r--spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/gitlab/search_archived_filter_shared_examples.rb33
-rw-r--r--spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb87
-rw-r--r--spec/support/shared_examples/lib/menus_shared_examples.rb7
-rw-r--r--spec/support/shared_examples/lib/sidebars/user_profile/user_profile_menus_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/loose_foreign_keys/have_loose_foreign_key.rb6
-rw-r--r--spec/support/shared_examples/mailers/notify_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/migrations/add_work_item_widget_shared_examples.rb107
-rw-r--r--spec/support/shared_examples/models/concerns/linkable_items_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/models/group_shared_examples.rb45
-rw-r--r--spec/support/shared_examples/models/members_notifications_shared_example.rb8
-rw-r--r--spec/support/shared_examples/models/users/pages_visits_shared_examples.rb27
-rw-r--r--spec/support/shared_examples/redis/redis_shared_examples.rb84
-rw-r--r--spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb33
-rw-r--r--spec/support/shared_examples/requests/api/graphql/work_item_list_shared_examples.rb98
-rw-r--r--spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb15
-rw-r--r--spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb69
-rw-r--r--spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb42
-rw-r--r--spec/support/shared_examples/requests/api_keyset_pagination_shared_examples.rb50
-rw-r--r--spec/support/shared_examples/requests/rack_attack_shared_examples.rb38
-rw-r--r--spec/support/shared_examples/services/incident_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/issuable/issuable_update_service_shared_examples.rb71
-rw-r--r--spec/support/shared_examples/services/issuable_links/destroyable_issuable_links_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb193
-rw-r--r--spec/support/shared_examples/services/migrate_to_ghost_user_service_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/pages_size_limit_shared_examples.rb32
-rw-r--r--spec/support/shared_examples/services/protected_branches_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/services/users/build_service_shared_examples.rb51
-rw-r--r--spec/support/shared_examples/users/migrate_records_to_ghost_user_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/users/pages_visits_shared_examples.rb63
-rw-r--r--spec/support/shared_examples/workers/background_migration_worker_shared_examples.rb36
-rw-r--r--spec/support/shared_examples/workers/batched_background_migration_execution_worker_shared_example.rb21
-rw-r--r--spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb22
67 files changed, 1562 insertions, 638 deletions
diff --git a/spec/support/shared_examples/channels/noteable/notes_channel_shared_examples.rb b/spec/support/shared_examples/channels/noteable/notes_channel_shared_examples.rb
index cb7001a9faf..21bba14f3e6 100644
--- a/spec/support/shared_examples/channels/noteable/notes_channel_shared_examples.rb
+++ b/spec/support/shared_examples/channels/noteable/notes_channel_shared_examples.rb
@@ -15,16 +15,4 @@ RSpec.shared_examples 'handle subscription based on user access' do
expect(subscription).to be_rejected
end
-
- context 'when action_cable_notes is disabled' do
- before do
- stub_feature_flags(action_cable_notes: false)
- end
-
- it 'rejects the subscription' do
- subscribe(subscribe_params)
-
- expect(subscription).to be_rejected
- end
- end
end
diff --git a/spec/support/shared_examples/ci/create_pipeline_service_environment_shared_examples.rb b/spec/support/shared_examples/ci/create_pipeline_service_environment_shared_examples.rb
new file mode 100644
index 00000000000..77a352a8326
--- /dev/null
+++ b/spec/support/shared_examples/ci/create_pipeline_service_environment_shared_examples.rb
@@ -0,0 +1,166 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'creating a pipeline with environment keyword' do
+ context 'with environment' do
+ let(:config) do
+ YAML.dump(
+ deploy: {
+ environment: { name: "review/$CI_COMMIT_REF_NAME" },
+ **base_config
+ })
+ end
+
+ it 'creates the environment', :sidekiq_inline do
+ result = execute_service.payload
+
+ expect(result).to be_persisted
+ expect(Environment.find_by(name: "review/master")).to be_present
+ expect(result.all_jobs.first.deployment).to be_persisted
+ expect(result.all_jobs.first.deployment.deployable).to be_a(expected_deployable_class)
+ end
+
+ it 'sets tags when build job' do
+ skip unless expected_deployable_class == Ci::Build
+
+ result = execute_service.payload
+
+ expect(result).to be_persisted
+ expect(result.all_jobs.first.tag_list).to match_array(expected_tag_names)
+ end
+ end
+
+ context 'with environment with auto_stop_in' do
+ let(:config) do
+ YAML.dump(
+ deploy: {
+ environment: { name: "review/$CI_COMMIT_REF_NAME", auto_stop_in: '1 day' },
+ **base_config
+ })
+ end
+
+ it 'creates the environment with auto stop in' do
+ result = execute_service.payload
+
+ expect(result).to be_persisted
+ expect(result.all_jobs.first.options[:environment][:auto_stop_in]).to eq('1 day')
+ end
+ end
+
+ context 'with environment name including persisted variables' do
+ let(:config) do
+ YAML.dump(
+ deploy: {
+ environment: { name: "review/id1$CI_PIPELINE_ID/id2$CI_JOB_ID" },
+ **base_config
+ }
+ )
+ end
+
+ it 'skips persisted variables in environment name' do
+ result = execute_service.payload
+
+ expect(result).to be_persisted
+ expect(Environment.find_by(name: "review/id1/id2")).to be_present
+ end
+ end
+
+ context 'when environment with Kubernetes configuration' do
+ let(:kubernetes_namespace) { 'custom-namespace' }
+ let(:config) do
+ YAML.dump(
+ deploy: {
+ environment: {
+ name: "environment-name",
+ kubernetes: { namespace: kubernetes_namespace }
+ },
+ **base_config
+ }
+ )
+ end
+
+ it 'stores the requested namespace' do
+ result = execute_service.payload
+ job = result.all_jobs.first
+
+ expect(result).to be_persisted
+ expect(job.options.dig(:environment, :kubernetes, :namespace)).to eq(kubernetes_namespace)
+ end
+ end
+
+ context 'when environment with invalid name' do
+ let(:config) do
+ YAML.dump(deploy: { environment: { name: 'name,with,commas' }, **base_config })
+ end
+
+ it 'does not create an environment' do
+ expect do
+ result = execute_service.payload
+
+ expect(result).to be_persisted
+ end.not_to change { Environment.count }
+ end
+ end
+
+ context 'when environment with duplicate names' do
+ let(:config) do
+ YAML.dump({
+ deploy: { environment: { name: 'production' }, **base_config },
+ deploy_2: { environment: { name: 'production' }, **base_config }
+ })
+ end
+
+ it 'creates a pipeline with the environment', :sidekiq_inline do
+ result = execute_service.payload
+
+ expect(result).to be_persisted
+ expect(Environment.find_by(name: 'production')).to be_present
+ expect(result.all_jobs.first.deployment).to be_persisted
+ expect(result.all_jobs.first.deployment.deployable).to be_a(expected_deployable_class)
+ end
+ end
+
+ context 'when pipeline has a job with environment' do
+ let(:pipeline) { execute_service.payload }
+
+ context 'when environment name is valid' do
+ let(:config) do
+ YAML.dump({
+ review_app: {
+ environment: {
+ name: 'review/${CI_COMMIT_REF_NAME}',
+ url: 'http://${CI_COMMIT_REF_SLUG}-staging.example.com'
+ },
+ **base_config
+ }
+ })
+ end
+
+ it 'has a job with environment', :sidekiq_inline do
+ expect(pipeline.all_jobs.count).to eq(1)
+ expect(pipeline.all_jobs.first.persisted_environment.name).to eq('review/master')
+ expect(pipeline.all_jobs.first.deployment.status).to eq(expected_deployment_status)
+ expect(pipeline.all_jobs.first.status).to eq(expected_job_status)
+ end
+ end
+
+ context 'when environment name is invalid' do
+ let(:config) do
+ YAML.dump({
+ 'job:deploy-to-test-site': {
+ environment: {
+ name: '${CI_JOB_NAME}',
+ url: 'https://$APP_URL'
+ },
+ **base_config
+ }
+ })
+ end
+
+ it 'has a job without environment' do
+ expect(pipeline.all_jobs.count).to eq(1)
+ expect(pipeline.all_jobs.first.persisted_environment).to be_nil
+ expect(pipeline.all_jobs.first.deployment).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/ci/deployable_shared_examples.rb b/spec/support/shared_examples/ci/deployable_shared_examples.rb
index b51a8fa20e2..4f43d38e604 100644
--- a/spec/support/shared_examples/ci/deployable_shared_examples.rb
+++ b/spec/support/shared_examples/ci/deployable_shared_examples.rb
@@ -96,7 +96,7 @@ RSpec.shared_examples 'a deployable job' do
ActiveRecord::QueryRecorder.new { subject }
end
- index_for_build = recorded.log.index { |l| l.include?("UPDATE #{Ci::Build.quoted_table_name}") }
+ index_for_build = recorded.log.index { |l| l.include?("UPDATE #{described_class.quoted_table_name}") }
index_for_deployment = recorded.log.index { |l| l.include?("UPDATE \"deployments\"") }
expect(index_for_build).to be < index_for_deployment
@@ -259,7 +259,7 @@ RSpec.shared_examples 'a deployable job' do
describe '#environment_tier_from_options' do
subject { job.environment_tier_from_options }
- let(:job) { Ci::Build.new(options: options) }
+ let(:job) { described_class.new(options: options) }
let(:options) { { environment: { deployment_tier: 'production' } } }
it { is_expected.to eq('production') }
@@ -276,7 +276,7 @@ RSpec.shared_examples 'a deployable job' do
let(:options) { { environment: { deployment_tier: 'production' } } }
let!(:environment) { create(:environment, name: 'production', tier: 'development', project: project) }
- let(:job) { Ci::Build.new(options: options, environment: 'production', project: project) }
+ let(:job) { described_class.new(options: options, environment: 'production', project: project) }
it { is_expected.to eq('production') }
@@ -295,6 +295,52 @@ RSpec.shared_examples 'a deployable job' do
end
end
+ describe '#environment_url' do
+ subject { job.environment_url }
+
+ let!(:job) { create(factory_type, :with_deployment, :deploy_to_production, pipeline: pipeline) }
+
+ it { is_expected.to eq('http://prd.example.com/$CI_JOB_NAME') }
+
+ context 'when options does not include url' do
+ before do
+ job.update!(options: { environment: { url: nil } })
+ job.persisted_environment.update!(external_url: 'http://prd.example.com/$CI_JOB_NAME')
+ end
+
+ it 'fetches from the persisted environment' do
+ expect_any_instance_of(::Environment) do |environment|
+ expect(environment).to receive(:external_url).once
+ end
+
+ is_expected.to eq('http://prd.example.com/$CI_JOB_NAME')
+ end
+
+ context 'when persisted environment is absent' do
+ before do
+ job.clear_memoization(:persisted_environment)
+ job.persisted_environment = nil
+ end
+
+ it { is_expected.to be_nil }
+ end
+ end
+ end
+
+ describe '#environment_slug' do
+ subject { job.environment_slug }
+
+ let!(:job) { create(factory_type, :with_deployment, :start_review_app, pipeline: pipeline) }
+
+ it { is_expected.to eq('review-master-8dyme2') }
+
+ context 'when persisted environment is absent' do
+ let!(:job) { create(factory_type, :start_review_app, pipeline: pipeline) }
+
+ it { is_expected.to be_nil }
+ end
+ end
+
describe 'environment' do
describe '#has_environment_keyword?' do
subject { job.has_environment_keyword? }
@@ -536,10 +582,6 @@ RSpec.shared_examples 'a deployable job' do
end
describe '#deployment_status' do
- before do
- allow_any_instance_of(Ci::Build).to receive(:create_deployment) # rubocop:disable RSpec/AnyInstanceOf
- end
-
context 'when job is a last deployment' do
let(:job) { create(factory_type, :success, environment: 'production', pipeline: pipeline) }
let(:environment) { create(:environment, name: 'production', project: job.project) }
diff --git a/spec/support/shared_examples/config/metrics/every_metric_definition_shared_examples.rb b/spec/support/shared_examples/config/metrics/every_metric_definition_shared_examples.rb
index e61c884cd2b..14d0ac81250 100644
--- a/spec/support/shared_examples/config/metrics/every_metric_definition_shared_examples.rb
+++ b/spec/support/shared_examples/config/metrics/every_metric_definition_shared_examples.rb
@@ -121,8 +121,7 @@ RSpec.shared_examples 'every metric definition' do
let(:ignored_classes) do
[
Gitlab::Usage::Metrics::Instrumentations::IssuesWithAlertManagementAlertsMetric,
- Gitlab::Usage::Metrics::Instrumentations::IssuesWithPrometheusAlertEvents,
- Gitlab::Usage::Metrics::Instrumentations::IssuesWithSelfManagedPrometheusAlertEvents
+ Gitlab::Usage::Metrics::Instrumentations::IssuesWithPrometheusAlertEvents
].freeze
end
diff --git a/spec/support/shared_examples/controllers/concerns/web_hooks/integrations_hook_log_actions_shared_examples.rb b/spec/support/shared_examples/controllers/concerns/web_hooks/integrations_hook_log_actions_shared_examples.rb
index 56a5dcb10b2..cb4e68122d9 100644
--- a/spec/support/shared_examples/controllers/concerns/web_hooks/integrations_hook_log_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/concerns/web_hooks/integrations_hook_log_actions_shared_examples.rb
@@ -43,5 +43,14 @@ RSpec.shared_examples WebHooks::HookLogActions do
expect(response).to have_gitlab_http_status(:not_found)
end
+
+ it 'redirects back with a warning if the hook log url is outdated' do
+ web_hook_log.update!(url_hash: 'some_other_value')
+
+ post retry_path, headers: { 'REFERER' => show_path }
+
+ expect(response).to redirect_to(show_path)
+ expect(flash[:warning]).to eq(_('The hook URL has changed, and this log entry cannot be retried'))
+ end
end
end
diff --git a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
index a4eb6a839c0..bd9c2582d2f 100644
--- a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
@@ -18,23 +18,6 @@ RSpec.shared_examples 'issuable notes filter' do
expect(UserPreference.count).to eq(1)
end
- it 'expires notes e-tag cache for issuable if filter changed' do
- notes_filter = UserPreference::NOTES_FILTERS[:only_comments]
-
- expect_any_instance_of(issuable.class).to receive(:expire_note_etag_cache)
-
- get :discussions, params: params.merge(notes_filter: notes_filter)
- end
-
- it 'does not expires notes e-tag cache for issuable if filter did not change' do
- notes_filter = UserPreference::NOTES_FILTERS[:only_comments]
- user.set_notes_filter(notes_filter, issuable)
-
- expect_any_instance_of(issuable.class).not_to receive(:expire_note_etag_cache)
-
- get :discussions, params: params.merge(notes_filter: notes_filter)
- end
-
it 'does not set notes filter when database is in read-only mode' do
allow(Gitlab::Database).to receive(:read_only?).and_return(true)
notes_filter = UserPreference::NOTES_FILTERS[:only_comments]
diff --git a/spec/support/shared_examples/controllers/labels_controller_shared_examples.rb b/spec/support/shared_examples/controllers/labels_controller_shared_examples.rb
new file mode 100644
index 00000000000..aea552f6ac7
--- /dev/null
+++ b/spec/support/shared_examples/controllers/labels_controller_shared_examples.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'lock_on_merge when editing labels' do
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(enforce_locked_labels_on_merge: false)
+ visit edit_label_path_unlocked
+ end
+
+ it 'does not display the checkbox/help text' do
+ expect(page).not_to have_content(_('Lock label after a merge request is merged'))
+ expect(page).not_to have_content(label_lock_on_merge_help_text)
+ end
+ end
+
+ it 'updates lock_on_merge' do
+ expect(page).to have_content(_('Lock label after a merge request is merged'))
+ expect(page).to have_content(label_lock_on_merge_help_text)
+
+ check(_('Lock label after a merge request is merged'))
+ click_button 'Save changes'
+
+ expect(label_unlocked.reload.lock_on_merge).to be_truthy
+ end
+
+ it 'checkbox is disabled if lock_on_merge already set' do
+ visit edit_label_path_locked
+
+ expect(page.find('#label_lock_on_merge')).to be_disabled
+ end
+end
+
+RSpec.shared_examples 'lock_on_merge when creating labels' do
+ it 'is not supported when creating a label' do
+ expect(page).not_to have_content(_('Lock label after a merge request is merged'))
+ expect(page).not_to have_content(label_lock_on_merge_help_text)
+ end
+end
diff --git a/spec/support/shared_examples/controllers/search_rate_limit_shared_examples.rb b/spec/support/shared_examples/controllers/search_rate_limit_shared_examples.rb
new file mode 100644
index 00000000000..aefcdc70082
--- /dev/null
+++ b/spec/support/shared_examples/controllers/search_rate_limit_shared_examples.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+# Requires a context containing:
+# - user
+# - params
+
+RSpec.shared_examples 'search request exceeding rate limit' do
+ include_examples 'rate limited endpoint', rate_limit_key: :search_rate_limit
+
+ it 'allows user in allow-list to search without applying rate limit', :freeze_time,
+ :clean_gitlab_redis_rate_limiting do
+ allow(Gitlab::ApplicationRateLimiter).to receive(:threshold).with(:search_rate_limit).and_return(1)
+
+ stub_application_setting(search_rate_limit_allowlist: [current_user.username])
+
+ request
+ request
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+end
diff --git a/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb b/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb
index 3d3b619451d..29d6f202498 100644
--- a/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb
+++ b/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb
@@ -57,7 +57,7 @@ RSpec.shared_examples 'Snowplow event tracking with Redis context' do |overrides
it_behaves_like 'Snowplow event tracking', overrides: overrides do
let(:context) do
key_path = try(:label) || action
- [Gitlab::Tracking::ServicePingContext.new(data_source: :redis, key_path: key_path).to_context.to_json]
+ [Gitlab::Usage::MetricDefinition.context_for(key_path).to_context.to_json]
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 6c4e98c9989..f50874b6b05 100644
--- a/spec/support/shared_examples/features/2fa_shared_examples.rb
+++ b/spec/support/shared_examples/features/2fa_shared_examples.rb
@@ -14,7 +14,7 @@ RSpec.shared_examples 'hardware device for 2fa' do |device_type|
end
describe "registration" do
- let(:user) { create(:user) }
+ let(:user) { create(:user, :no_super_sidebar) }
before do
gitlab_sign_in(user)
@@ -67,7 +67,7 @@ RSpec.shared_examples 'hardware device for 2fa' do |device_type|
end
describe 'fallback code authentication' do
- let(:user) { create(:user) }
+ let(:user) { create(:user, :no_super_sidebar) }
before do
# Register and logout
diff --git a/spec/support/shared_examples/features/content_editor_shared_examples.rb b/spec/support/shared_examples/features/content_editor_shared_examples.rb
index fff8ef915eb..3e81f969462 100644
--- a/spec/support/shared_examples/features/content_editor_shared_examples.rb
+++ b/spec/support/shared_examples/features/content_editor_shared_examples.rb
@@ -244,7 +244,8 @@ RSpec.shared_examples 'edits content using the content editor' do |params = { wi
end
end
- it 'expands the link, updates the link attributes and text if text is updated' do
+ it 'expands the link, updates the link attributes and text if text is updated',
+ quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/419684' do
page.within '[data-testid="link-bubble-menu"]' do
fill_in 'link-text', with: 'new text'
fill_in 'link-href', with: 'https://about.gitlab.com'
diff --git a/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb b/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb
index d410653ca43..58bf461c733 100644
--- a/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb
+++ b/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb
@@ -4,8 +4,8 @@ RSpec.shared_examples 'project features apply to issuables' do |klass|
let(:described_class) { klass }
let(:group) { create(:group) }
- let(:user_in_group) { create(:group_member, :developer, user: create(:user), group: group ).user }
- let(:user_outside_group) { create(:user) }
+ let(:user_in_group) { create(:group_member, :developer, user: create(:user, :no_super_sidebar), group: group ).user }
+ let(:user_outside_group) { create(:user, :no_super_sidebar) }
let(:project) { create(:project, :public, project_args) }
diff --git a/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb
index fb882ef8a23..fc717fbac20 100644
--- a/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb
+++ b/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb
@@ -2,6 +2,7 @@
RSpec.shared_examples "protected branches > access control > CE" do
let(:no_one) { ProtectedRef::AccessLevel.humanize(::Gitlab::Access::NO_ACCESS) }
+ let_it_be(:edit_form) { '.js-protected-branch-edit-form' }
ProtectedRef::AccessLevel.human_access_levels.each do |(access_type_id, access_type_name)|
it "allows creating protected branches that #{access_type_name} can push to" do
@@ -30,7 +31,8 @@ RSpec.shared_examples "protected branches > access control > CE" do
expect(ProtectedBranch.last.merge_access_levels.map(&:access_level)).to eq([access_type_id])
end
- it "allows updating protected branches so that #{access_type_name} can push to them" do
+ it "allows updating protected branches so that #{access_type_name} can push to them",
+ quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/425080' do
visit project_protected_branches_path(project)
show_add_form
@@ -41,18 +43,14 @@ RSpec.shared_examples "protected branches > access control > CE" do
expect(ProtectedBranch.count).to eq(1)
- within(".protected-branches-list") do
- within_select(".js-allowed-to-push") do
- click_on(access_type_name)
- end
- end
-
+ set_allowed_to('push', access_type_name, form: edit_form)
wait_for_requests
expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).to include(access_type_id)
end
- it "allows updating protected branches so that #{access_type_name} can merge to them" do
+ it "allows updating protected branches so that #{access_type_name} can merge to them",
+ quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/425080' do
visit project_protected_branches_path(project)
show_add_form
@@ -63,12 +61,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
expect(ProtectedBranch.count).to eq(1)
- within(".protected-branches-list") do
- within_select(".js-allowed-to-merge") do
- click_on(access_type_name)
- end
- end
-
+ set_allowed_to('merge', access_type_name, form: edit_form)
wait_for_requests
expect(ProtectedBranch.last.merge_access_levels.map(&:access_level)).to include(access_type_id)
diff --git a/spec/support/shared_examples/features/protected_tags_with_deploy_keys_examples.rb b/spec/support/shared_examples/features/protected_tags_with_deploy_keys_examples.rb
index 703ba5b018a..2b7147fa4b4 100644
--- a/spec/support/shared_examples/features/protected_tags_with_deploy_keys_examples.rb
+++ b/spec/support/shared_examples/features/protected_tags_with_deploy_keys_examples.rb
@@ -19,7 +19,7 @@ RSpec.shared_examples 'Deploy keys with protected tags' do
find(".js-allowed-to-create").click
wait_for_requests
- within('[data-testid="allowed-to-create-dropdown"]') do
+ within('.dropdown-menu') do
dropdown_headers = page.all('.dropdown-header').map(&:text)
expect(dropdown_headers).to contain_exactly(*all_dropdown_sections)
@@ -53,7 +53,7 @@ RSpec.shared_examples 'Deploy keys with protected tags' do
find(".js-allowed-to-create").click
wait_for_requests
- within('[data-testid="allowed-to-create-dropdown"]') do
+ within('.dropdown-menu') do
dropdown_headers = page.all('.dropdown-header').map(&:text)
expect(dropdown_headers).to contain_exactly(*dropdown_sections_minus_deploy_keys)
diff --git a/spec/support/shared_examples/features/runners_shared_examples.rb b/spec/support/shared_examples/features/runners_shared_examples.rb
index 0c043f48c5f..861c205337a 100644
--- a/spec/support/shared_examples/features/runners_shared_examples.rb
+++ b/spec/support/shared_examples/features/runners_shared_examples.rb
@@ -94,6 +94,17 @@ RSpec.shared_examples 'shows runner in list' do
end
end
+RSpec.shared_examples 'shows runner details from list' do
+ it 'shows runner details page' do
+ click_link("##{runner.id} (#{runner.short_sha})")
+
+ expect(current_url).to include(runner_page_path)
+
+ expect(page).to have_selector 'h1', text: "##{runner.id} (#{runner.short_sha})"
+ expect(page).to have_content "#{s_('Runners|Description')} #{runner.description}"
+ end
+end
+
RSpec.shared_examples 'pauses, resumes and deletes a runner' do
include Spec::Support::Helpers::ModalHelpers
@@ -191,6 +202,13 @@ RSpec.shared_examples 'shows runner jobs tab' do
end
end
+RSpec.shared_examples 'shows locked field' do
+ it 'shows locked checkbox with description', :js do
+ expect(page).to have_selector('input[type="checkbox"][name="locked"]')
+ expect(page).to have_content(_('Lock to current projects'))
+ end
+end
+
RSpec.shared_examples 'submits edit runner form' do
it 'breadcrumb contains runner id and token' do
page.within '[data-testid="breadcrumb-links"]' do
diff --git a/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb b/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb
index 8ebec19a884..c2d144bef3b 100644
--- a/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb
+++ b/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb
@@ -86,7 +86,7 @@ RSpec.shared_examples 'labels sidebar widget' do
context 'creating a label', :js do
before do
page.within(labels_widget) do
- page.find('[data-testid="create-label-button"]').click
+ click_button 'Create project label'
end
end
@@ -96,12 +96,11 @@ RSpec.shared_examples 'labels sidebar widget' do
end
end
- it 'creates new label', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/391240' do
+ it 'creates new label' do
page.within(labels_widget) do
fill_in 'Name new label', with: 'wontfix'
- page.find('.suggest-colors a', match: :first).click
- page.find('button', text: 'Create').click
- wait_for_requests
+ click_link 'Magenta-pink'
+ click_button 'Create'
expect(page).to have_content 'wontfix'
end
diff --git a/spec/support/shared_examples/features/snippets_shared_examples.rb b/spec/support/shared_examples/features/snippets_shared_examples.rb
index bf870b3ce66..383f81d048f 100644
--- a/spec/support/shared_examples/features/snippets_shared_examples.rb
+++ b/spec/support/shared_examples/features/snippets_shared_examples.rb
@@ -52,7 +52,7 @@ RSpec.shared_examples 'tabs with counts' do
end
RSpec.shared_examples 'does not show New Snippet button' do
- let(:user) { create(:user, :external) }
+ let(:user) { create(:user, :external, :no_super_sidebar) }
specify do
sign_in(user)
diff --git a/spec/support/shared_examples/features/variable_list_pagination_shared_examples.rb b/spec/support/shared_examples/features/variable_list_pagination_shared_examples.rb
index 0b0c9edcb42..c1057671699 100644
--- a/spec/support/shared_examples/features/variable_list_pagination_shared_examples.rb
+++ b/spec/support/shared_examples/features/variable_list_pagination_shared_examples.rb
@@ -41,15 +41,15 @@ RSpec.shared_examples 'variable list pagination' do |variable_type|
it 'sorts variables alphabetically in ASC and DESC order' do
page.within('[data-testid="ci-variable-table"]') do
- expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq(variable.key)
- expect(find('.js-ci-variable-row:nth-child(20) td[data-label="Key"]').text).to eq('test_key_8')
+ expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]')).to have_content(variable.key)
+ expect(find('.js-ci-variable-row:nth-child(20) td[data-label="Key"]')).to have_content('test_key_8')
end
click_button 'Next'
wait_for_requests
page.within('[data-testid="ci-variable-table"]') do
- expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq('test_key_9')
+ expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]')).to have_content('test_key_9')
end
page.within('[data-testid="ci-variable-table"]') do
@@ -59,8 +59,8 @@ RSpec.shared_examples 'variable list pagination' do |variable_type|
wait_for_requests
page.within('[data-testid="ci-variable-table"]') do
- expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq('test_key_9')
- expect(find('.js-ci-variable-row:nth-child(20) td[data-label="Key"]').text).to eq('test_key_0')
+ expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]')).to have_content('test_key_9')
+ expect(find('.js-ci-variable-row:nth-child(20) td[data-label="Key"]')).to have_content('test_key_0')
end
end
end
diff --git a/spec/support/shared_examples/features/variable_list_shared_examples.rb b/spec/support/shared_examples/features/variable_list_shared_examples.rb
index 3a91b798bbd..5951d3e781b 100644
--- a/spec/support/shared_examples/features/variable_list_shared_examples.rb
+++ b/spec/support/shared_examples/features/variable_list_shared_examples.rb
@@ -3,7 +3,7 @@
RSpec.shared_examples 'variable list' do
it 'shows a list of variables' do
page.within('[data-testid="ci-variable-table"]') do
- expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq(variable.key)
+ expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]')).to have_content(variable.key)
end
end
@@ -17,7 +17,7 @@ RSpec.shared_examples 'variable list' do
wait_for_requests
page.within('[data-testid="ci-variable-table"]') do
- expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq('key')
+ expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]')).to have_content('key')
end
end
@@ -31,8 +31,8 @@ RSpec.shared_examples 'variable list' do
wait_for_requests
page.within('[data-testid="ci-variable-table"]') do
- expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Key')}']").text).to eq('key')
- expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Attributes')}']")).to have_content(s_('CiVariables|Protected'))
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Key')}']")).to have_content('key')
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Key')}']")).to have_content(s_('CiVariables|Protected'))
end
end
@@ -46,25 +46,25 @@ RSpec.shared_examples 'variable list' do
wait_for_requests
page.within('[data-testid="ci-variable-table"]') do
- expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Key')}']").text).to eq('key')
- expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Attributes')}']")).not_to have_content(s_('CiVariables|Masked'))
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Key')}']")).to have_content('key')
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Key')}']")).not_to have_content(s_('CiVariables|Masked'))
end
end
it 'reveals and hides variables' do
page.within('[data-testid="ci-variable-table"]') do
- expect(first('.js-ci-variable-row td[data-label="Key"]').text).to eq(variable.key)
+ expect(first('.js-ci-variable-row td[data-label="Key"]')).to have_content(variable.key)
expect(page).to have_content('*' * 5)
click_button('Reveal value')
- expect(first('.js-ci-variable-row td[data-label="Key"]').text).to eq(variable.key)
- expect(first('.js-ci-variable-row td[data-label="Value"]').text).to eq(variable.value)
+ expect(first('.js-ci-variable-row td[data-label="Key"]')).to have_content(variable.key)
+ expect(first('.js-ci-variable-row td[data-label="Value"]')).to have_content(variable.value)
expect(page).not_to have_content('*' * 5)
click_button('Hide value')
- expect(first('.js-ci-variable-row td[data-label="Key"]').text).to eq(variable.key)
+ expect(first('.js-ci-variable-row td[data-label="Key"]')).to have_content(variable.key)
expect(page).to have_content('*' * 5)
end
end
@@ -98,7 +98,7 @@ RSpec.shared_examples 'variable list' do
wait_for_requests
- expect(first('.js-ci-variable-row td[data-label="Key"]').text).to eq('new_key')
+ expect(first('.js-ci-variable-row td[data-label="Key"]')).to have_content('new_key')
end
it 'edits a variable to be unmasked' do
@@ -116,8 +116,8 @@ RSpec.shared_examples 'variable list' do
wait_for_requests
page.within('[data-testid="ci-variable-table"]') do
- expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Attributes')}']")).to have_content(s_('CiVariables|Protected'))
- expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Attributes')}']")).not_to have_content(s_('CiVariables|Masked'))
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Key')}']")).to have_content(s_('CiVariables|Protected'))
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Key')}']")).not_to have_content(s_('CiVariables|Masked'))
end
end
@@ -145,7 +145,7 @@ RSpec.shared_examples 'variable list' do
end
page.within('[data-testid="ci-variable-table"]') do
- expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Attributes')}']")).to have_content(s_('CiVariables|Masked'))
+ expect(find(".js-ci-variable-row:nth-child(1) td[data-label='#{s_('CiVariables|Key')}']")).to have_content(s_('CiVariables|Masked'))
end
end
@@ -234,9 +234,9 @@ RSpec.shared_examples 'variable list' do
# expect to find 3 rows of variables in alphabetical order
expect(page).to have_selector('.js-ci-variable-row', count: 3)
rows = all('.js-ci-variable-row')
- expect(rows[0].find('td[data-label="Key"]').text).to eq('ckey')
- expect(rows[1].find('td[data-label="Key"]').text).to eq('test_key')
- expect(rows[2].find('td[data-label="Key"]').text).to eq('zkey')
+ expect(rows[0].find('td[data-label="Key"]')).to have_content('ckey')
+ expect(rows[1].find('td[data-label="Key"]')).to have_content('test_key')
+ expect(rows[2].find('td[data-label="Key"]')).to have_content('zkey')
end
context 'defaults to the application setting' do
diff --git a/spec/support/shared_examples/features/work_items_shared_examples.rb b/spec/support/shared_examples/features/work_items_shared_examples.rb
index d3863c9a675..18e0cfdad00 100644
--- a/spec/support/shared_examples/features/work_items_shared_examples.rb
+++ b/spec/support/shared_examples/features/work_items_shared_examples.rb
@@ -179,6 +179,45 @@ RSpec.shared_examples 'work items assignees' do
expect(work_item.reload.assignees).to include(user)
end
+
+ it 'successfully assigns the current user by clicking `Assign myself` button' do
+ find('[data-testid="work-item-assignees-input"]').hover
+ find('[data-testid="assign-self"]').click
+ wait_for_requests
+
+ expect(work_item.reload.assignees).to include(user)
+ end
+
+ it 'successfully removes all users on clear all button click' do
+ find('[data-testid="work-item-assignees-input"]').hover
+ find('[data-testid="assign-self"]').click
+ wait_for_requests
+
+ expect(work_item.reload.assignees).to include(user)
+
+ find('[data-testid="work-item-assignees-input"]').click
+ find('[data-testid="clear-all-button"]').click
+ find("body").click
+ wait_for_requests
+
+ expect(work_item.reload.assignees).not_to include(user)
+ end
+
+ it 'successfully removes user on clicking badge cross button' do
+ find('[data-testid="work-item-assignees-input"]').hover
+ find('[data-testid="assign-self"]').click
+ wait_for_requests
+
+ expect(work_item.reload.assignees).to include(user)
+
+ within('[data-testid="work-item-assignees-input"]') do
+ find('[data-testid="close-icon"]').click
+ end
+ find("body").click
+ wait_for_requests
+
+ expect(work_item.reload.assignees).not_to include(user)
+ end
end
RSpec.shared_examples 'work items labels' do
diff --git a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
index 19001abcbe2..ed8feebf1f6 100644
--- a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
+++ b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
@@ -1406,7 +1406,7 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context
it 'returns true' do
expect(finder.use_cte_for_search?).to be_truthy
expect(finder.execute.to_sql)
- .to match(/^WITH "issues" AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported}/)
+ .to match(/^WITH "issues" AS MATERIALIZED/)
end
end
@@ -1416,7 +1416,7 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context
it 'returns true' do
expect(finder.use_cte_for_search?).to be_truthy
expect(finder.execute.to_sql)
- .to match(/^WITH "issues" AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported}/)
+ .to match(/^WITH "issues" AS MATERIALIZED/)
end
end
@@ -1426,7 +1426,7 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context
it 'returns true' do
expect(finder.use_cte_for_search?).to be_truthy
expect(finder.execute.to_sql)
- .to match(/^WITH "issues" AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported}/)
+ .to match(/^WITH "issues" AS MATERIALIZED/)
end
end
@@ -1436,7 +1436,7 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context
it 'returns true' do
expect(finder.use_cte_for_search?).to be_truthy
expect(finder.execute.to_sql)
- .to match(/^WITH "issues" AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported}/)
+ .to match(/^WITH "issues" AS MATERIALIZED/)
end
end
end
diff --git a/spec/support/shared_examples/graphql/mutations/update_time_estimate_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/update_time_estimate_shared_examples.rb
index d6d360bb413..a69b56c3d58 100644
--- a/spec/support/shared_examples/graphql/mutations/update_time_estimate_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/mutations/update_time_estimate_shared_examples.rb
@@ -6,11 +6,25 @@ RSpec.shared_examples 'updating time estimate' do
let(:input_params) { input.merge(extra_params).merge({ timeEstimate: time_estimate }) }
+ before do
+ resource.update!(time_estimate: 1800)
+ end
+
+ context 'when time estimate is not provided' do
+ let(:input_params) { input.merge(extra_params).except(:timeEstimate) }
+
+ it 'does not update' do
+ expect { post_graphql_mutation(mutation, current_user: current_user) }
+ .not_to change { resource.reload.time_estimate }
+ end
+ end
+
context 'when time estimate is not a valid numerical value' do
let(:time_estimate) { '-3.5d' }
it 'does not update' do
- expect { post_graphql_mutation(mutation, current_user: current_user) }.not_to change { resource.time_estimate }
+ expect { post_graphql_mutation(mutation, current_user: current_user) }
+ .not_to change { resource.reload.time_estimate }
end
it 'returns error' do
@@ -24,7 +38,8 @@ RSpec.shared_examples 'updating time estimate' do
let(:time_estimate) { 'nonsense' }
it 'does not update' do
- expect { post_graphql_mutation(mutation, current_user: current_user) }.not_to change { resource.time_estimate }
+ expect { post_graphql_mutation(mutation, current_user: current_user) }
+ .not_to change { resource.reload.time_estimate }
end
it 'returns error' do
@@ -47,6 +62,7 @@ RSpec.shared_examples 'updating time estimate' do
'1h' | 3600
'0h' | 0
'-0h' | 0
+ nil | 0
end
with_them do
diff --git a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
index b346f35bdc9..2c94f21e144 100644
--- a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
@@ -61,14 +61,6 @@ RSpec.shared_examples 'Gitlab-style deprecations' do
expect(deprecable.deprecation_reason).to eq('This was renamed. Deprecated in 1.10.')
end
- it 'supports named reasons: alpha' do
- deprecable = subject(deprecated: { milestone: '1.10', reason: :alpha })
-
- expect(deprecable.deprecation_reason).to eq(
- 'This feature is an Experiment. It can be changed or removed at any time. Introduced in 1.10.'
- )
- end
-
it 'supports :alpha' do
deprecable = subject(alpha: { milestone: '1.10' })
@@ -82,7 +74,7 @@ RSpec.shared_examples 'Gitlab-style deprecations' do
subject(alpha: { milestone: '1.10' }, deprecated: { milestone: '1.10', reason: 'my reason' } )
end.to raise_error(
ArgumentError,
- eq("`experiment` and `deprecated` arguments cannot be passed at the same time")
+ eq("`alpha` and `deprecated` arguments cannot be passed at the same time")
)
end
diff --git a/spec/support/shared_examples/harbor/artifacts_controller_shared_examples.rb b/spec/support/shared_examples/harbor/artifacts_controller_shared_examples.rb
index 85fcd426e3d..16e25bf96dd 100644
--- a/spec/support/shared_examples/harbor/artifacts_controller_shared_examples.rb
+++ b/spec/support/shared_examples/harbor/artifacts_controller_shared_examples.rb
@@ -87,17 +87,7 @@ RSpec.shared_examples 'a harbor artifacts controller' do |args|
get harbor_artifact_url(container, repository_id), headers: json_header
end
- context 'with harbor registry feature flag enabled' do
- it_behaves_like 'responds with 200 status with json'
- end
-
- context 'with harbor registry feature flag disabled' do
- before do
- stub_feature_flags(harbor_registry_integration: false)
- end
-
- it_behaves_like 'responds with 404 status'
- end
+ it_behaves_like 'responds with 200 status with json'
context 'with anonymous user' do
before do
diff --git a/spec/support/shared_examples/harbor/repositories_controller_shared_examples.rb b/spec/support/shared_examples/harbor/repositories_controller_shared_examples.rb
index b35595a10b2..a0d47d1a2d1 100644
--- a/spec/support/shared_examples/harbor/repositories_controller_shared_examples.rb
+++ b/spec/support/shared_examples/harbor/repositories_controller_shared_examples.rb
@@ -87,17 +87,7 @@ RSpec.shared_examples 'a harbor repositories controller' do |args|
get harbor_repository_url(container)
end
- context 'with harbor registry feature flag enabled' do
- it_behaves_like 'responds with 200 status with html'
- end
-
- context 'with harbor registry feature flag disabled' do
- before do
- stub_feature_flags(harbor_registry_integration: false)
- end
-
- it_behaves_like 'responds with 404 status'
- end
+ it_behaves_like 'responds with 200 status with html'
context 'with anonymous user' do
before do
@@ -121,17 +111,7 @@ RSpec.shared_examples 'a harbor repositories controller' do |args|
get harbor_repository_url(container), headers: json_header
end
- context 'with harbor registry feature flag enabled' do
- it_behaves_like 'responds with 200 status with json'
- end
-
- context 'with harbor registry feature flag disabled' do
- before do
- stub_feature_flags(harbor_registry_integration: false)
- end
-
- it_behaves_like 'responds with 404 status'
- end
+ it_behaves_like 'responds with 200 status with json'
context 'with valid params' do
context 'with valid page params' do
diff --git a/spec/support/shared_examples/harbor/tags_controller_shared_examples.rb b/spec/support/shared_examples/harbor/tags_controller_shared_examples.rb
index 46fea7fdff6..aee728295de 100644
--- a/spec/support/shared_examples/harbor/tags_controller_shared_examples.rb
+++ b/spec/support/shared_examples/harbor/tags_controller_shared_examples.rb
@@ -76,17 +76,7 @@ RSpec.shared_examples 'a harbor tags controller' do |args|
headers: json_header)
end
- context 'with harbor registry feature flag enabled' do
- it_behaves_like 'responds with 200 status with json'
- end
-
- context 'with harbor registry feature flag disabled' do
- before do
- stub_feature_flags(harbor_registry_integration: false)
- end
-
- it_behaves_like 'responds with 404 status'
- end
+ it_behaves_like 'responds with 200 status with json'
context 'with anonymous user' do
before do
diff --git a/spec/support/shared_examples/lib/api/ai_workhorse_shared_examples.rb b/spec/support/shared_examples/lib/api/ai_workhorse_shared_examples.rb
deleted file mode 100644
index d4fe45a91a0..00000000000
--- a/spec/support/shared_examples/lib/api/ai_workhorse_shared_examples.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'behind AI related feature flags' do |provider_flag|
- context "when #{provider_flag} is disabled" do
- before do
- stub_feature_flags(provider_flag => false)
- end
-
- it 'responds as not found' do
- post api(url, current_user), params: input_params
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'when ai_experimentation_api is disabled' do
- before do
- stub_feature_flags(ai_experimentation_api: false)
- end
-
- it 'responds as not found' do
- post api(url, current_user), params: input_params
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-end
-
-RSpec.shared_examples 'delegates AI request to Workhorse' do
- it 'responds with Workhorse send-url headers' do
- post api(url, current_user), params: input_params
-
- expect(response.body).to eq('""')
- expect(response).to have_gitlab_http_status(:ok)
-
- send_url_prefix, encoded_data = response.headers['Gitlab-Workhorse-Send-Data'].split(':')
- data = Gitlab::Json.parse(Base64.urlsafe_decode64(encoded_data))
-
- expect(send_url_prefix).to eq('send-url')
- expect(data).to eq({
- 'AllowRedirects' => false,
- 'Method' => 'POST'
- }.merge(expected_params))
- end
-end
diff --git a/spec/support/shared_examples/lib/gitlab/bitbucket_import/object_import_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/bitbucket_import/object_import_shared_examples.rb
new file mode 100644
index 00000000000..3dbe43d822f
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/bitbucket_import/object_import_shared_examples.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples Gitlab::BitbucketImport::ObjectImporter do
+ include AfterNextHelpers
+
+ describe '.sidekiq_retries_exhausted' do
+ let(:job) { { 'args' => [1, {}, 'key'], 'jid' => 'jid' } }
+
+ it 'notifies the waiter' do
+ expect(Gitlab::JobWaiter).to receive(:notify).with('key', 'jid')
+
+ described_class.sidekiq_retries_exhausted_block.call(job, StandardError.new)
+ end
+ end
+
+ describe '#perform' do
+ let_it_be(:import_started_project) { create(:project, :import_started) }
+
+ let(:project_id) { project_id }
+ let(:waiter_key) { 'key' }
+
+ shared_examples 'notifies the waiter' do
+ specify do
+ allow_next(worker.importer_class).to receive(:execute)
+
+ expect(Gitlab::JobWaiter).to receive(:notify).with(waiter_key, anything)
+
+ worker.perform(project_id, {}, waiter_key)
+ end
+ end
+
+ context 'when project does not exist' do
+ let(:project_id) { non_existing_record_id }
+
+ it_behaves_like 'notifies the waiter'
+ end
+
+ context 'when project has import started' do
+ let_it_be(:project) do
+ create(:project, :import_started, import_data_attributes: {
+ data: { 'project_key' => 'key', 'repo_slug' => 'slug' },
+ credentials: { 'token' => 'token' }
+ })
+ end
+
+ let(:project_id) { project.id }
+
+ it 'calls the importer' do
+ expect(Gitlab::BitbucketImport::Logger).to receive(:info).twice
+ expect_next(worker.importer_class, project, kind_of(Hash)).to receive(:execute)
+
+ worker.perform(project_id, {}, waiter_key)
+ end
+
+ it_behaves_like 'notifies the waiter'
+
+ context 'when the importer raises an ActiveRecord::RecordInvalid error' do
+ before do
+ allow_next(worker.importer_class).to receive(:execute).and_raise(ActiveRecord::RecordInvalid)
+ end
+
+ it 'tracks the error' do
+ expect(Gitlab::Import::ImportFailureService).to receive(:track).once
+
+ worker.perform(project_id, {}, waiter_key)
+ end
+ end
+
+ context 'when the importer raises a StandardError' do
+ before do
+ allow_next(worker.importer_class).to receive(:execute).and_raise(StandardError)
+ end
+
+ it 'tracks the error and raises the error' do
+ expect(Gitlab::Import::ImportFailureService).to receive(:track).once
+
+ expect { worker.perform(project_id, {}, waiter_key) }.to raise_error(StandardError)
+ end
+ end
+ end
+
+ context 'when project import has been cancelled' do
+ let_it_be(:project_id) { create(:project, :import_canceled).id }
+
+ it 'does not call the importer' do
+ expect_next(worker.importer_class).not_to receive(:execute)
+
+ worker.perform(project_id, {}, waiter_key)
+ end
+
+ it_behaves_like 'notifies the waiter'
+ end
+ end
+
+ describe '#importer_class' do
+ it 'does not raise a NotImplementedError' do
+ expect(worker.importer_class).not_to be_nil
+ end
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/bitbucket_import/stage_methods_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/bitbucket_import/stage_methods_shared_examples.rb
new file mode 100644
index 00000000000..f128aa92a53
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/bitbucket_import/stage_methods_shared_examples.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples Gitlab::BitbucketImport::StageMethods do
+ describe '.sidekiq_retries_exhausted' do
+ let(:job) { { 'args' => [project.id] } }
+
+ it 'tracks the import failure' do
+ expect(Gitlab::Import::ImportFailureService)
+ .to receive(:track).with(
+ project_id: project.id,
+ exception: StandardError.new,
+ fail_import: true
+ )
+
+ described_class.sidekiq_retries_exhausted_block.call(job, StandardError.new)
+ end
+ end
+
+ describe '.perform' do
+ let(:worker) { described_class.new }
+
+ it 'executes the import' do
+ expect(worker).to receive(:import).with(project).once
+ expect(Gitlab::BitbucketImport::Logger).to receive(:info).twice
+
+ worker.perform(project.id)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/bitbucket_server_import/object_import_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/bitbucket_server_import/object_import_shared_examples.rb
index ec2ae0b8a73..4eae8632467 100644
--- a/spec/support/shared_examples/lib/gitlab/bitbucket_server_import/object_import_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/bitbucket_server_import/object_import_shared_examples.rb
@@ -7,7 +7,7 @@ RSpec.shared_examples Gitlab::BitbucketServerImport::ObjectImporter do
let(:job) { { 'args' => [1, {}, 'key'], 'jid' => 'jid' } }
it 'notifies the waiter' do
- expect(Gitlab::JobWaiter).to receive(:notify).with('key', 'jid')
+ expect(Gitlab::JobWaiter).to receive(:notify).with('key', 'jid', ttl: Gitlab::Import::JOB_WAITER_TTL)
described_class.sidekiq_retries_exhausted_block.call(job, StandardError.new)
end
@@ -23,7 +23,7 @@ RSpec.shared_examples Gitlab::BitbucketServerImport::ObjectImporter do
specify do
allow_next(worker.importer_class).to receive(:execute)
- expect(Gitlab::JobWaiter).to receive(:notify).with(waiter_key, anything)
+ expect(Gitlab::JobWaiter).to receive(:notify).with(waiter_key, anything, ttl: Gitlab::Import::JOB_WAITER_TTL)
worker.perform(project_id, {}, waiter_key)
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 10f58748698..7cfab5c8295 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 ::Ci::Build.sticking.unstick_or_continue_sticking' do
- expect(::Ci::Build.sticking).to receive(:unstick_or_continue_sticking)
+ it 'calls ::Ci::Build.sticking.find_caught_up_replica' do
+ expect(::Ci::Build.sticking).to receive(:find_caught_up_replica)
.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 ::Ci::Build.sticking.unstick_or_continue_sticking' do
- expect(::Ci::Build.sticking).not_to receive(:unstick_or_continue_sticking)
+ it 'does not call ::Ci::Build.sticking.find_caught_up_replica' do
+ expect(::Ci::Build.sticking).not_to receive(:find_caught_up_replica)
trace.read { |stream| stream }
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 df795723874..b80a51a1fc6 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
@@ -4,40 +4,17 @@ RSpec.shared_examples 'CTE with MATERIALIZED keyword examples' do
describe 'adding MATERIALIZE to the CTE' do
let(:options) { {} }
- before do
- # Clear the cached value before the test
- Gitlab::Database::AsWithMaterialized.clear_memoization(:materialized_supported)
- end
-
- context 'when PG version is <12' do
- it 'does not add MATERIALIZE keyword' do
- allow(ApplicationRecord.database).to receive(:version).and_return('11.1')
+ it 'adds MATERIALIZE keyword' do
+ allow(ApplicationRecord.database).to receive(:version).and_return('12.1')
- expect(query).to include(expected_query_block_without_materialized)
- end
+ expect(query).to include(expected_query_block_with_materialized)
end
- context 'when PG version is >=12' do
- it 'adds MATERIALIZE keyword' do
- allow(ApplicationRecord.database).to receive(:version).and_return('12.1')
-
- expect(query).to include(expected_query_block_with_materialized)
- end
+ context 'when materialized is disabled' do
+ let(:options) { { materialized: false } }
- context 'when version is higher than 12' do
- it 'adds MATERIALIZE keyword' do
- allow(ApplicationRecord.database).to receive(:version).and_return('15.1')
-
- expect(query).to include(expected_query_block_with_materialized)
- end
- end
-
- context 'when materialized is disabled' do
- let(:options) { { materialized: false } }
-
- it 'does not add MATERIALIZE keyword' do
- expect(query).to include(expected_query_block_without_materialized)
- end
+ it 'does not add MATERIALIZE keyword' do
+ expect(query).to include(expected_query_block_without_materialized)
end
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/import/advance_stage_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/import/advance_stage_shared_examples.rb
new file mode 100644
index 00000000000..0fef5269ab6
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/import/advance_stage_shared_examples.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples Gitlab::Import::AdvanceStage do |factory:|
+ let_it_be(:project) { create(:project) }
+ let_it_be_with_reload(:import_state) { create(factory, :started, project: project, jid: '123') }
+ let(:worker) { described_class.new }
+ let(:next_stage) { :finish }
+
+ describe '#perform', :clean_gitlab_redis_shared_state do
+ context 'when the project no longer exists' do
+ it 'does not perform any work' do
+ expect(worker).not_to receive(:wait_for_jobs)
+
+ worker.perform(non_existing_record_id, { '123' => 2 }, next_stage)
+ end
+ end
+
+ context 'when there are remaining jobs' do
+ it 'reschedules itself' do
+ expect(worker)
+ .to receive(:wait_for_jobs)
+ .with({ '123' => 2 })
+ .and_return({ '123' => 1 })
+
+ expect(described_class)
+ .to receive(:perform_in)
+ .with(described_class::INTERVAL, project.id, { '123' => 1 }, next_stage)
+
+ worker.perform(project.id, { '123' => 2 }, next_stage)
+ end
+
+ context 'when the project import is not running' do
+ before do
+ import_state.update_column(:status, :failed)
+ end
+
+ it 'does not perform any work' do
+ expect(worker).not_to receive(:wait_for_jobs)
+ expect(described_class).not_to receive(:perform_in)
+
+ worker.perform(project.id, { '123' => 2 }, next_stage)
+ end
+
+ it 'clears the JobWaiter cache' do
+ expect(Gitlab::JobWaiter).to receive(:delete_key).with('123')
+
+ worker.perform(project.id, { '123' => 2 }, next_stage)
+ end
+ end
+ end
+
+ context 'when there are no remaining jobs' do
+ before do
+ allow(worker)
+ .to receive(:wait_for_jobs)
+ .with({ '123' => 2 })
+ .and_return({})
+ end
+
+ it 'schedules the next stage' do
+ next_worker = described_class::STAGES[next_stage]
+
+ expect_next_found_instance_of(import_state.class) do |state|
+ expect(state).to receive(:refresh_jid_expiration)
+ end
+
+ expect(next_worker).to receive(:perform_async).with(project.id)
+
+ worker.perform(project.id, { '123' => 2 }, next_stage)
+ end
+
+ it 'raises KeyError when the stage name is invalid' do
+ expect { worker.perform(project.id, { '123' => 2 }, :kittens) }
+ .to raise_error(KeyError)
+ end
+ end
+ end
+
+ describe '#wait_for_jobs' do
+ it 'waits for jobs to complete and returns a new pair of keys to wait for' do
+ waiter1 = instance_double("Gitlab::JobWaiter", jobs_remaining: 1, key: '123')
+ waiter2 = instance_double("Gitlab::JobWaiter", jobs_remaining: 0, key: '456')
+
+ expect(Gitlab::JobWaiter)
+ .to receive(:new)
+ .ordered
+ .with(2, '123')
+ .and_return(waiter1)
+
+ expect(Gitlab::JobWaiter)
+ .to receive(:new)
+ .ordered
+ .with(1, '456')
+ .and_return(waiter2)
+
+ expect(waiter1)
+ .to receive(:wait)
+ .with(described_class::BLOCKING_WAIT_TIME)
+
+ expect(waiter2)
+ .to receive(:wait)
+ .with(described_class::BLOCKING_WAIT_TIME)
+
+ new_waiters = worker.wait_for_jobs({ '123' => 2, '456' => 1 })
+
+ expect(new_waiters).to eq({ '123' => 1 })
+ end
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb
index c2898513424..025f0d5c7ea 100644
--- a/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb
@@ -15,7 +15,7 @@ RSpec.shared_examples 'a repo type' do
describe '#repository_for' do
it 'finds the repository for the repo type' do
- expect(described_class.repository_for(expected_repository_resolver)).to eq(expected_repository)
+ expect(described_class.repository_for(expected_container)).to eq(expected_repository)
end
it 'returns nil when container is nil' do
diff --git a/spec/support/shared_examples/lib/gitlab/search_archived_filter_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/search_archived_filter_shared_examples.rb
index 6b296d0e78a..ac72b31d5a4 100644
--- a/spec/support/shared_examples/lib/gitlab/search_archived_filter_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/search_archived_filter_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.shared_examples 'search results filtered by archived' do |feature_flag_name|
+RSpec.shared_examples 'search results filtered by archived' do |feature_flag_name, migration_name|
context 'when filter not provided (all behavior)' do
let(:filters) { {} }
@@ -28,16 +28,33 @@ RSpec.shared_examples 'search results filtered by archived' do |feature_flag_nam
end
end
- context "when the #{feature_flag_name} feature flag is disabled" do
- let(:filters) { {} }
+ if feature_flag_name.present?
+ context "when the #{feature_flag_name} feature flag is disabled" do
+ let(:filters) { {} }
+
+ before do
+ stub_feature_flags("#{feature_flag_name}": false)
+ end
- before do
- stub_feature_flags("#{feature_flag_name}": false)
+ it 'returns archived and unarchived results' do
+ expect(results.objects(scope)).to include unarchived_result
+ expect(results.objects(scope)).to include archived_result
+ end
end
+ end
- it 'returns archived and unarchived results' do
- expect(results.objects(scope)).to include unarchived_result
- expect(results.objects(scope)).to include archived_result
+ if migration_name.present?
+ context "when the #{migration_name} is not completed" do
+ let(:filters) { {} }
+
+ before do
+ set_elasticsearch_migration_to(migration_name.to_s, including: false)
+ end
+
+ it 'returns archived and unarchived results' do
+ expect(results.objects(scope)).to include unarchived_result
+ expect(results.objects(scope)).to include archived_result
+ end
end
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb
deleted file mode 100644
index 9dc18555340..00000000000
--- a/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb
+++ /dev/null
@@ -1,87 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'tracked issuable events' do
- before do
- stub_application_setting(usage_ping_enabled: true)
- end
-
- def count_unique(date_from: Date.today.beginning_of_week, date_to: 1.week.from_now)
- Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: action, start_date: date_from, end_date: date_to)
- end
-
- specify do
- aggregate_failures do
- expect(track_action({ author: user1 }.merge(track_params))).to be_truthy
- expect(track_action({ author: user1 }.merge(track_params))).to be_truthy
- expect(track_action({ author: user2 }.merge(track_params))).to be_truthy
- expect(count_unique).to eq(2)
- end
- end
-
- it 'does not track edit actions if author is not present' do
- expect(track_action({ author: nil }.merge(track_params))).to be_nil
- end
-end
-
-RSpec.shared_examples 'tracked issuable snowplow and service ping events for given event params' do
- it_behaves_like 'tracked issuable events'
-
- it 'emits snowplow event' do
- track_action({ author: user1 }.merge(track_params))
-
- expect_snowplow_event(**{ category: category, action: event_action, user: user1 }.merge(event_params))
- end
-end
-
-RSpec.shared_examples 'tracked issuable internal event for given event params' do
- it_behaves_like 'tracked issuable events'
-
- it_behaves_like 'internal event tracking' do
- subject(:track_event) { track_action({ author: user1 }.merge(track_params)) }
-
- let(:user) { user1 }
- let(:namespace) { project&.namespace }
- end
-end
-
-RSpec.shared_examples 'tracked issuable internal event with project' do
- it_behaves_like 'tracked issuable internal event for given event params' do
- let(:track_params) { original_params || { project: project } }
- end
-end
-
-RSpec.shared_examples 'tracked issuable snowplow and service ping events with project' do
- it_behaves_like 'tracked issuable snowplow and service ping events for given event params' do
- let(:context) do
- Gitlab::Tracking::ServicePingContext
- .new(data_source: :redis_hll, event: event_property)
- .to_h
- end
-
- let(:track_params) { original_params || { project: project } }
- let(:event_params) { { project: project }.merge(label: event_label, property: event_property, namespace: project.namespace, context: [context]) }
- end
-end
-
-RSpec.shared_examples 'tracked issuable snowplow and service ping events with namespace' do
- it_behaves_like 'tracked issuable snowplow and service ping events for given event params' do
- let(:context) do
- Gitlab::Tracking::ServicePingContext
- .new(data_source: :redis_hll, event: event_property)
- .to_h
- end
-
- let(:track_params) { { namespace: namespace } }
- let(:event_params) { track_params.merge(label: event_label, property: event_property, context: [context]) }
- end
-end
-
-RSpec.shared_examples 'does not track with namespace when feature flag is disabled' do |feature_flag|
- context "when feature flag #{feature_flag} is disabled" do
- it 'does not track action' do
- stub_feature_flags(feature_flag => false)
-
- expect(track_action(author: user1, namespace: namespace)).to be_nil
- end
- end
-end
diff --git a/spec/support/shared_examples/lib/menus_shared_examples.rb b/spec/support/shared_examples/lib/menus_shared_examples.rb
index 0aa98517444..575f48c43e0 100644
--- a/spec/support/shared_examples/lib/menus_shared_examples.rb
+++ b/spec/support/shared_examples/lib/menus_shared_examples.rb
@@ -62,6 +62,13 @@ RSpec.shared_examples_for 'not serializable as super_sidebar_menu_args' do
end
end
+RSpec.shared_examples_for 'a panel instantiable by the anonymous user' do
+ it do
+ context.instance_variable_set(:@current_user, nil)
+ expect(described_class.new(context)).to be_a(described_class)
+ end
+end
+
RSpec.shared_examples_for 'a panel with uniquely identifiable menu items' do
let(:menu_items) do
subject.instance_variable_get(:@menus)
diff --git a/spec/support/shared_examples/lib/sidebars/user_profile/user_profile_menus_shared_examples.rb b/spec/support/shared_examples/lib/sidebars/user_profile/user_profile_menus_shared_examples.rb
index 5e8aebb4f29..6a43b9b4300 100644
--- a/spec/support/shared_examples/lib/sidebars/user_profile/user_profile_menus_shared_examples.rb
+++ b/spec/support/shared_examples/lib/sidebars/user_profile/user_profile_menus_shared_examples.rb
@@ -1,6 +1,10 @@
# frozen_string_literal: true
-RSpec.shared_examples 'User profile menu' do |title:, icon:, active_route:|
+RSpec.shared_examples 'User profile menu' do |
+ icon:, active_route:, avatar_shape: 'rect', expect_avatar: false, entity_id: nil,
+ # A nil title will fall back to user.name.
+ title: nil
+|
let_it_be(:current_user) { build(:user) }
let_it_be(:user) { build(:user) }
@@ -17,13 +21,19 @@ RSpec.shared_examples 'User profile menu' do |title:, icon:, active_route:|
end
it 'renders the correct title' do
- expect(subject.title).to eq title
+ expect(subject.title).to eq(title || user.name)
end
it 'renders the correct icon' do
expect(subject.sprite_icon).to eq icon
end
+ it 'renders the correct avatar' do
+ expect(subject.avatar).to eq(expect_avatar ? user.avatar_url : nil)
+ expect(subject.avatar_shape).to eq(avatar_shape)
+ expect(subject.entity_id).to eq(entity_id)
+ end
+
it 'defines correct active route' do
expect(subject.active_routes[:path]).to be active_route
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
index 5f59d43ad19..179bbc8734d 100644
--- 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
@@ -55,6 +55,8 @@ RSpec.shared_examples 'it has loose foreign keys' do
end
RSpec.shared_examples 'cleanup by a loose foreign key' do
+ include LooseForeignKeysHelper
+
let(:foreign_key_definition) do
foreign_keys_for_parent = Gitlab::Database::LooseForeignKeys.definitions_by_table[parent.class.table_name]
foreign_keys_for_parent.find { |definition| definition.from_table == model.class.table_name }
@@ -75,9 +77,7 @@ RSpec.shared_examples 'cleanup by a loose foreign key' do
expect(find_model).to be_present
- LooseForeignKeys::DeletedRecord.using_connection(parent.connection) do
- LooseForeignKeys::ProcessDeletedRecordsService.new(connection: parent.connection).execute
- end
+ process_loose_foreign_key_deletions(record: parent)
if foreign_key_definition.on_delete.eql?(:async_delete)
expect(find_model).not_to be_present
diff --git a/spec/support/shared_examples/mailers/notify_shared_examples.rb b/spec/support/shared_examples/mailers/notify_shared_examples.rb
index cf1ab7697ab..987060d73b9 100644
--- a/spec/support/shared_examples/mailers/notify_shared_examples.rb
+++ b/spec/support/shared_examples/mailers/notify_shared_examples.rb
@@ -54,6 +54,14 @@ RSpec.shared_examples 'an email with X-GitLab headers containing IDs' do
expect(subject.header["X-GitLab-#{model.class.name}-IID"]).to eq nil
end
end
+
+ it 'has X-GitLab-*-State header if model has state defined' do
+ if model.respond_to?(:state)
+ is_expected.to have_header "X-GitLab-#{model.class.name}-State", model.state.to_s
+ else
+ expect(subject.header["X-GitLab-#{model.class.name}-State"]).to eq nil
+ end
+ end
end
RSpec.shared_examples 'an email with X-GitLab headers containing project details' do
diff --git a/spec/support/shared_examples/migrations/add_work_item_widget_shared_examples.rb b/spec/support/shared_examples/migrations/add_work_item_widget_shared_examples.rb
index fdb31fa5d9d..37c338a7712 100644
--- a/spec/support/shared_examples/migrations/add_work_item_widget_shared_examples.rb
+++ b/spec/support/shared_examples/migrations/add_work_item_widget_shared_examples.rb
@@ -32,3 +32,110 @@ RSpec.shared_examples 'migration that adds widget to work items definitions' do
end
end
end
+
+# Shared examples for testing migration that adds a single widget to a work item type
+#
+# It expects the following variables
+# - `target_type_enum_value`: Int, enum value for the target work item type, typically defined in the migration
+# as a constant
+# - `target_type`: Symbol, the target type's name
+# - `additional_types`: Hash (optional), name of work item types and their corresponding enum value that are defined
+# at the time the migration was created but are missing from `base_types`.
+# - `widgets_for_type`: Hash, name of the widgets included in the target type with their corresponding enum value
+RSpec.shared_examples 'migration that adds a widget to a work item type' do
+ include MigrationHelpers::WorkItemTypesHelper
+
+ let(:work_item_types) { table(:work_item_types) }
+ let(:work_item_widget_definitions) { table(:work_item_widget_definitions) }
+ let(:additional_base_types) { try(:additional_types) || {} }
+ let(:base_types) do
+ {
+ issue: 0,
+ incident: 1,
+ test_case: 2,
+ requirement: 3,
+ task: 4,
+ objective: 5,
+ key_result: 6,
+ epic: 7
+ }.merge!(additional_base_types)
+ end
+
+ after(:all) do
+ # Make sure base types are recreated after running the migration
+ # because migration specs are not run in a transaction
+ reset_work_item_types
+ end
+
+ before do
+ # Database needs to be in a similar state as when the migration was created
+ reset_db_state_prior_to_migration
+ end
+
+ describe '#up' do
+ it "adds widget to work item type", :aggregate_failures do
+ expect do
+ migrate!
+ end.to change { work_item_widget_definitions.count }.by(1)
+
+ work_item_type = work_item_types.find_by(namespace_id: nil, base_type: target_type_enum_value)
+ created_widget = work_item_widget_definitions.last
+
+ expect(created_widget).to have_attributes(
+ widget_type: described_class::WIDGET_ENUM_VALUE,
+ name: described_class::WIDGET_NAME,
+ work_item_type_id: work_item_type.id
+ )
+ end
+
+ context 'when type does not exist' do
+ it 'skips creating the new widget definition' do
+ work_item_types.where(namespace_id: nil, base_type: base_types[target_type]).delete_all
+
+ expect do
+ migrate!
+ end.to not_change(work_item_widget_definitions, :count)
+ end
+ end
+ end
+
+ describe '#down' do
+ it "removes widget from work item type" do
+ migrate!
+
+ expect { schema_migrate_down! }.to change { work_item_widget_definitions.count }.by(-1)
+ end
+ end
+
+ def reset_db_state_prior_to_migration
+ work_item_types.delete_all
+
+ base_types.each do |type_sym, type_enum|
+ create_work_item_type!(type_sym.to_s.titleize, type_enum)
+ end
+
+ target_type_record = work_item_types.find_by_name(target_type.to_s.titleize)
+
+ widgets = widgets_for_type.map do |widget_name_value, widget_enum_value|
+ {
+ work_item_type_id: target_type_record.id,
+ name: widget_name_value,
+ widget_type: widget_enum_value
+ }
+ end
+
+ # Creating all widgets for the type so the state in the DB is as close as possible to the actual state
+ work_item_widget_definitions.upsert_all(
+ widgets,
+ unique_by: :index_work_item_widget_definitions_on_default_witype_and_name
+ )
+ end
+
+ def create_work_item_type!(type_name, type_enum_value)
+ work_item_types.create!(
+ name: type_name,
+ namespace_id: nil,
+ base_type: type_enum_value
+ )
+ end
+end
diff --git a/spec/support/shared_examples/models/concerns/linkable_items_shared_examples.rb b/spec/support/shared_examples/models/concerns/linkable_items_shared_examples.rb
index efd27a051fe..eb37fe66c11 100644
--- a/spec/support/shared_examples/models/concerns/linkable_items_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/linkable_items_shared_examples.rb
@@ -78,5 +78,16 @@ RSpec.shared_examples 'includes LinkableItem concern' do
expect(described_class.for_items(item, item2)).to contain_exactly(target_link)
end
end
+
+ describe '.for_source_and_target' do
+ let_it_be(:item3) { create(:work_item, project: project) }
+ let_it_be(:link1) { create(link_factory, source: item, target: item1) }
+ let_it_be(:link2) { create(link_factory, source: item, target: item2) }
+ let_it_be(:link3) { create(link_factory, source: item, target: item3) }
+
+ it 'includes links for provided source and target' do
+ expect(described_class.for_source_and_target(item, [item1, item2])).to contain_exactly(link1, link2)
+ end
+ end
end
end
diff --git a/spec/support/shared_examples/models/group_shared_examples.rb b/spec/support/shared_examples/models/group_shared_examples.rb
index 9f3359ba4ab..6397e7a87d7 100644
--- a/spec/support/shared_examples/models/group_shared_examples.rb
+++ b/spec/support/shared_examples/models/group_shared_examples.rb
@@ -3,7 +3,6 @@
RSpec.shared_examples 'checks self and root ancestor feature flag' do
let_it_be(:root_group) { create(:group) }
let_it_be(:group) { create(:group, parent: root_group) }
- let_it_be(:project) { create(:project, group: group) }
subject { group.public_send(feature_flag_method) }
@@ -41,3 +40,47 @@ RSpec.shared_examples 'checks self and root ancestor feature flag' do
it { is_expected.to be_truthy }
end
end
+
+RSpec.shared_examples 'checks self (project) and root ancestor feature flag' do
+ let_it_be(:root_group) { create(:group) }
+ let_it_be(:group) { create(:group, parent: root_group) }
+ let_it_be(:project) { create(:project, group: group) }
+
+ subject { project.public_send(feature_flag_method) }
+
+ context 'when FF is enabled for the root group' do
+ before do
+ stub_feature_flags(feature_flag => root_group)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when FF is enabled for the group' do
+ before do
+ stub_feature_flags(feature_flag => group)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when FF is enabled for the project' do
+ before do
+ stub_feature_flags(feature_flag => project)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when FF is disabled globally' do
+ before do
+ stub_feature_flags(feature_flag => false)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when FF is enabled globally' do
+ it { is_expected.to be_truthy }
+ end
+end
diff --git a/spec/support/shared_examples/models/members_notifications_shared_example.rb b/spec/support/shared_examples/models/members_notifications_shared_example.rb
index 329cb812a08..5c783b5cfa7 100644
--- a/spec/support/shared_examples/models/members_notifications_shared_example.rb
+++ b/spec/support/shared_examples/models/members_notifications_shared_example.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
RSpec.shared_examples 'members notifications' do |entity_type|
+ let_it_be(:user) { create(:user) }
+
let(:notification_service) { double('NotificationService').as_null_object }
before do
@@ -8,7 +10,7 @@ RSpec.shared_examples 'members notifications' do |entity_type|
end
describe "#after_create" do
- let(:member) { build(:"#{entity_type}_member", "#{entity_type}": create(entity_type.to_s)) }
+ let(:member) { build(:"#{entity_type}_member", "#{entity_type}": create(entity_type.to_s), user: user) }
it "sends email to user" do
expect(notification_service).to receive(:"new_#{entity_type}_member").with(member)
@@ -35,7 +37,9 @@ RSpec.shared_examples 'members notifications' do |entity_type|
describe '#after_commit' do
context 'on creation of a member requesting access' do
- let(:member) { build(:"#{entity_type}_member", :access_request, "#{entity_type}": create(entity_type.to_s)) }
+ let(:member) do
+ build(:"#{entity_type}_member", :access_request, "#{entity_type}": create(entity_type.to_s), user: user)
+ end
it "calls NotificationService.new_access_request" do
expect(notification_service).to receive(:new_access_request).with(member)
diff --git a/spec/support/shared_examples/models/users/pages_visits_shared_examples.rb b/spec/support/shared_examples/models/users/pages_visits_shared_examples.rb
new file mode 100644
index 00000000000..0b3e8516d25
--- /dev/null
+++ b/spec/support/shared_examples/models/users/pages_visits_shared_examples.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'namespace visits model' do
+ it { is_expected.to validate_presence_of(:entity_id) }
+ it { is_expected.to validate_presence_of(:user_id) }
+ it { is_expected.to validate_presence_of(:visited_at) }
+
+ describe '#visited_around?' do
+ context 'when the checked time matches a recent visit' do
+ [-15.minutes, 15.minutes].each do |time_diff|
+ it 'returns true' do
+ expect(described_class.visited_around?(entity_id: entity.id, user_id: user.id,
+ time: base_time + time_diff)).to be(true)
+ end
+ end
+ end
+
+ context 'when the checked time does not match a recent visit' do
+ [-16.minutes, 16.minutes].each do |time_diff|
+ it 'returns false' do
+ expect(described_class.visited_around?(entity_id: entity.id, user_id: user.id,
+ time: base_time + time_diff)).to be(false)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/redis/redis_shared_examples.rb b/spec/support/shared_examples/redis/redis_shared_examples.rb
index 9224e01b1fe..23ec4a632b7 100644
--- a/spec/support/shared_examples/redis/redis_shared_examples.rb
+++ b/spec/support/shared_examples/redis/redis_shared_examples.rb
@@ -365,6 +365,90 @@ RSpec.shared_examples "redis_shared_examples" do
end
end
+ describe "#parse_client_tls_options" do
+ let(:dummy_certificate) { OpenSSL::X509::Certificate.new }
+ let(:dummy_key) { OpenSSL::PKey::RSA.new }
+ let(:resque_yaml_config_without_tls) { { url: 'redis://localhost:6379' } }
+ let(:resque_yaml_config_with_tls) do
+ {
+ url: 'rediss://localhost:6380',
+ ssl_params: {
+ cert_file: '/tmp/client.crt',
+ key_file: '/tmp/client.key'
+ }
+ }
+ end
+
+ let(:parsed_config_with_tls) do
+ {
+ url: 'rediss://localhost:6380',
+ ssl_params: {
+ cert: dummy_certificate,
+ key: dummy_key
+ }
+ }
+ end
+
+ before do
+ allow(::File).to receive(:exist?).and_call_original
+ allow(::File).to receive(:read).and_call_original
+ end
+
+ context 'when configuration does not have TLS related options' do
+ it 'returns the coniguration as-is' do
+ expect(subject.send(:parse_client_tls_options,
+ resque_yaml_config_without_tls)).to eq(resque_yaml_config_without_tls)
+ end
+ end
+
+ context 'when specified certificate file does not exist' do
+ before do
+ allow(::File).to receive(:exist?).with("/tmp/client.crt").and_return(false)
+ allow(::File).to receive(:exist?).with("/tmp/client.key").and_return(true)
+ end
+
+ it 'raises error about missing certificate file' do
+ expect do
+ subject.send(:parse_client_tls_options,
+ resque_yaml_config_with_tls)
+ end.to raise_error(Gitlab::Redis::Wrapper::InvalidPathError,
+ "Certificate file /tmp/client.crt specified in in `resque.yml` does not exist.")
+ end
+ end
+
+ context 'when specified key file does not exist' do
+ before do
+ allow(::File).to receive(:exist?).with("/tmp/client.crt").and_return(true)
+ allow(::File).to receive(:read).with("/tmp/client.crt").and_return("DUMMY_CERTIFICATE")
+ allow(OpenSSL::X509::Certificate).to receive(:new).with("DUMMY_CERTIFICATE").and_return(dummy_certificate)
+ allow(::File).to receive(:exist?).with("/tmp/client.key").and_return(false)
+ end
+
+ it 'raises error about missing key file' do
+ expect do
+ subject.send(:parse_client_tls_options,
+ resque_yaml_config_with_tls)
+ end.to raise_error(Gitlab::Redis::Wrapper::InvalidPathError,
+ "Key file /tmp/client.key specified in in `resque.yml` does not exist.")
+ end
+ end
+
+ context 'when configuration valid TLS related options' do
+ before do
+ allow(::File).to receive(:exist?).with("/tmp/client.crt").and_return(true)
+ allow(::File).to receive(:exist?).with("/tmp/client.key").and_return(true)
+ allow(::File).to receive(:read).with("/tmp/client.crt").and_return("DUMMY_CERTIFICATE")
+ allow(::File).to receive(:read).with("/tmp/client.key").and_return("DUMMY_KEY")
+ allow(OpenSSL::X509::Certificate).to receive(:new).with("DUMMY_CERTIFICATE").and_return(dummy_certificate)
+ allow(OpenSSL::PKey).to receive(:read).with("DUMMY_KEY").and_return(dummy_key)
+ end
+
+ it "converts cert_file and key_file appropriately" do
+ expect(subject.send(:parse_client_tls_options, resque_yaml_config_with_tls)).to eq(parsed_config_with_tls)
+ end
+ end
+ end
+
describe '#fetch_config' do
before do
FileUtils.mkdir_p(File.join(rails_root, 'config'))
diff --git a/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb b/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb
index 74dbec063e0..625f16824b4 100644
--- a/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb
+++ b/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb
@@ -72,7 +72,7 @@ RSpec.shared_examples 'GET access tokens are paginated and ordered' do
first_token = assigns(:active_access_tokens).first.as_json
expect(first_token['name']).to eq("Token1")
- expect(first_token['expires_at']).to eq(expires_1_day_from_now.strftime("%Y-%m-%d"))
+ expect(first_token['expires_at']).to eq(expires_1_day_from_now.iso8601)
end
it "orders tokens on id in case token has same expires_at" do
@@ -82,11 +82,11 @@ RSpec.shared_examples 'GET access tokens are paginated and ordered' do
first_token = assigns(:active_access_tokens).first.as_json
expect(first_token['name']).to eq("Token3")
- expect(first_token['expires_at']).to eq(expires_1_day_from_now.strftime("%Y-%m-%d"))
+ expect(first_token['expires_at']).to eq(expires_1_day_from_now.iso8601)
second_token = assigns(:active_access_tokens).second.as_json
expect(second_token['name']).to eq("Token1")
- expect(second_token['expires_at']).to eq(expires_1_day_from_now.strftime("%Y-%m-%d"))
+ expect(second_token['expires_at']).to eq(expires_1_day_from_now.iso8601)
end
end
diff --git a/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb
index 6eceb7c350d..04f340fef37 100644
--- a/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/issue_list_shared_examples.rb
@@ -518,6 +518,39 @@ RSpec.shared_examples 'graphql issue list request spec' do
end
end
+ context 'when fetching external participants' do
+ before_all do
+ issue_a.update!(external_author: 'user@example.com')
+ end
+
+ let(:fields) do
+ <<~QUERY
+ nodes {
+ id
+ externalAuthor
+ }
+ QUERY
+ end
+
+ it 'returns the email address' do
+ post_query
+
+ emails = issues_data.pluck('externalAuthor').compact
+ expect(emails).to contain_exactly('user@example.com')
+ end
+
+ context 'when user does not have access to view emails' do
+ let(:current_user) { external_user }
+
+ it 'obfuscates the email address' do
+ post_query
+
+ emails = issues_data.pluck('externalAuthor').compact
+ expect(emails).to contain_exactly("us*****@e*****.c**")
+ end
+ end
+ end
+
context 'when fetching escalation status' do
let_it_be(:escalation_status) { create(:incident_management_issuable_escalation_status, issue: issue_a) }
let_it_be(:incident_type) { WorkItems::Type.default_by_type(:incident) }
diff --git a/spec/support/shared_examples/requests/api/graphql/work_item_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/work_item_list_shared_examples.rb
new file mode 100644
index 00000000000..a9c422c8f2d
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/graphql/work_item_list_shared_examples.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'graphql work item list request spec' do
+ let(:work_item_ids) { graphql_dig_at(work_item_data, :id) }
+
+ it_behaves_like 'a working graphql query' do
+ before do
+ post_query
+ end
+ end
+
+ describe 'filters' do
+ before do
+ post_query
+ end
+
+ context 'when filtering by author username' do
+ let(:author) { create(:author) }
+ let(:authored_work_item) { create(:work_item, author: author, **container_build_params) }
+
+ let(:item_filter_params) { { author_username: authored_work_item.author.username } }
+
+ it 'returns correct results' do
+ expect(work_item_ids).to contain_exactly(authored_work_item.to_global_id.to_s)
+ end
+ end
+
+ context 'when filtering by state' do
+ let_it_be(:opened_work_item) { create(:work_item, :opened, **container_build_params) }
+ let_it_be(:closed_work_item) { create(:work_item, :closed, **container_build_params) }
+
+ context 'when filtering by state opened' do
+ let(:item_filter_params) { { state: :opened } }
+
+ it 'filters by state' do
+ expect(work_item_ids).to include(opened_work_item.to_global_id.to_s)
+ expect(work_item_ids).not_to include(closed_work_item.to_global_id.to_s)
+ end
+ end
+
+ context 'when filtering by state closed' do
+ let(:item_filter_params) { { state: :closed } }
+
+ it 'filters by state' do
+ expect(work_item_ids).not_to include(opened_work_item.to_global_id.to_s)
+ expect(work_item_ids).to include(closed_work_item.to_global_id.to_s)
+ end
+ end
+ end
+
+ context 'when filtering by type' do
+ let_it_be(:issue_work_item) { create(:work_item, :issue, **container_build_params) }
+ let_it_be(:task_work_item) { create(:work_item, :task, **container_build_params) }
+
+ context 'when filtering by issue type' do
+ let(:item_filter_params) { { types: [:ISSUE] } }
+
+ it 'filters by type' do
+ expect(work_item_ids).to include(issue_work_item.to_global_id.to_s)
+ expect(work_item_ids).not_to include(task_work_item.to_global_id.to_s)
+ end
+ end
+
+ context 'when filtering by task type' do
+ let(:item_filter_params) { { types: [:TASK] } }
+
+ it 'filters by type' do
+ expect(work_item_ids).not_to include(issue_work_item.to_global_id.to_s)
+ expect(work_item_ids).to include(task_work_item.to_global_id.to_s)
+ end
+ end
+ end
+
+ context 'when filtering by iid' do
+ let_it_be(:work_item_by_iid) { create(:work_item, **container_build_params) }
+
+ context 'when using the iid filter' do
+ let(:item_filter_params) { { iid: work_item_by_iid.iid.to_s } }
+
+ it 'returns only items by the given iid' do
+ expect(work_item_ids).to contain_exactly(work_item_by_iid.to_global_id.to_s)
+ end
+ end
+
+ context 'when using the iids filter' do
+ let(:item_filter_params) { { iids: [work_item_by_iid.iid.to_s] } }
+
+ it 'returns only items by the given iid' do
+ expect(work_item_ids).to contain_exactly(work_item_by_iid.to_global_id.to_s)
+ end
+ end
+ end
+ end
+
+ def work_item_data
+ graphql_data.dig(*work_item_node_path)
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb b/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb
index f2c38d70508..00e50b07909 100644
--- a/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb
@@ -8,12 +8,25 @@ RSpec.shared_examples 'MLflow|Not Found - Resource Does Not Exist' do
end
end
-RSpec.shared_examples 'MLflow|Requires api scope' do
+RSpec.shared_examples 'MLflow|Requires api scope and write permission' do
context 'when user has access but token has wrong scope' do
let(:access_token) { tokens[:read] }
it { is_expected.to have_gitlab_http_status(:forbidden) }
end
+
+ context 'when user has access but is not allowed to write' do
+ before do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?)
+ .with(current_user, :write_model_experiments, project)
+ .and_return(false)
+ end
+
+ it "is Unauthorized" do
+ is_expected.to have_gitlab_http_status(:unauthorized)
+ end
+ end
end
RSpec.shared_examples 'MLflow|Requires read_api scope' do
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 6b6bf375827..5f043cdd996 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
@@ -280,7 +280,10 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project|
end
end
- status = :not_found if scope == :group && params[:package_name_type] == :non_existing && !params[:request_forward]
+ if (scope == :group && params[:package_name_type] == :non_existing) &&
+ (!params[:request_forward] || (!params[:auth] && params[:request_forward] && params[:visibility] != :public))
+ status = :not_found
+ end
# Check the error message for :not_found
example_name = 'returning response status with error' if status == :not_found
@@ -873,3 +876,67 @@ RSpec.shared_examples 'rejects invalid package names' do
expect(Gitlab::Json.parse(response.body)).to eq({ 'error' => 'package_name should be a valid file path' })
end
end
+
+RSpec.shared_examples 'handling get metadata requests for packages in multiple projects' do
+ let_it_be(:project2) { create(:project, namespace: namespace) }
+ let_it_be(:package2) do
+ create(:npm_package,
+ project: project2,
+ name: "@#{group.path}/scoped_package",
+ version: '1.2.0')
+ end
+
+ let(:headers) { build_token_auth_header(personal_access_token.token) }
+
+ subject { get(url, headers: headers) }
+
+ before_all do
+ project.update!(visibility: 'private')
+
+ group.add_guest(user)
+ project.add_reporter(user)
+ project2.add_reporter(user)
+ end
+
+ it 'includes all matching package versions in the response' do
+ subject
+
+ expect(json_response['versions'].keys).to match_array([package.version, package2.version])
+ end
+
+ context 'with the feature flag disabled' do
+ before do
+ stub_feature_flags(npm_allow_packages_in_multiple_projects: false)
+ end
+
+ it 'returns matching package versions from only one project' do
+ subject
+
+ expect(json_response['versions'].keys).to match_array([package2.version])
+ end
+ end
+
+ context 'with limited access to the project with the last package version' do
+ before_all do
+ project2.add_guest(user)
+ end
+
+ it 'includes matching package versions from authorized projects in the response' do
+ subject
+
+ expect(json_response['versions'].keys).to contain_exactly(package.version)
+ end
+ end
+
+ context 'with limited access to the project with the first package version' do
+ before do
+ project.add_guest(user)
+ end
+
+ it 'includes matching package versions from authorized projects in the response' do
+ subject
+
+ expect(json_response['versions'].keys).to contain_exactly(package2.version)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
index 2e66bae26ba..1be99040ae5 100644
--- a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
@@ -373,14 +373,6 @@ RSpec.shared_examples 'process nuget download content request' do |user_type, st
end
it_behaves_like 'bumping the package last downloaded at field'
-
- context 'when nuget_normalized_version feature flag is disabled' do
- before do
- stub_feature_flags(nuget_normalized_version: false)
- end
-
- it_behaves_like 'returning response status', :not_found
- end
end
end
end
@@ -710,4 +702,38 @@ RSpec.shared_examples 'nuget upload endpoint' do |symbol_package: false|
it_behaves_like 'returning response status', :forbidden
end
+
+ context 'when package duplicates are not allowed' do
+ let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_headers) }
+ let_it_be(:existing_package) { create(:nuget_package, project: project) }
+ let_it_be(:metadata) { { package_name: existing_package.name, package_version: existing_package.version } }
+ let_it_be(:package_settings) do
+ create(:namespace_package_setting, :group, namespace: project.namespace, nuget_duplicates_allowed: false)
+ end
+
+ before do
+ allow_next_instance_of(::Packages::Nuget::MetadataExtractionService) do |instance|
+ allow(instance).to receive(:execute).and_return(ServiceResponse.success(payload: metadata))
+ end
+ end
+
+ it_behaves_like 'returning response status', :conflict unless symbol_package
+ it_behaves_like 'returning response status', :created if symbol_package
+
+ context 'when exception_regex is set' do
+ before do
+ package_settings.update_column(:nuget_duplicate_exception_regex, ".*#{existing_package.name.last(3)}.*")
+ end
+
+ it_behaves_like 'returning response status', :created
+ end
+
+ context 'when nuget_duplicates_option feature flag is disabled' do
+ before do
+ stub_feature_flags(nuget_duplicates_option: false)
+ end
+
+ it_behaves_like 'returning response status', :created
+ end
+ end
end
diff --git a/spec/support/shared_examples/requests/api_keyset_pagination_shared_examples.rb b/spec/support/shared_examples/requests/api_keyset_pagination_shared_examples.rb
new file mode 100644
index 00000000000..85db36013dd
--- /dev/null
+++ b/spec/support/shared_examples/requests/api_keyset_pagination_shared_examples.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'an endpoint with keyset pagination' do |invalid_order: 'name', invalid_sort: 'asc'|
+ include KeysetPaginationHelpers
+
+ let(:keyset_params) { { pagination: 'keyset', per_page: 1 } }
+ let(:additional_params) { {} }
+
+ subject do
+ get api_call, params: keyset_params.merge(additional_params)
+ response
+ end
+
+ context 'on making requests with supported ordering structure' do
+ it 'includes keyset url params in the url response' do
+ is_expected.to have_gitlab_http_status(:ok)
+ is_expected.to include_keyset_url_params
+ end
+
+ it 'does not include pagination headers' do
+ is_expected.to have_gitlab_http_status(:ok)
+ is_expected.not_to include_pagination_headers
+ end
+
+ it 'paginates the records correctly', :aggregate_failures do
+ is_expected.to have_gitlab_http_status(:ok)
+ records = json_response
+ expect(records.size).to eq(1)
+ expect(records.first['id']).to eq(first_record.id)
+
+ get api_call, params: pagination_params_from_next_url(response)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ records = Gitlab::Json.parse(response.body)
+ expect(records.size).to eq(1)
+ expect(records.first['id']).to eq(second_record.id)
+ end
+ end
+
+ context 'on making requests with unsupported ordering structure' do
+ let(:additional_params) { { order_by: invalid_order, sort: invalid_sort } }
+
+ if invalid_order
+ it 'returns error', :aggregate_failures do
+ is_expected.to have_gitlab_http_status(:method_not_allowed)
+ expect(json_response['error']).to eq('Keyset pagination is not yet available for this type of request')
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
index dafa324b3c6..48d3e438322 100644
--- a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
+++ b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
@@ -148,6 +148,12 @@ RSpec.shared_examples 'rate-limited token requests' do
expect(response).not_to have_gitlab_http_status(:too_many_requests)
end
+ matched = throttle_types[throttle_setting_prefix]
+
+ if request_method == 'GET' && throttle_setting_prefix == 'throttle_protected_paths'
+ matched = 'throttle_authenticated_get_protected_paths_api'
+ end
+
arguments = a_hash_including({
message: 'Rack_Attack',
status: 429,
@@ -155,7 +161,7 @@ RSpec.shared_examples 'rate-limited token requests' do
remote_ip: '127.0.0.1',
request_method: request_method,
path: request_args.first,
- matched: throttle_types[throttle_setting_prefix]
+ matched: matched
}.merge(log_data))
expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
@@ -166,7 +172,14 @@ RSpec.shared_examples 'rate-limited token requests' do
end
it_behaves_like 'tracking when dry-run mode is set' do
- let(:throttle_name) { throttle_types[throttle_setting_prefix] }
+ let(:throttle_name) do
+ name = throttle_types[throttle_setting_prefix]
+ if request_method == 'GET' && throttle_setting_prefix == 'throttle_protected_paths'
+ name = 'throttle_authenticated_get_protected_paths_api'
+ end
+
+ name
+ end
def do_request
make_request(request_args)
@@ -315,7 +328,13 @@ RSpec.shared_examples 'rate-limited web authenticated requests' do
expect(response).not_to have_gitlab_http_status(:too_many_requests)
end
- arguments = a_hash_including({
+ matched = throttle_types[throttle_setting_prefix]
+
+ if request_method == 'GET' && throttle_setting_prefix == 'throttle_protected_paths'
+ matched = 'throttle_authenticated_get_protected_paths_web'
+ end
+
+ arguments = a_hash_including(
message: 'Rack_Attack',
status: 429,
env: :throttle,
@@ -324,15 +343,22 @@ RSpec.shared_examples 'rate-limited web authenticated requests' do
path: url_that_requires_authentication,
user_id: user.id,
'meta.user' => user.username,
- matched: throttle_types[throttle_setting_prefix]
- })
+ matched: matched
+ )
expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
expect { request_authenticated_web_url }.not_to exceed_query_limit(control_count)
end
it_behaves_like 'tracking when dry-run mode is set' do
- let(:throttle_name) { throttle_types[throttle_setting_prefix] }
+ let(:throttle_name) do
+ name = throttle_types[throttle_setting_prefix]
+ if request_method == 'GET' && throttle_setting_prefix == 'throttle_protected_paths'
+ name = 'throttle_authenticated_get_protected_paths_web'
+ end
+
+ name
+ end
def do_request
request_authenticated_web_url
diff --git a/spec/support/shared_examples/services/incident_shared_examples.rb b/spec/support/shared_examples/services/incident_shared_examples.rb
index db2b448f567..94467ad53fa 100644
--- a/spec/support/shared_examples/services/incident_shared_examples.rb
+++ b/spec/support/shared_examples/services/incident_shared_examples.rb
@@ -40,7 +40,7 @@ end
RSpec.shared_examples 'incident management label service' do
let_it_be(:project) { create(:project, :private) }
- let_it_be(:user) { User.alert_bot }
+ let_it_be(:user) { Users::Internal.alert_bot }
let(:service) { described_class.new(project, user) }
subject(:execute) { service.execute }
diff --git a/spec/support/shared_examples/services/issuable/issuable_update_service_shared_examples.rb b/spec/support/shared_examples/services/issuable/issuable_update_service_shared_examples.rb
index 3f95d6060ea..9624f7a4450 100644
--- a/spec/support/shared_examples/services/issuable/issuable_update_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/issuable/issuable_update_service_shared_examples.rb
@@ -145,6 +145,77 @@ RSpec.shared_examples 'updating issuable labels' do
end
end
+RSpec.shared_examples 'updating merged MR with locked labels' do
+ context 'when add_label_ids and label_ids are passed' do
+ let(:params) { { label_ids: [label_a.id], add_label_ids: [label_c.id] } }
+
+ it 'replaces unlocked labels with the ones in label_ids and adds those in add_label_ids' do
+ issuable.update!(labels: [label_b, label_unlocked])
+ update_issuable(params)
+
+ expect(issuable.label_ids).to contain_exactly(label_a.id, label_b.id, label_c.id)
+ end
+ end
+
+ context 'when remove_label_ids and label_ids are passed' do
+ let(:params) { { label_ids: [label_a.id, label_b.id, label_c.id], remove_label_ids: [label_a.id] } }
+
+ it 'replaces unlocked labels with the ones in label_ids and does not remove locked label in remove_label_ids' do
+ issuable.update!(labels: [label_a, label_c, label_unlocked])
+ update_issuable(params)
+
+ expect(issuable.label_ids).to contain_exactly(label_a.id, label_b.id, label_c.id)
+ end
+ end
+
+ context 'when add_label_ids and remove_label_ids are passed' do
+ let(:params) { { add_label_ids: [label_c.id], remove_label_ids: [label_a.id, label_unlocked.id] } }
+
+ before do
+ issuable.update!(labels: [label_a, label_unlocked])
+ update_issuable(params)
+ end
+
+ it 'adds the passed labels' do
+ expect(issuable.label_ids).to include(label_c.id)
+ end
+
+ it 'removes the passed unlocked labels' do
+ expect(issuable.label_ids).to include(label_a.id)
+ expect(issuable.label_ids).not_to include(label_unlocked.id)
+ end
+ end
+
+ context 'when same id is passed as add_label_ids and remove_label_ids' do
+ let(:params) { { add_label_ids: [label_a.id], remove_label_ids: [label_a.id] } }
+
+ context 'for a label assigned to an issue' do
+ it 'does not remove the label' do
+ issuable.update!(labels: [label_a])
+ update_issuable(params)
+
+ expect(issuable.label_ids).to contain_exactly(label_a.id)
+ end
+ end
+
+ context 'for a label not assigned to an issue' do
+ it 'does not add the label' do
+ expect(issuable.label_ids).to be_empty
+ end
+ end
+ end
+
+ context 'when duplicate label titles are given' do
+ let(:params) { { labels: [label_c.title, label_c.title] } }
+
+ it 'assigns the label once' do
+ update_issuable(params)
+
+ expect(issuable.labels).to contain_exactly(label_c)
+ end
+ end
+end
+
RSpec.shared_examples 'keeps issuable labels sorted after update' do
before do
update_issuable(label_ids: [label_b.id])
diff --git a/spec/support/shared_examples/services/issuable_links/destroyable_issuable_links_shared_examples.rb b/spec/support/shared_examples/services/issuable_links/destroyable_issuable_links_shared_examples.rb
index 1532e870dcc..b955b71a6bb 100644
--- a/spec/support/shared_examples/services/issuable_links/destroyable_issuable_links_shared_examples.rb
+++ b/spec/support/shared_examples/services/issuable_links/destroyable_issuable_links_shared_examples.rb
@@ -1,13 +1,7 @@
# frozen_string_literal: true
-RSpec.shared_examples 'a destroyable issuable link' do |required_role: :reporter|
+RSpec.shared_examples 'a destroyable issuable link' do
context 'when successfully removes an issuable link' do
- before do
- [issuable_link.target, issuable_link.source].each do |issuable|
- issuable.resource_parent.try(:"add_#{required_role}", user)
- end
- end
-
it 'removes related issue' do
expect { subject }.to change { issuable_link.class.count }.by(-1)
end
@@ -28,6 +22,9 @@ RSpec.shared_examples 'a destroyable issuable link' do |required_role: :reporter
end
context 'when failing to remove an issuable link' do
+ let_it_be(:non_member) { create(:user) }
+ let(:user) { non_member }
+
it 'does not remove relation' do
expect { subject }.not_to change { issuable_link.class.count }.from(1)
end
diff --git a/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb b/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb
deleted file mode 100644
index 9b2e038a331..00000000000
--- a/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb
+++ /dev/null
@@ -1,193 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'misconfigured dashboard service response' do |status_code, message = nil|
- it 'returns an appropriate message and status code', :aggregate_failures do
- result = service_call
-
- expect(result.keys).to contain_exactly(:message, :http_status, :status)
- expect(result[:status]).to eq(:error)
- expect(result[:http_status]).to eq(status_code)
- expect(result[:message]).to eq(message) if message
- end
-end
-
-RSpec.shared_examples 'valid dashboard service response for schema' do
- it 'returns a json representation of the dashboard' do
- result = service_call
-
- expect(result.keys).to contain_exactly(:dashboard, :status)
- expect(result[:status]).to eq(:success)
-
- schema_path = Rails.root.join('spec/fixtures', dashboard_schema)
- validator = JSONSchemer.schema(schema_path)
- expect(validator.valid?(result[:dashboard].with_indifferent_access)).to be true
- end
-end
-
-RSpec.shared_examples 'valid dashboard service response' do
- let(:dashboard_schema) { 'lib/gitlab/metrics/dashboard/schemas/dashboard.json' }
-
- it_behaves_like 'valid dashboard service response for schema'
-end
-
-RSpec.shared_examples 'caches the unprocessed dashboard for subsequent calls' do
- specify do
- expect_next_instance_of(::Gitlab::Config::Loader::Yaml) do |loader|
- expect(loader).to receive(:load_raw!).once.and_call_original
- end
-
- described_class.new(*service_params).get_dashboard
- described_class.new(*service_params).get_dashboard
- end
-end
-
-# This spec is applicable for predefined/out-of-the-box dashboard services.
-RSpec.shared_examples 'refreshes cache when dashboard_version is changed' do
- specify do
- allow_next_instance_of(described_class) do |service|
- allow(service).to receive(:dashboard_version).and_return('1', '2')
- end
-
- expect_file_read(Rails.root.join(described_class::DASHBOARD_PATH)).twice.and_call_original
-
- service = described_class.new(*service_params)
-
- service.get_dashboard
- service.get_dashboard
- end
-end
-
-# This spec is applicable for predefined/out-of-the-box dashboard services.
-# This shared_example requires the following variables to be defined:
-# dashboard_path: Relative path to the dashboard, ex: 'config/prometheus/common_metrics.yml'
-# dashboard_version: The version string used in the cache_key.
-RSpec.shared_examples 'dashboard_version contains SHA256 hash of dashboard file content' do
- specify do
- dashboard = File.read(Rails.root.join(dashboard_path))
- expect(dashboard_version).to eq(Digest::SHA256.hexdigest(dashboard))
- end
-end
-
-RSpec.shared_examples 'valid embedded dashboard service response' do
- let(:dashboard_schema) { 'lib/gitlab/metrics/dashboard/schemas/embedded_dashboard.json' }
-
- it_behaves_like 'valid dashboard service response for schema'
-end
-
-RSpec.shared_examples 'raises error for users with insufficient permissions' do
- context 'when the user does not have sufficient access' do
- let(:user) { build(:user) }
-
- it_behaves_like 'misconfigured dashboard service response', :unauthorized
- end
-
- context 'when the user is anonymous' do
- let(:user) { nil }
-
- it_behaves_like 'misconfigured dashboard service response', :unauthorized
- end
-end
-
-RSpec.shared_examples 'valid dashboard cloning process' do |dashboard_template, sequence|
- context "dashboard template: #{dashboard_template}" do
- let(:dashboard) { dashboard_template }
- let(:dashboard_attrs) do
- {
- commit_message: commit_message,
- branch_name: branch,
- start_branch: project.default_branch,
- encoding: 'text',
- file_path: ".gitlab/dashboards/#{file_name}",
- file_content: file_content_hash.to_yaml
- }
- end
-
- it 'delegates commit creation to Files::CreateService', :aggregate_failures do
- service_instance = instance_double(::Files::CreateService)
- allow(::Gitlab::Metrics::Dashboard::Processor).to receive(:new).and_return(double(process: file_content_hash))
- expect(::Files::CreateService).to receive(:new).with(project, user, dashboard_attrs).and_return(service_instance)
- expect(service_instance).to receive(:execute).and_return(status: :success)
-
- service_call
- end
-
- context 'user has defined custom metrics' do
- it 'uses external service to includes them into new file content', :aggregate_failures do
- service_instance = double(::Gitlab::Metrics::Dashboard::Processor)
- expect(::Gitlab::Metrics::Dashboard::Processor).to receive(:new).with(project, file_content_hash, sequence, {}).and_return(service_instance)
- expect(service_instance).to receive(:process).and_return(file_content_hash)
- expect(::Files::CreateService).to receive(:new).with(project, user, dashboard_attrs).and_return(double(execute: { status: :success }))
-
- service_call
- end
- end
- end
-end
-
-RSpec.shared_examples 'misconfigured dashboard service response with stepable' do |status_code, message = nil|
- it 'returns an appropriate message and status code', :aggregate_failures do
- result = service_call
-
- expect(result.keys).to contain_exactly(:message, :http_status, :status, :last_step)
- expect(result[:status]).to eq(:error)
- expect(result[:http_status]).to eq(status_code)
- expect(result[:message]).to eq(message) if message
- end
-end
-
-RSpec.shared_examples 'updates gitlab_metrics_dashboard_processing_time_ms metric' do
- specify :prometheus do
- service_call
- metric = subject.send(:processing_time_metric)
- labels = subject.send(:processing_time_metric_labels)
-
- expect(metric.get(labels)).to be > 0
- end
-end
-
-RSpec.shared_examples '#raw_dashboard raises error if dashboard loading fails' do
- context 'when yaml is too large' do
- before do
- allow_next_instance_of(::Gitlab::Config::Loader::Yaml) do |loader|
- allow(loader).to receive(:load_raw!)
- .and_raise(Gitlab::Config::Loader::Yaml::DataTooLargeError, 'The parsed YAML is too big')
- end
- end
-
- it 'raises error' do
- expect { subject.raw_dashboard }.to raise_error(
- Gitlab::Metrics::Dashboard::Errors::LayoutError,
- 'The parsed YAML is too big'
- )
- end
- end
-
- context 'when yaml loader returns error' do
- before do
- allow_next_instance_of(::Gitlab::Config::Loader::Yaml) do |loader|
- allow(loader).to receive(:load_raw!)
- .and_raise(Gitlab::Config::Loader::FormatError, 'Invalid configuration format')
- end
- end
-
- it 'raises error' do
- expect { subject.raw_dashboard }.to raise_error(
- Gitlab::Metrics::Dashboard::Errors::LayoutError,
- 'Invalid yaml'
- )
- end
- end
-
- context 'when yaml is not a hash' do
- before do
- allow_next_instance_of(::Gitlab::Config::Loader::Yaml) do |loader|
- allow(loader).to receive(:load_raw!)
- .and_raise(Gitlab::Config::Loader::Yaml::NotHashError, 'Invalid configuration format')
- end
- end
-
- it 'returns nil' do
- expect(subject.raw_dashboard).to eq({})
- end
- end
-end
diff --git a/spec/support/shared_examples/services/migrate_to_ghost_user_service_shared_examples.rb b/spec/support/shared_examples/services/migrate_to_ghost_user_service_shared_examples.rb
index e77d73d1c72..621fb99afe5 100644
--- a/spec/support/shared_examples/services/migrate_to_ghost_user_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/migrate_to_ghost_user_service_shared_examples.rb
@@ -38,7 +38,7 @@ RSpec.shared_examples "migrating a deleted user's associated records to the ghos
migrated_record = record_class.find_by_id(record.id)
migrated_fields.each do |field|
- expect(migrated_record.public_send(field)).to eq(User.ghost)
+ expect(migrated_record.public_send(field)).to eq(Users::Internal.ghost)
end
end
@@ -47,7 +47,7 @@ RSpec.shared_examples "migrating a deleted user's associated records to the ghos
migrated_record = record_class.find_by_id(record.id)
- check_user = always_ghost ? User.ghost : user
+ check_user = always_ghost ? Users::Internal.ghost : user
migrated_fields.each do |field|
expect(migrated_record.public_send(field)).to eq(check_user)
diff --git a/spec/support/shared_examples/services/pages_size_limit_shared_examples.rb b/spec/support/shared_examples/services/pages_size_limit_shared_examples.rb
deleted file mode 100644
index d9e906ebb75..00000000000
--- a/spec/support/shared_examples/services/pages_size_limit_shared_examples.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'pages size limit is' do |size_limit|
- context "when size is below the limit" do
- before do
- allow(metadata).to receive(:total_size).and_return(size_limit - 1.megabyte)
- allow(metadata).to receive(:entries).and_return([])
- end
-
- it 'updates pages correctly' do
- subject.execute
-
- expect(deploy_status.description).not_to be_present
- expect(project.pages_metadatum).to be_deployed
- end
- end
-
- context "when size is above the limit" do
- before do
- allow(metadata).to receive(:total_size).and_return(size_limit + 1.megabyte)
- allow(metadata).to receive(:entries).and_return([])
- end
-
- it 'limits the maximum size of gitlab pages' do
- subject.execute
-
- expect(deploy_status.description)
- .to match(/artifacts for pages are too large/)
- expect(deploy_status).to be_script_failure
- end
- end
-end
diff --git a/spec/support/shared_examples/services/protected_branches_shared_examples.rb b/spec/support/shared_examples/services/protected_branches_shared_examples.rb
new file mode 100644
index 00000000000..ce607a6b956
--- /dev/null
+++ b/spec/support/shared_examples/services/protected_branches_shared_examples.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'with scan result policy blocking protected branches' do
+ before do
+ create(
+ :scan_result_policy_read,
+ :blocking_protected_branches,
+ project: project)
+
+ stub_licensed_features(security_orchestration_policies: true)
+ end
+end
diff --git a/spec/support/shared_examples/services/users/build_service_shared_examples.rb b/spec/support/shared_examples/services/users/build_service_shared_examples.rb
index e448f2f874b..45ebc837e6d 100644
--- a/spec/support/shared_examples/services/users/build_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/users/build_service_shared_examples.rb
@@ -33,57 +33,6 @@ RSpec.shared_examples 'common user build items' do
end
RSpec.shared_examples_for 'current user not admin build items' do
- using RSpec::Parameterized::TableSyntax
-
- context 'with "user_default_external" application setting' do
- where(:user_default_external, :external, :email, :user_default_internal_regex, :result) do
- true | nil | 'fl@example.com' | nil | true
- true | true | 'fl@example.com' | nil | true
- true | false | 'fl@example.com' | nil | true # admin difference
-
- true | nil | 'fl@example.com' | '' | true
- true | true | 'fl@example.com' | '' | true
- true | false | 'fl@example.com' | '' | true # admin difference
-
- true | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
- true | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false # admin difference
- true | false | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
-
- true | nil | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
- true | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
- true | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true # admin difference
-
- false | nil | 'fl@example.com' | nil | false
- false | true | 'fl@example.com' | nil | false # admin difference
- false | false | 'fl@example.com' | nil | false
-
- false | nil | 'fl@example.com' | '' | false
- false | true | 'fl@example.com' | '' | false # admin difference
- false | false | 'fl@example.com' | '' | false
-
- false | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
- false | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false # admin difference
- false | false | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
-
- false | nil | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
- false | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false # admin difference
- false | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
- end
-
- with_them do
- before do
- stub_application_setting(user_default_external: user_default_external)
- stub_application_setting(user_default_internal_regex: user_default_internal_regex)
-
- params.merge!({ external: external, email: email }.compact)
- end
-
- it 'sets the value of Gitlab::CurrentSettings.user_default_external' do
- expect(user.external).to eq(result)
- end
- end
- end
-
context 'when "email_confirmation_setting" application setting is set to `hard`' do
before do
stub_application_setting_enum('email_confirmation_setting', 'hard')
diff --git a/spec/support/shared_examples/users/migrate_records_to_ghost_user_service_shared_examples.rb b/spec/support/shared_examples/users/migrate_records_to_ghost_user_service_shared_examples.rb
index eb03f0888b9..5d24aa10453 100644
--- a/spec/support/shared_examples/users/migrate_records_to_ghost_user_service_shared_examples.rb
+++ b/spec/support/shared_examples/users/migrate_records_to_ghost_user_service_shared_examples.rb
@@ -32,7 +32,7 @@ RSpec.shared_examples 'migrating records to the ghost user' do |record_class, fi
migrated_record = record_class.find_by_id(record.id)
migrated_fields.each do |field|
- expect(migrated_record.public_send(field)).to eq(User.ghost)
+ expect(migrated_record.public_send(field)).to eq(Users::Internal.ghost)
end
end
end
diff --git a/spec/support/shared_examples/users/pages_visits_shared_examples.rb b/spec/support/shared_examples/users/pages_visits_shared_examples.rb
new file mode 100644
index 00000000000..1a613fa0aed
--- /dev/null
+++ b/spec/support/shared_examples/users/pages_visits_shared_examples.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'namespace visits tracking worker' do
+ let_it_be(:base_time) { DateTime.now }
+
+ context 'when params are provided' do
+ before do
+ worker.perform(entity_type, entity.id, user.id, base_time)
+ end
+
+ include_examples 'an idempotent worker' do
+ let(:job_args) { [entity_type, entity.id, user.id, base_time] }
+
+ it 'tracks the entity visit' do
+ latest_visit = model.last
+
+ expect(model.count).to be(1)
+ expect(latest_visit[:entity_id]).to be(entity.id)
+ expect(latest_visit.user_id).to be(user.id)
+ end
+ end
+
+ context 'when a visit occurs within 15 minutes of a previously tracked one' do
+ [-15.minutes, 15.minutes].each do |time_diff|
+ it 'does not track the visit' do
+ worker.perform(entity_type, entity.id, user.id, base_time + time_diff)
+
+ expect(model.count).to be(1)
+ end
+ end
+ end
+
+ context 'when a visit occurs more than 15 minutes away from a previously tracked one' do
+ [-16.minutes, 16.minutes].each do |time_diff|
+ it 'tracks the visit' do
+ worker.perform(entity_type, entity.id, user.id, base_time + time_diff)
+
+ expect(model.count).to be > 1
+ end
+ end
+ end
+ end
+
+ context 'when user is missing' do
+ before do
+ worker.perform(entity_type, entity.id, nil, base_time)
+ end
+
+ it 'does not do anything' do
+ expect(model.count).to be(0)
+ end
+ end
+
+ context 'when entity is missing' do
+ before do
+ worker.perform(entity_type, nil, user.id, base_time)
+ end
+
+ it 'does not do anything' do
+ expect(model.count).to be(0)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/workers/background_migration_worker_shared_examples.rb b/spec/support/shared_examples/workers/background_migration_worker_shared_examples.rb
index 8ecb04bfdd6..1ea5eb6fd2e 100644
--- a/spec/support/shared_examples/workers/background_migration_worker_shared_examples.rb
+++ b/spec/support/shared_examples/workers/background_migration_worker_shared_examples.rb
@@ -1,6 +1,10 @@
# frozen_string_literal: true
RSpec.shared_examples 'it runs background migration jobs' do |tracking_database|
+ before do
+ stub_feature_flags(disallow_database_ddl_feature_flags: false)
+ end
+
describe 'defining the job attributes' do
it 'defines the data_consistency as always' do
expect(described_class.get_data_consistency).to eq(:always)
@@ -74,6 +78,38 @@ RSpec.shared_examples 'it runs background migration jobs' do |tracking_database|
end
end
+ context 'when disallow_database_ddl_feature_flags feature flag is disabled' do
+ before do
+ stub_feature_flags(disallow_database_ddl_feature_flags: true)
+ end
+
+ it 'does not perform the job, reschedules it in the future, and logs a message' do
+ expect(worker).not_to receive(:perform_with_connection)
+
+ expect(Sidekiq.logger).to receive(:info) do |payload|
+ expect(payload[:class]).to eq(described_class.name)
+ expect(payload[:database]).to eq(tracking_database)
+ expect(payload[:message]).to match(/skipping execution, migration rescheduled/)
+ end
+
+ lease_attempts = 3
+ delay = described_class::BACKGROUND_MIGRATIONS_DELAY
+ job_args = [10, 20]
+
+ freeze_time do
+ worker.perform('Foo', job_args, lease_attempts)
+
+ job = described_class.jobs.find { |job| job['args'] == ['Foo', job_args, lease_attempts] }
+ expect(job).to be, "Expected the job to be rescheduled with (#{job_args}, #{lease_attempts}), but it was not."
+
+ expected_time = delay.to_i + Time.now.to_i
+ expect(job['at']).to eq(expected_time),
+ "Expected the job to be rescheduled in #{expected_time} seconds, " \
+ "but it was rescheduled in #{job['at']} seconds."
+ end
+ end
+ end
+
context 'when execute_background_migrations feature flag is enabled' do
before do
stub_feature_flags(execute_background_migrations: true)
diff --git a/spec/support/shared_examples/workers/batched_background_migration_execution_worker_shared_example.rb b/spec/support/shared_examples/workers/batched_background_migration_execution_worker_shared_example.rb
index 8fdd59d1d8c..cf488a4d753 100644
--- a/spec/support/shared_examples/workers/batched_background_migration_execution_worker_shared_example.rb
+++ b/spec/support/shared_examples/workers/batched_background_migration_execution_worker_shared_example.rb
@@ -3,6 +3,10 @@
RSpec.shared_examples 'batched background migrations execution worker' do
include ExclusiveLeaseHelpers
+ before do
+ stub_feature_flags(disallow_database_ddl_feature_flags: false)
+ end
+
it 'is a limited capacity worker' do
expect(described_class.new).to be_a(LimitedCapacity::Worker)
end
@@ -100,6 +104,23 @@ RSpec.shared_examples 'batched background migrations execution worker' do
end
end
+ context 'when disable ddl flag is enabled' do
+ let(:migration) do
+ create(:batched_background_migration, :active, interval: job_interval, table_name: table_name)
+ end
+
+ before do
+ stub_feature_flags(disallow_database_ddl_feature_flags: true)
+ end
+
+ it 'does nothing' do
+ expect(Gitlab::Database::BackgroundMigration::BatchedMigration).not_to receive(:find_executable)
+ expect(worker).not_to receive(:run_migration_job)
+
+ worker.perform_work(database_name, migration.id)
+ end
+ end
+
context 'when the feature flag is enabled' do
before do
stub_feature_flags(execute_batched_migrations_on_schedule: true)
diff --git a/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb b/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
index e7385f9abb6..003b8d07819 100644
--- a/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
+++ b/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
@@ -3,6 +3,10 @@
RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_database, table_name|
include ExclusiveLeaseHelpers
+ before do
+ stub_feature_flags(disallow_database_ddl_feature_flags: false)
+ end
+
describe 'defining the job attributes' do
it 'defines the data_consistency as always' do
expect(described_class.get_data_consistency).to eq(:always)
@@ -51,6 +55,12 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
expect(described_class.enabled?).to be_falsey
end
+
+ it 'returns false when disallow_database_ddl_feature_flags feature flag is enabled' do
+ stub_feature_flags(disallow_database_ddl_feature_flags: true)
+
+ expect(described_class.enabled?).to be_falsey
+ end
end
describe '#perform' do
@@ -116,6 +126,18 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
end
end
+ context 'when the disallow_database_ddl_feature_flags feature flag is enabled' do
+ before do
+ stub_feature_flags(disallow_database_ddl_feature_flags: true)
+ end
+
+ it 'does nothing' do
+ expect(worker).not_to receive(:queue_migrations_for_execution)
+
+ worker.perform
+ end
+ end
+
context 'when the execute_batched_migrations_on_schedule feature flag is enabled' do
let(:base_model) { Gitlab::Database.database_base_models[tracking_database] }
let(:connection) { base_model.connection }