summaryrefslogtreecommitdiff
path: root/spec/services
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-05-19 15:44:42 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-05-19 15:44:42 +0000
commit4555e1b21c365ed8303ffb7a3325d773c9b8bf31 (patch)
tree5423a1c7516cffe36384133ade12572cf709398d /spec/services
parente570267f2f6b326480d284e0164a6464ba4081bc (diff)
downloadgitlab-ce-4555e1b21c365ed8303ffb7a3325d773c9b8bf31.tar.gz
Add latest changes from gitlab-org/gitlab@13-12-stable-eev13.12.0-rc42
Diffstat (limited to 'spec/services')
-rw-r--r--spec/services/admin/propagate_service_template_spec.rb6
-rw-r--r--spec/services/alert_management/process_prometheus_alert_service_spec.rb236
-rw-r--r--spec/services/analytics/cycle_analytics/stages/list_service_spec.rb25
-rw-r--r--spec/services/application_settings/update_service_spec.rb26
-rw-r--r--spec/services/auto_merge/base_service_spec.rb8
-rw-r--r--spec/services/boards/lists/destroy_service_spec.rb26
-rw-r--r--spec/services/boards/lists/update_service_spec.rb4
-rw-r--r--spec/services/boards/visits/create_service_spec.rb43
-rw-r--r--spec/services/branches/delete_service_spec.rb2
-rw-r--r--spec/services/bulk_create_integration_service_spec.rb4
-rw-r--r--spec/services/bulk_imports/export_service_spec.rb43
-rw-r--r--spec/services/bulk_imports/relation_export_service_spec.rb116
-rw-r--r--spec/services/bulk_update_integration_service_spec.rb2
-rw-r--r--spec/services/chat_names/find_user_service_spec.rb8
-rw-r--r--spec/services/ci/change_variable_service_spec.rb1
-rw-r--r--spec/services/ci/change_variables_service_spec.rb1
-rw-r--r--spec/services/ci/create_downstream_pipeline_service_spec.rb45
-rw-r--r--spec/services/ci/create_pipeline_service/cross_project_pipeline_spec.rb1
-rw-r--r--spec/services/ci/create_pipeline_service/custom_config_content_spec.rb1
-rw-r--r--spec/services/ci/create_pipeline_service/dry_run_spec.rb1
-rw-r--r--spec/services/ci/create_pipeline_service/environment_spec.rb1
-rw-r--r--spec/services/ci/create_pipeline_service/parameter_content_spec.rb1
-rw-r--r--spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb1
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb11
-rw-r--r--spec/services/ci/create_web_ide_terminal_service_spec.rb8
-rw-r--r--spec/services/ci/delete_unit_tests_service_spec.rb52
-rw-r--r--spec/services/ci/destroy_pipeline_service_spec.rb12
-rw-r--r--spec/services/ci/expire_pipeline_cache_service_spec.rb8
-rw-r--r--spec/services/ci/external_pull_requests/create_pipeline_service_spec.rb1
-rw-r--r--spec/services/ci/find_exposed_artifacts_service_spec.rb1
-rw-r--r--spec/services/ci/generate_codequality_mr_diff_report_service_spec.rb4
-rw-r--r--spec/services/ci/job_artifacts/create_service_spec.rb1
-rw-r--r--spec/services/ci/job_artifacts/destroy_associations_service_spec.rb54
-rw-r--r--spec/services/ci/job_artifacts/destroy_batch_service_spec.rb39
-rw-r--r--spec/services/ci/parse_dotenv_artifact_service_spec.rb3
-rw-r--r--spec/services/ci/pipeline_artifacts/create_code_quality_mr_diff_report_service_spec.rb88
-rw-r--r--spec/services/ci/pipeline_artifacts/destroy_all_expired_service_spec.rb20
-rw-r--r--spec/services/ci/pipeline_bridge_status_service_spec.rb1
-rw-r--r--spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb4
-rw-r--r--spec/services/ci/pipeline_processing/test_cases/dag_test_manual_post_test_needs_deploy_is_stage.yml50
-rw-r--r--spec/services/ci/pipeline_trigger_service_spec.rb43
-rw-r--r--spec/services/ci/process_pipeline_service_spec.rb13
-rw-r--r--spec/services/ci/prometheus_metrics/observe_histograms_service_spec.rb27
-rw-r--r--spec/services/ci/register_job_service_spec.rb117
-rw-r--r--spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb1
-rw-r--r--spec/services/ci/retry_build_service_spec.rb1
-rw-r--r--spec/services/ci/stop_environments_service_spec.rb1
-rw-r--r--spec/services/clusters/applications/prometheus_update_service_spec.rb119
-rw-r--r--spec/services/clusters/applications/schedule_update_service_spec.rb26
-rw-r--r--spec/services/clusters/integrations/create_service_spec.rb103
-rw-r--r--spec/services/clusters/management/create_project_service_spec.rb126
-rw-r--r--spec/services/container_expiration_policies/cleanup_service_spec.rb250
-rw-r--r--spec/services/deployments/create_service_spec.rb6
-rw-r--r--spec/services/deployments/update_environment_service_spec.rb3
-rw-r--r--spec/services/discussions/capture_diff_note_positions_service_spec.rb2
-rw-r--r--spec/services/draft_notes/publish_service_spec.rb2
-rw-r--r--spec/services/feature_flags/create_service_spec.rb1
-rw-r--r--spec/services/feature_flags/destroy_service_spec.rb1
-rw-r--r--spec/services/feature_flags/disable_service_spec.rb1
-rw-r--r--spec/services/feature_flags/enable_service_spec.rb1
-rw-r--r--spec/services/feature_flags/update_service_spec.rb1
-rw-r--r--spec/services/git/branch_hooks_service_spec.rb28
-rw-r--r--spec/services/git/wiki_push_service_spec.rb8
-rw-r--r--spec/services/groups/autocomplete_service_spec.rb119
-rw-r--r--spec/services/groups/create_service_spec.rb18
-rw-r--r--spec/services/groups/open_issues_count_service_spec.rb11
-rw-r--r--spec/services/groups/transfer_service_spec.rb9
-rw-r--r--spec/services/import/gitlab_projects/create_project_from_remote_file_service_spec.rb182
-rw-r--r--spec/services/import/gitlab_projects/create_project_from_uploaded_file_service_spec.rb71
-rw-r--r--spec/services/issuable/bulk_update_service_spec.rb30
-rw-r--r--spec/services/issuable/common_system_notes_service_spec.rb4
-rw-r--r--spec/services/issuable/destroy_label_links_service_spec.rb19
-rw-r--r--spec/services/issuable/destroy_service_spec.rb10
-rw-r--r--spec/services/issue_rebalancing_service_spec.rb71
-rw-r--r--spec/services/issues/after_create_service_spec.rb2
-rw-r--r--spec/services/issues/build_service_spec.rb14
-rw-r--r--spec/services/issues/clone_service_spec.rb2
-rw-r--r--spec/services/issues/close_service_spec.rb117
-rw-r--r--spec/services/issues/create_service_spec.rb60
-rw-r--r--spec/services/issues/duplicate_service_spec.rb2
-rw-r--r--spec/services/issues/move_service_spec.rb2
-rw-r--r--spec/services/issues/referenced_merge_requests_service_spec.rb4
-rw-r--r--spec/services/issues/related_branches_service_spec.rb4
-rw-r--r--spec/services/issues/reopen_service_spec.rb14
-rw-r--r--spec/services/issues/reorder_service_spec.rb4
-rw-r--r--spec/services/issues/resolve_discussions_spec.rb38
-rw-r--r--spec/services/issues/update_service_spec.rb70
-rw-r--r--spec/services/issues/zoom_link_service_spec.rb2
-rw-r--r--spec/services/labels/available_labels_service_spec.rb9
-rw-r--r--spec/services/labels/find_or_create_service_spec.rb29
-rw-r--r--spec/services/lfs/push_service_spec.rb2
-rw-r--r--spec/services/merge_requests/add_context_service_spec.rb6
-rw-r--r--spec/services/merge_requests/add_spent_time_service_spec.rb63
-rw-r--r--spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb2
-rw-r--r--spec/services/merge_requests/after_create_service_spec.rb4
-rw-r--r--spec/services/merge_requests/approval_service_spec.rb2
-rw-r--r--spec/services/merge_requests/assign_issues_service_spec.rb22
-rw-r--r--spec/services/merge_requests/base_service_spec.rb2
-rw-r--r--spec/services/merge_requests/build_service_spec.rb4
-rw-r--r--spec/services/merge_requests/cleanup_refs_service_spec.rb2
-rw-r--r--spec/services/merge_requests/close_service_spec.rb14
-rw-r--r--spec/services/merge_requests/create_from_issue_service_spec.rb14
-rw-r--r--spec/services/merge_requests/create_pipeline_service_spec.rb2
-rw-r--r--spec/services/merge_requests/create_service_spec.rb26
-rw-r--r--spec/services/merge_requests/ff_merge_service_spec.rb4
-rw-r--r--spec/services/merge_requests/get_urls_service_spec.rb4
-rw-r--r--spec/services/merge_requests/handle_assignees_change_service_spec.rb14
-rw-r--r--spec/services/merge_requests/link_lfs_objects_service_spec.rb2
-rw-r--r--spec/services/merge_requests/mark_reviewer_reviewed_service_spec.rb6
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb46
-rw-r--r--spec/services/merge_requests/merge_to_ref_service_spec.rb8
-rw-r--r--spec/services/merge_requests/mergeability_check_service_spec.rb12
-rw-r--r--spec/services/merge_requests/post_merge_service_spec.rb3
-rw-r--r--spec/services/merge_requests/push_options_handler_service_spec.rb2
-rw-r--r--spec/services/merge_requests/pushed_branches_service_spec.rb2
-rw-r--r--spec/services/merge_requests/rebase_service_spec.rb2
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb62
-rw-r--r--spec/services/merge_requests/reload_merge_head_diff_service_spec.rb2
-rw-r--r--spec/services/merge_requests/remove_approval_service_spec.rb2
-rw-r--r--spec/services/merge_requests/reopen_service_spec.rb12
-rw-r--r--spec/services/merge_requests/request_review_service_spec.rb4
-rw-r--r--spec/services/merge_requests/resolve_todos_service_spec.rb12
-rw-r--r--spec/services/merge_requests/resolved_discussion_notification_service_spec.rb2
-rw-r--r--spec/services/merge_requests/retarget_chain_service_spec.rb2
-rw-r--r--spec/services/merge_requests/squash_service_spec.rb6
-rw-r--r--spec/services/merge_requests/update_assignees_service_spec.rb24
-rw-r--r--spec/services/merge_requests/update_service_spec.rb83
-rw-r--r--spec/services/namespaces/package_settings/update_service_spec.rb11
-rw-r--r--spec/services/notes/build_service_spec.rb2
-rw-r--r--spec/services/notes/copy_service_spec.rb2
-rw-r--r--spec/services/notes/create_service_spec.rb2
-rw-r--r--spec/services/notes/quick_actions_service_spec.rb32
-rw-r--r--spec/services/notification_service_spec.rb14
-rw-r--r--spec/services/packages/debian/generate_distribution_key_service_spec.rb35
-rw-r--r--spec/services/packages/debian/generate_distribution_service_spec.rb182
-rw-r--r--spec/services/packages/debian/process_changes_service_spec.rb1
-rw-r--r--spec/services/packages/generic/create_package_file_service_spec.rb42
-rw-r--r--spec/services/packages/maven/find_or_create_package_service_spec.rb10
-rw-r--r--spec/services/packages/nuget/search_service_spec.rb12
-rw-r--r--spec/services/packages/rubygems/dependency_resolver_service_spec.rb2
-rw-r--r--spec/services/packages/rubygems/process_gem_service_spec.rb5
-rw-r--r--spec/services/packages/terraform_module/create_package_service_spec.rb57
-rw-r--r--spec/services/post_receive_service_spec.rb2
-rw-r--r--spec/services/projects/alerting/notify_service_spec.rb258
-rw-r--r--spec/services/projects/create_service_spec.rb45
-rw-r--r--spec/services/projects/destroy_service_spec.rb48
-rw-r--r--spec/services/projects/housekeeping_service_spec.rb20
-rw-r--r--spec/services/projects/prometheus/alerts/notify_service_spec.rb119
-rw-r--r--spec/services/projects/transfer_service_spec.rb2
-rw-r--r--spec/services/projects/unlink_fork_service_spec.rb12
-rw-r--r--spec/services/projects/update_repository_storage_service_spec.rb2
-rw-r--r--spec/services/projects/update_statistics_service_spec.rb46
-rw-r--r--spec/services/quick_actions/interpret_service_spec.rb28
-rw-r--r--spec/services/security/ci_configuration/sast_create_service_spec.rb75
-rw-r--r--spec/services/security/ci_configuration/secret_detection_create_service_spec.rb19
-rw-r--r--spec/services/snippets/update_repository_storage_service_spec.rb2
-rw-r--r--spec/services/spam/spam_action_service_spec.rb54
-rw-r--r--spec/services/spam/spam_verdict_service_spec.rb189
-rw-r--r--spec/services/submit_usage_ping_service_spec.rb2
-rw-r--r--spec/services/suggestions/apply_service_spec.rb75
-rw-r--r--spec/services/system_hooks_service_spec.rb159
-rw-r--r--spec/services/todo_service_spec.rb15
-rw-r--r--spec/services/users/ban_service_spec.rb50
-rw-r--r--spec/services/users/build_service_spec.rb244
-rw-r--r--spec/services/users/registrations_build_service_spec.rb73
-rw-r--r--spec/services/users/update_assigned_open_issue_count_service_spec.rb49
-rw-r--r--spec/services/users/update_todo_count_cache_service_spec.rb32
-rw-r--r--spec/services/users/upsert_credit_card_validation_service_spec.rb84
-rw-r--r--spec/services/web_hook_service_spec.rb244
-rw-r--r--spec/services/web_hooks/destroy_service_spec.rb8
170 files changed, 3804 insertions, 1799 deletions
diff --git a/spec/services/admin/propagate_service_template_spec.rb b/spec/services/admin/propagate_service_template_spec.rb
index d95d31ceaea..406da790a66 100644
--- a/spec/services/admin/propagate_service_template_spec.rb
+++ b/spec/services/admin/propagate_service_template_spec.rb
@@ -29,7 +29,7 @@ RSpec.describe Admin::PropagateServiceTemplate do
context 'with a project that has another service' do
before do
- BambooService.create!(
+ Integrations::Bamboo.create!(
active: true,
project: project,
properties: {
@@ -50,10 +50,10 @@ RSpec.describe Admin::PropagateServiceTemplate do
end
it 'does not create the service if it exists already' do
- Service.build_from_integration(service_template, project_id: project.id).save!
+ Integration.build_from_integration(service_template, project_id: project.id).save!
expect { described_class.propagate(service_template) }
- .not_to change { Service.count }
+ .not_to change { Integration.count }
end
end
end
diff --git a/spec/services/alert_management/process_prometheus_alert_service_spec.rb b/spec/services/alert_management/process_prometheus_alert_service_spec.rb
index 9bd71ea6f64..86a6cdee52d 100644
--- a/spec/services/alert_management/process_prometheus_alert_service_spec.rb
+++ b/spec/services/alert_management/process_prometheus_alert_service_spec.rb
@@ -5,38 +5,27 @@ require 'spec_helper'
RSpec.describe AlertManagement::ProcessPrometheusAlertService do
let_it_be(:project, reload: true) { create(:project, :repository) }
- before do
- allow(ProjectServiceWorker).to receive(:perform_async)
- end
+ let(:service) { described_class.new(project, payload) }
describe '#execute' do
- let(:service) { described_class.new(project, payload) }
- let(:source) { 'Prometheus' }
- let(:auto_close_incident) { true }
- let(:create_issue) { true }
- let(:send_email) { true }
- let(:incident_management_setting) do
- double(
- auto_close_incident?: auto_close_incident,
- create_issue?: create_issue,
- send_email?: send_email
- )
- end
+ include_context 'incident management settings enabled'
+
+ subject(:execute) { service.execute }
before do
- allow(service)
- .to receive(:incident_management_setting)
- .and_return(incident_management_setting)
+ stub_licensed_features(oncall_schedules: false, generic_alert_fingerprinting: false)
end
- subject(:execute) { service.execute }
-
context 'when alert payload is valid' do
- let(:parsed_payload) { Gitlab::AlertManagement::Payload.parse(project, payload, monitoring_tool: source) }
- let(:fingerprint) { parsed_payload.gitlab_fingerprint }
+ let_it_be(:starts_at) { '2020-04-27T10:10:22.265949279Z' }
+ let_it_be(:title) { 'Alert title' }
+ let_it_be(:fingerprint) { [starts_at, title, 'vector(1)'].join('/') }
+ let_it_be(:source) { 'Prometheus' }
+
+ let(:prometheus_status) { 'firing' }
let(:payload) do
{
- 'status' => status,
+ 'status' => prometheus_status,
'labels' => {
'alertname' => 'GitalyFileServerDown',
'channel' => 'gitaly',
@@ -46,196 +35,32 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do
'annotations' => {
'description' => 'Alert description',
'runbook' => 'troubleshooting/gitaly-down.md',
- 'title' => 'Alert title'
+ 'title' => title
},
- 'startsAt' => '2020-04-27T10:10:22.265949279Z',
+ 'startsAt' => starts_at,
'endsAt' => '2020-04-27T10:20:22.265949279Z',
- 'generatorURL' => 'http://8d467bd4607a:9090/graph?g0.expr=vector%281%29&g0.tab=1',
- 'fingerprint' => 'b6ac4d42057c43c1'
+ 'generatorURL' => 'http://8d467bd4607a:9090/graph?g0.expr=vector%281%29&g0.tab=1'
}
end
- let(:status) { 'firing' }
-
- context 'when Prometheus alert status is firing' do
- context 'when alert with the same fingerprint already exists' do
- let!(:alert) { create(:alert_management_alert, project: project, fingerprint: fingerprint) }
-
- it_behaves_like 'adds an alert management alert event'
- it_behaves_like 'processes incident issues'
- it_behaves_like 'Alert Notification Service sends notification email'
-
- context 'existing alert is resolved' do
- let!(:alert) { create(:alert_management_alert, :resolved, project: project, fingerprint: fingerprint) }
-
- it_behaves_like 'creates an alert management alert'
- it_behaves_like 'Alert Notification Service sends notification email'
- end
-
- context 'existing alert is ignored' do
- let!(:alert) { create(:alert_management_alert, :ignored, project: project, fingerprint: fingerprint) }
-
- it_behaves_like 'adds an alert management alert event'
- it_behaves_like 'Alert Notification Service sends no notifications'
- end
-
- context 'existing alert is acknowledged' do
- let!(:alert) { create(:alert_management_alert, :acknowledged, project: project, fingerprint: fingerprint) }
-
- it_behaves_like 'adds an alert management alert event'
- it_behaves_like 'Alert Notification Service sends no notifications'
- end
-
- context 'two existing alerts, one resolved one open' do
- let!(:resolved_alert) { create(:alert_management_alert, :resolved, project: project, fingerprint: fingerprint) }
- let!(:alert) { create(:alert_management_alert, project: project, fingerprint: fingerprint) }
-
- it_behaves_like 'adds an alert management alert event'
- it_behaves_like 'Alert Notification Service sends notification email'
- end
-
- context 'when auto-creation of issues is disabled' do
- let(:create_issue) { false }
-
- it_behaves_like 'does not process incident issues'
- end
-
- context 'when emails are disabled' do
- let(:send_email) { false }
-
- it_behaves_like 'Alert Notification Service sends no notifications'
- end
- end
-
- context 'when alert does not exist' do
- context 'when alert can be created' do
- it_behaves_like 'creates an alert management alert'
- it_behaves_like 'Alert Notification Service sends notification email'
- it_behaves_like 'processes incident issues'
-
- it_behaves_like 'creates single system note based on the source of the alert'
-
- context 'when auto-alert creation is disabled' do
- let(:create_issue) { false }
-
- it_behaves_like 'does not process incident issues'
- end
-
- context 'when emails are disabled' do
- let(:send_email) { false }
-
- it_behaves_like 'Alert Notification Service sends no notifications'
- end
- end
-
- context 'when alert cannot be created' do
- let(:errors) { double(messages: { hosts: ['hosts array is over 255 chars'] })}
-
- before do
- allow(service).to receive(:alert).and_call_original
- allow(service).to receive_message_chain(:alert, :save).and_return(false)
- allow(service).to receive_message_chain(:alert, :errors).and_return(errors)
- end
-
- it_behaves_like 'Alert Notification Service sends no notifications', http_status: :bad_request
- it_behaves_like 'does not process incident issues due to error', http_status: :bad_request
-
- it 'writes a warning to the log' do
- expect(Gitlab::AppLogger).to receive(:warn).with(
- message: 'Unable to create AlertManagement::Alert from Prometheus',
- project_id: project.id,
- alert_errors: { hosts: ['hosts array is over 255 chars'] }
- )
-
- execute
- end
- end
-
- it { is_expected.to be_success }
- end
- end
-
- context 'when Prometheus alert status is resolved' do
- let(:status) { 'resolved' }
- let!(:alert) { create(:alert_management_alert, project: project, fingerprint: fingerprint, monitoring_tool: source) }
-
- context 'when auto_resolve_incident set to true' do
- context 'when status can be changed' do
- it_behaves_like 'Alert Notification Service sends notification email'
- it_behaves_like 'does not process incident issues'
-
- it 'resolves an existing alert without error' do
- expect(Gitlab::AppLogger).not_to receive(:warn)
- expect { execute }.to change { alert.reload.resolved? }.to(true)
- end
-
- it_behaves_like 'creates status-change system note for an auto-resolved alert'
-
- context 'existing issue' do
- let!(:alert) { create(:alert_management_alert, :with_issue, project: project, fingerprint: fingerprint) }
-
- it 'closes the issue' do
- issue = alert.issue
-
- expect { execute }
- .to change { issue.reload.state }
- .from('opened')
- .to('closed')
- end
-
- it 'creates a resource state event' do
- expect { execute }.to change(ResourceStateEvent, :count).by(1)
- end
- end
- end
-
- context 'when status change did not succeed' do
- before do
- allow(AlertManagement::Alert).to receive(:for_fingerprint).and_return([alert])
- allow(alert).to receive(:resolve).and_return(false)
- end
-
- it 'writes a warning to the log' do
- expect(Gitlab::AppLogger).to receive(:warn).with(
- message: 'Unable to update AlertManagement::Alert status to resolved',
- project_id: project.id,
- alert_id: alert.id
- )
-
- execute
- end
-
- it_behaves_like 'Alert Notification Service sends notification email'
- end
-
- it { is_expected.to be_success }
- end
+ it_behaves_like 'processes new firing alert'
- context 'when auto_resolve_incident set to false' do
- let(:auto_close_incident) { false }
+ context 'with resolving payload' do
+ let(:prometheus_status) { 'resolved' }
- it 'does not resolve an existing alert' do
- expect { execute }.not_to change { alert.reload.resolved? }
- end
-
- it_behaves_like 'creates single system note based on the source of the alert'
- end
-
- context 'when emails are disabled' do
- let(:send_email) { false }
-
- it_behaves_like 'Alert Notification Service sends no notifications'
- end
+ it_behaves_like 'processes recovery alert'
end
context 'environment given' do
let(:environment) { create(:environment, project: project) }
+ let(:alert) { project.alert_management_alerts.last }
- it 'sets the environment' do
+ before do
payload['labels']['gitlab_environment_name'] = environment.name
- execute
+ end
- alert = project.alert_management_alerts.last
+ it 'sets the environment' do
+ execute
expect(alert.environment).to eq(environment)
end
@@ -243,12 +68,14 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do
context 'prometheus alert given' do
let(:prometheus_alert) { create(:prometheus_alert, project: project) }
+ let(:alert) { project.alert_management_alerts.last }
- it 'sets the prometheus alert and environment' do
+ before do
payload['labels']['gitlab_alert_id'] = prometheus_alert.prometheus_metric_id
- execute
+ end
- alert = project.alert_management_alerts.last
+ it 'sets the prometheus alert and environment' do
+ execute
expect(alert.prometheus_alert).to eq(prometheus_alert)
expect(alert.environment).to eq(prometheus_alert.environment)
@@ -259,10 +86,7 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do
context 'when alert payload is invalid' do
let(:payload) { {} }
- it 'responds with bad_request' do
- expect(execute).to be_error
- expect(execute.http_status).to eq(:bad_request)
- end
+ it_behaves_like 'alerts service responds with an error and takes no actions', :bad_request
end
end
end
diff --git a/spec/services/analytics/cycle_analytics/stages/list_service_spec.rb b/spec/services/analytics/cycle_analytics/stages/list_service_spec.rb
new file mode 100644
index 00000000000..24f0123ed3b
--- /dev/null
+++ b/spec/services/analytics/cycle_analytics/stages/list_service_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Analytics::CycleAnalytics::Stages::ListService do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+
+ let(:value_stream) { Analytics::CycleAnalytics::ProjectValueStream.build_default_value_stream(project) }
+ let(:stages) { subject.payload[:stages] }
+
+ subject { described_class.new(parent: project, current_user: user).execute }
+
+ before_all do
+ project.add_reporter(user)
+ end
+
+ it 'returns only the default stages' do
+ expect(stages.size).to eq(Gitlab::Analytics::CycleAnalytics::DefaultStages.all.size)
+ end
+
+ it 'provides the default stages as non-persisted objects' do
+ expect(stages.map(&:id)).to all(be_nil)
+ end
+end
diff --git a/spec/services/application_settings/update_service_spec.rb b/spec/services/application_settings/update_service_spec.rb
index 258b3d25aee..56c1284927d 100644
--- a/spec/services/application_settings/update_service_spec.rb
+++ b/spec/services/application_settings/update_service_spec.rb
@@ -336,6 +336,32 @@ RSpec.describe ApplicationSettings::UpdateService do
end
end
+ context 'when package registry rate limits are passed' do
+ let(:params) do
+ {
+ throttle_unauthenticated_packages_api_enabled: 1,
+ throttle_unauthenticated_packages_api_period_in_seconds: 500,
+ throttle_unauthenticated_packages_api_requests_per_period: 20,
+ throttle_authenticated_packages_api_enabled: 1,
+ throttle_authenticated_packages_api_period_in_seconds: 600,
+ throttle_authenticated_packages_api_requests_per_period: 10
+ }
+ end
+
+ it 'updates package registry throttle settings' do
+ subject.execute
+
+ application_settings.reload
+
+ expect(application_settings.throttle_unauthenticated_packages_api_enabled).to be_truthy
+ expect(application_settings.throttle_unauthenticated_packages_api_period_in_seconds).to eq(500)
+ expect(application_settings.throttle_unauthenticated_packages_api_requests_per_period).to eq(20)
+ expect(application_settings.throttle_authenticated_packages_api_enabled).to be_truthy
+ expect(application_settings.throttle_authenticated_packages_api_period_in_seconds).to eq(600)
+ expect(application_settings.throttle_authenticated_packages_api_requests_per_period).to eq(10)
+ end
+ end
+
context 'when issues_create_limit is passed' do
let(:params) do
{
diff --git a/spec/services/auto_merge/base_service_spec.rb b/spec/services/auto_merge/base_service_spec.rb
index 1d33dc15838..3f535b83788 100644
--- a/spec/services/auto_merge/base_service_spec.rb
+++ b/spec/services/auto_merge/base_service_spec.rb
@@ -84,7 +84,7 @@ RSpec.describe AutoMerge::BaseService do
context 'when failed to save merge request' do
before do
- allow(merge_request).to receive(:save!) { raise ActiveRecord::RecordInvalid.new }
+ allow(merge_request).to receive(:save!) { raise ActiveRecord::RecordInvalid }
end
it 'does not yield block' do
@@ -195,7 +195,7 @@ RSpec.describe AutoMerge::BaseService do
context 'when failed to save' do
before do
- allow(merge_request).to receive(:save!) { raise ActiveRecord::RecordInvalid.new }
+ allow(merge_request).to receive(:save!) { raise ActiveRecord::RecordInvalid }
end
it 'does not yield block' do
@@ -213,7 +213,7 @@ RSpec.describe AutoMerge::BaseService do
context 'when failed to save merge request' do
before do
- allow(merge_request).to receive(:save!) { raise ActiveRecord::RecordInvalid.new }
+ allow(merge_request).to receive(:save!) { raise ActiveRecord::RecordInvalid }
end
it 'returns error status' do
@@ -260,7 +260,7 @@ RSpec.describe AutoMerge::BaseService do
context 'when failed to save' do
before do
- allow(merge_request).to receive(:save!) { raise ActiveRecord::RecordInvalid.new }
+ allow(merge_request).to receive(:save!) { raise ActiveRecord::RecordInvalid }
end
it 'returns error status' do
diff --git a/spec/services/boards/lists/destroy_service_spec.rb b/spec/services/boards/lists/destroy_service_spec.rb
index 4c512b96065..d5358bcc1e1 100644
--- a/spec/services/boards/lists/destroy_service_spec.rb
+++ b/spec/services/boards/lists/destroy_service_spec.rb
@@ -3,11 +3,20 @@
require 'spec_helper'
RSpec.describe Boards::Lists::DestroyService do
+ let_it_be(:user) { create(:user) }
+
+ let(:list_type) { :list }
+
describe '#execute' do
context 'when board parent is a project' do
- let(:project) { create(:project) }
- let(:board) { create(:board, project: project) }
- let(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:board) { create(:board, project: project) }
+ let_it_be(:list) { create(:list, board: board) }
+ let_it_be(:closed_list) { board.lists.closed.first }
+
+ let(:params) do
+ { board: board }
+ end
let(:parent) { project }
@@ -15,9 +24,14 @@ RSpec.describe Boards::Lists::DestroyService do
end
context 'when board parent is a group' do
- let(:group) { create(:group) }
- let(:board) { create(:board, group: group) }
- let(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:board) { create(:board, group: group) }
+ let_it_be(:list) { create(:list, board: board) }
+ let_it_be(:closed_list) { board.lists.closed.first }
+
+ let(:params) do
+ { board: board }
+ end
let(:parent) { group }
diff --git a/spec/services/boards/lists/update_service_spec.rb b/spec/services/boards/lists/update_service_spec.rb
index 10fed9b7aac..21216e1b945 100644
--- a/spec/services/boards/lists/update_service_spec.rb
+++ b/spec/services/boards/lists/update_service_spec.rb
@@ -3,8 +3,10 @@
require 'spec_helper'
RSpec.describe Boards::Lists::UpdateService do
- let(:user) { create(:user) }
+ let_it_be(:user) { create(:user) }
+
let!(:list) { create(:list, board: board, position: 0) }
+ let!(:list2) { create(:list, board: board, position: 1) }
describe '#execute' do
let(:service) { described_class.new(board.resource_parent, user, params) }
diff --git a/spec/services/boards/visits/create_service_spec.rb b/spec/services/boards/visits/create_service_spec.rb
index a9a8754825b..8910345d170 100644
--- a/spec/services/boards/visits/create_service_spec.rb
+++ b/spec/services/boards/visits/create_service_spec.rb
@@ -7,47 +7,20 @@ RSpec.describe Boards::Visits::CreateService do
let(:user) { create(:user) }
context 'when a project board' do
- let(:project) { create(:project) }
- let(:project_board) { create(:board, project: project) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:board) { create(:board, project: project) }
- subject(:service) { described_class.new(project_board.resource_parent, user) }
+ let_it_be(:model) { BoardProjectRecentVisit }
- it 'returns nil when there is no user' do
- service.current_user = nil
-
- expect(service.execute(project_board)).to eq nil
- end
-
- it 'returns nil when database is read only' do
- allow(Gitlab::Database).to receive(:read_only?) { true }
-
- expect(service.execute(project_board)).to eq nil
- end
-
- it 'records the visit' do
- expect(BoardProjectRecentVisit).to receive(:visited!).once
-
- service.execute(project_board)
- end
+ it_behaves_like 'boards recent visit create service'
end
context 'when a group board' do
- let(:group) { create(:group) }
- let(:group_board) { create(:board, group: group) }
-
- subject(:service) { described_class.new(group_board.resource_parent, user) }
-
- it 'returns nil when there is no user' do
- service.current_user = nil
-
- expect(service.execute(group_board)).to eq nil
- end
-
- it 'records the visit' do
- expect(BoardGroupRecentVisit).to receive(:visited!).once
+ let_it_be(:group) { create(:group) }
+ let_it_be(:board) { create(:board, group: group) }
+ let_it_be(:model) { BoardGroupRecentVisit }
- service.execute(group_board)
- end
+ it_behaves_like 'boards recent visit create service'
end
end
end
diff --git a/spec/services/branches/delete_service_spec.rb b/spec/services/branches/delete_service_spec.rb
index 291431c1723..727cadc5a50 100644
--- a/spec/services/branches/delete_service_spec.rb
+++ b/spec/services/branches/delete_service_spec.rb
@@ -41,7 +41,7 @@ RSpec.describe Branches::DeleteService do
context 'when Gitlab::Git::CommandError is raised' do
before do
allow(repository).to receive(:rm_branch) do
- raise Gitlab::Git::CommandError.new('Could not update patch')
+ raise Gitlab::Git::CommandError, 'Could not update patch'
end
end
diff --git a/spec/services/bulk_create_integration_service_spec.rb b/spec/services/bulk_create_integration_service_spec.rb
index 479309572a5..8369eb48088 100644
--- a/spec/services/bulk_create_integration_service_spec.rb
+++ b/spec/services/bulk_create_integration_service_spec.rb
@@ -59,7 +59,7 @@ RSpec.describe BulkCreateIntegrationService do
context 'with a group association' do
let!(:group) { create(:group) }
- let(:created_integration) { Service.find_by(group: group) }
+ let(:created_integration) { Integration.find_by(group: group) }
let(:batch) { Group.where(id: group.id) }
let(:association) { 'group' }
@@ -86,7 +86,7 @@ RSpec.describe BulkCreateIntegrationService do
context 'with a group association' do
let!(:subgroup) { create(:group, parent: group) }
let(:integration) { create(:jira_service, group: group, project: nil, inherit_from_id: instance_integration.id) }
- let(:created_integration) { Service.find_by(group: subgroup) }
+ let(:created_integration) { Integration.find_by(group: subgroup) }
let(:batch) { Group.where(id: subgroup.id) }
let(:association) { 'group' }
let(:inherit_from_id) { instance_integration.id }
diff --git a/spec/services/bulk_imports/export_service_spec.rb b/spec/services/bulk_imports/export_service_spec.rb
new file mode 100644
index 00000000000..2414f7c5ca7
--- /dev/null
+++ b/spec/services/bulk_imports/export_service_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::ExportService do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:user) { create(:user) }
+
+ before do
+ group.add_owner(user)
+ end
+
+ subject { described_class.new(portable: group, user: user) }
+
+ describe '#execute' do
+ it 'schedules RelationExportWorker for each top level relation' do
+ expect(subject).to receive(:execute).and_return(ServiceResponse.success).and_call_original
+ top_level_relations = BulkImports::FileTransfer.config_for(group).portable_relations
+
+ top_level_relations.each do |relation|
+ expect(BulkImports::RelationExportWorker)
+ .to receive(:perform_async)
+ .with(user.id, group.id, group.class.name, relation)
+ end
+
+ subject.execute
+ end
+
+ context 'when exception occurs' do
+ it 'does not schedule RelationExportWorker' do
+ service = described_class.new(portable: nil, user: user)
+
+ expect(service)
+ .to receive(:execute)
+ .and_return(ServiceResponse.error(message: 'Gitlab::ImportExport::Error', http_status: :unprocessible_entity))
+ .and_call_original
+ expect(BulkImports::RelationExportWorker).not_to receive(:perform_async)
+
+ service.execute
+ end
+ end
+ end
+end
diff --git a/spec/services/bulk_imports/relation_export_service_spec.rb b/spec/services/bulk_imports/relation_export_service_spec.rb
new file mode 100644
index 00000000000..bf286998df2
--- /dev/null
+++ b/spec/services/bulk_imports/relation_export_service_spec.rb
@@ -0,0 +1,116 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::RelationExportService do
+ let_it_be(:jid) { 'jid' }
+ let_it_be(:relation) { 'labels' }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:label) { create(:group_label, group: group) }
+ let_it_be(:export_path) { "#{Dir.tmpdir}/relation_export_service_spec/tree" }
+ let_it_be_with_reload(:export) { create(:bulk_import_export, group: group, relation: relation) }
+
+ before do
+ group.add_owner(user)
+
+ allow(export).to receive(:export_path).and_return(export_path)
+ end
+
+ after :all do
+ FileUtils.rm_rf(export_path)
+ end
+
+ subject { described_class.new(user, group, relation, jid) }
+
+ describe '#execute' do
+ it 'exports specified relation and marks export as finished' do
+ subject.execute
+
+ expect(export.reload.upload.export_file).to be_present
+ expect(export.finished?).to eq(true)
+ end
+
+ it 'removes temp export files' do
+ subject.execute
+
+ expect(Dir.exist?(export_path)).to eq(false)
+ end
+
+ it 'exports specified relation and marks export as finished' do
+ subject.execute
+
+ expect(export.upload.export_file).to be_present
+ end
+
+ context 'when export record does not exist' do
+ let(:another_group) { create(:group) }
+
+ subject { described_class.new(user, another_group, relation, jid) }
+
+ it 'creates export record' do
+ another_group.add_owner(user)
+
+ expect { subject.execute }
+ .to change { another_group.bulk_import_exports.count }
+ .from(0)
+ .to(1)
+ end
+ end
+
+ context 'when there is existing export present' do
+ let(:upload) { create(:bulk_import_export_upload, export: export) }
+
+ it 'removes existing export before exporting' do
+ upload.update!(export_file: fixture_file_upload('spec/fixtures/bulk_imports/labels.ndjson.gz'))
+
+ expect_any_instance_of(BulkImports::ExportUpload) do |upload|
+ expect(upload).to receive(:remove_export_file!)
+ end
+
+ subject.execute
+ end
+ end
+
+ context 'when exception occurs during export' do
+ shared_examples 'tracks exception' do |exception_class|
+ it 'tracks exception' do
+ expect(Gitlab::ErrorTracking)
+ .to receive(:track_exception)
+ .with(exception_class, portable_id: group.id, portable_type: group.class.name)
+ .and_call_original
+
+ subject.execute
+ end
+ end
+
+ before do
+ allow_next_instance_of(BulkImports::ExportUpload) do |upload|
+ allow(upload).to receive(:save!).and_raise(StandardError)
+ end
+ end
+
+ it 'marks export as failed' do
+ subject.execute
+
+ expect(export.reload.failed?).to eq(true)
+ end
+
+ include_examples 'tracks exception', StandardError
+
+ context 'when passed relation is not supported' do
+ let(:relation) { 'unsupported' }
+
+ include_examples 'tracks exception', ActiveRecord::RecordInvalid
+ end
+
+ context 'when user is not allowed to perform export' do
+ let(:another_user) { create(:user) }
+
+ subject { described_class.new(another_user, group, relation, jid) }
+
+ include_examples 'tracks exception', Gitlab::ImportExport::Error
+ end
+ end
+ end
+end
diff --git a/spec/services/bulk_update_integration_service_spec.rb b/spec/services/bulk_update_integration_service_spec.rb
index e20bcd44923..cd50a2a5708 100644
--- a/spec/services/bulk_update_integration_service_spec.rb
+++ b/spec/services/bulk_update_integration_service_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe BulkUpdateIntegrationService do
let(:excluded_attributes) { %w[id project_id group_id inherit_from_id instance template created_at updated_at] }
let(:batch) do
- Service.inherited_descendants_from_self_or_ancestors_from(subgroup_integration).where(id: group_integration.id..integration.id)
+ Integration.inherited_descendants_from_self_or_ancestors_from(subgroup_integration).where(id: group_integration.id..integration.id)
end
let_it_be(:group) { create(:group) }
diff --git a/spec/services/chat_names/find_user_service_spec.rb b/spec/services/chat_names/find_user_service_spec.rb
index a29b243ad2c..9bbad09cd0d 100644
--- a/spec/services/chat_names/find_user_service_spec.rb
+++ b/spec/services/chat_names/find_user_service_spec.rb
@@ -4,13 +4,13 @@ require 'spec_helper'
RSpec.describe ChatNames::FindUserService, :clean_gitlab_redis_shared_state do
describe '#execute' do
- let(:service) { create(:service) }
+ let(:integration) { create(:service) }
- subject { described_class.new(service, params).execute }
+ subject { described_class.new(integration, params).execute }
context 'find user mapping' do
let(:user) { create(:user) }
- let!(:chat_name) { create(:chat_name, user: user, service: service) }
+ let!(:chat_name) { create(:chat_name, user: user, integration: integration) }
context 'when existing user is requested' do
let(:params) { { team_id: chat_name.team_id, user_id: chat_name.chat_id } }
@@ -28,7 +28,7 @@ RSpec.describe ChatNames::FindUserService, :clean_gitlab_redis_shared_state do
end
it 'only updates an existing timestamp once within a certain time frame' do
- service = described_class.new(service, params)
+ service = described_class.new(integration, params)
expect(chat_name.last_used_at).to be_nil
diff --git a/spec/services/ci/change_variable_service_spec.rb b/spec/services/ci/change_variable_service_spec.rb
index 7acdd4e834f..f86a87132b1 100644
--- a/spec/services/ci/change_variable_service_spec.rb
+++ b/spec/services/ci/change_variable_service_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Ci::ChangeVariableService do
let(:service) { described_class.new(container: group, current_user: user, params: params) }
let_it_be(:user) { create(:user) }
+
let(:group) { create(:group) }
describe '#execute' do
diff --git a/spec/services/ci/change_variables_service_spec.rb b/spec/services/ci/change_variables_service_spec.rb
index 5f1207eaf58..b710ca78554 100644
--- a/spec/services/ci/change_variables_service_spec.rb
+++ b/spec/services/ci/change_variables_service_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Ci::ChangeVariablesService do
let(:service) { described_class.new(container: group, current_user: user, params: params) }
let_it_be(:user) { create(:user) }
+
let(:group) { spy(:group, variables: []) }
let(:params) { { variables_attributes: [{ key: 'new_variable', value: 'variable_value' }] } }
diff --git a/spec/services/ci/create_downstream_pipeline_service_spec.rb b/spec/services/ci/create_downstream_pipeline_service_spec.rb
index dd10fb017aa..8bab7856375 100644
--- a/spec/services/ci/create_downstream_pipeline_service_spec.rb
+++ b/spec/services/ci/create_downstream_pipeline_service_spec.rb
@@ -3,9 +3,11 @@
require 'spec_helper'
RSpec.describe Ci::CreateDownstreamPipelineService, '#execute' do
+ include Ci::SourcePipelineHelpers
+
let_it_be(:user) { create(:user) }
let(:upstream_project) { create(:project, :repository) }
- let_it_be(:downstream_project) { create(:project, :repository) }
+ let_it_be(:downstream_project, refind: true) { create(:project, :repository) }
let!(:upstream_pipeline) do
create(:ci_pipeline, :running, project: upstream_project)
@@ -394,6 +396,47 @@ RSpec.describe Ci::CreateDownstreamPipelineService, '#execute' do
end
end
+ context 'when relationship between pipelines is cyclical' do
+ before do
+ pipeline_a = create(:ci_pipeline, project: upstream_project)
+ pipeline_b = create(:ci_pipeline, project: downstream_project)
+ pipeline_c = create(:ci_pipeline, project: upstream_project)
+
+ create_source_pipeline(pipeline_a, pipeline_b)
+ create_source_pipeline(pipeline_b, pipeline_c)
+ create_source_pipeline(pipeline_c, upstream_pipeline)
+ end
+
+ it 'does not create a new pipeline' do
+ expect { service.execute(bridge) }
+ .not_to change { Ci::Pipeline.count }
+ end
+
+ it 'changes status of the bridge build' do
+ service.execute(bridge)
+
+ expect(bridge.reload).to be_failed
+ expect(bridge.failure_reason).to eq 'pipeline_loop_detected'
+ end
+
+ context 'when ci_drop_cyclical_triggered_pipelines is not enabled' do
+ before do
+ stub_feature_flags(ci_drop_cyclical_triggered_pipelines: false)
+ end
+
+ it 'creates a new pipeline' do
+ expect { service.execute(bridge) }
+ .to change { Ci::Pipeline.count }
+ end
+
+ it 'expect bridge build not to be failed' do
+ service.execute(bridge)
+
+ expect(bridge.reload).not_to be_failed
+ end
+ end
+ end
+
context 'when downstream pipeline creation errors out' do
let(:stub_config) { false }
diff --git a/spec/services/ci/create_pipeline_service/cross_project_pipeline_spec.rb b/spec/services/ci/create_pipeline_service/cross_project_pipeline_spec.rb
index d4e9946ac46..b3b8e34dd8e 100644
--- a/spec/services/ci/create_pipeline_service/cross_project_pipeline_spec.rb
+++ b/spec/services/ci/create_pipeline_service/cross_project_pipeline_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Ci::CreatePipelineService, '#execute' do
let_it_be(:group) { create(:group, name: 'my-organization') }
+
let(:upstream_project) { create(:project, :repository, name: 'upstream', group: group) }
let(:downstram_project) { create(:project, :repository, name: 'downstream', group: group) }
let(:user) { create(:user) }
diff --git a/spec/services/ci/create_pipeline_service/custom_config_content_spec.rb b/spec/services/ci/create_pipeline_service/custom_config_content_spec.rb
index 6320a16d646..42c3f52541b 100644
--- a/spec/services/ci/create_pipeline_service/custom_config_content_spec.rb
+++ b/spec/services/ci/create_pipeline_service/custom_config_content_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Ci::CreatePipelineService do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { project.owner }
+
let(:ref) { 'refs/heads/master' }
let(:service) { described_class.new(project, user, { ref: ref }) }
diff --git a/spec/services/ci/create_pipeline_service/dry_run_spec.rb b/spec/services/ci/create_pipeline_service/dry_run_spec.rb
index c21a4ef0917..0fb500f5729 100644
--- a/spec/services/ci/create_pipeline_service/dry_run_spec.rb
+++ b/spec/services/ci/create_pipeline_service/dry_run_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Ci::CreatePipelineService do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { project.owner }
+
let(:ref) { 'refs/heads/master' }
let(:service) { described_class.new(project, user, { ref: ref }) }
diff --git a/spec/services/ci/create_pipeline_service/environment_spec.rb b/spec/services/ci/create_pipeline_service/environment_spec.rb
index 0ed63012325..e77591298ad 100644
--- a/spec/services/ci/create_pipeline_service/environment_spec.rb
+++ b/spec/services/ci/create_pipeline_service/environment_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Ci::CreatePipelineService do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:developer) { create(:user) }
+
let(:service) { described_class.new(project, user, ref: 'master') }
let(:user) { developer }
diff --git a/spec/services/ci/create_pipeline_service/parameter_content_spec.rb b/spec/services/ci/create_pipeline_service/parameter_content_spec.rb
index 90b8baa23a7..94500a550c6 100644
--- a/spec/services/ci/create_pipeline_service/parameter_content_spec.rb
+++ b/spec/services/ci/create_pipeline_service/parameter_content_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Ci::CreatePipelineService do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { project.owner }
+
let(:service) { described_class.new(project, user, { ref: 'refs/heads/master' }) }
let(:content) do
<<~EOY
diff --git a/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb b/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb
index 5ea75c2253b..512cf546e6a 100644
--- a/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb
+++ b/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Ci::CreatePipelineService, '#execute' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
+
let(:ref_name) { 'master' }
let(:service) do
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index 98c85234fe7..9fdce1ae926 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Ci::CreatePipelineService do
let_it_be(:project, reload: true) { create(:project, :repository) }
let_it_be(:user, reload: true) { project.owner }
+
let(:ref_name) { 'refs/heads/master' }
before do
@@ -101,14 +102,6 @@ RSpec.describe Ci::CreatePipelineService do
execute_service
end
- describe 'recording a conversion event' do
- it 'schedules a record conversion event worker' do
- expect(Experiments::RecordConversionEventWorker).to receive(:perform_async).with(:ci_syntax_templates_b, user.id)
-
- pipeline
- end
- end
-
context 'when merge requests already exist for this source branch' do
let(:merge_request_1) do
create(:merge_request, source_branch: 'feature', target_branch: "master", source_project: project)
@@ -539,7 +532,7 @@ RSpec.describe Ci::CreatePipelineService do
it 'pull it from Auto-DevOps' do
pipeline = execute_service
expect(pipeline).to be_auto_devops_source
- expect(pipeline.builds.map(&:name)).to match_array(%w[brakeman-sast build code_quality eslint-sast secret_detection_default_branch test])
+ expect(pipeline.builds.map(&:name)).to match_array(%w[brakeman-sast build code_quality eslint-sast secret_detection_default_branch semgrep-sast test])
end
end
diff --git a/spec/services/ci/create_web_ide_terminal_service_spec.rb b/spec/services/ci/create_web_ide_terminal_service_spec.rb
index c1acf8fd60c..0804773442d 100644
--- a/spec/services/ci/create_web_ide_terminal_service_spec.rb
+++ b/spec/services/ci/create_web_ide_terminal_service_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Ci::CreateWebIdeTerminalService do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
+
let(:ref) { 'master' }
describe '#execute' do
@@ -20,6 +21,13 @@ RSpec.describe Ci::CreateWebIdeTerminalService do
expect(subject[:pipeline].stages.count).to eq(1)
expect(subject[:pipeline].builds.count).to eq(1)
end
+
+ it 'calls ensure_project_iid explicitly' do
+ expect_next_instance_of(Ci::Pipeline) do |instance|
+ expect(instance).to receive(:ensure_project_iid!).twice
+ end
+ subject
+ end
end
before do
diff --git a/spec/services/ci/delete_unit_tests_service_spec.rb b/spec/services/ci/delete_unit_tests_service_spec.rb
new file mode 100644
index 00000000000..4c63c513d48
--- /dev/null
+++ b/spec/services/ci/delete_unit_tests_service_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::DeleteUnitTestsService do
+ describe '#execute' do
+ let!(:unit_test_1) { create(:ci_unit_test) }
+ let!(:unit_test_2) { create(:ci_unit_test) }
+ let!(:unit_test_3) { create(:ci_unit_test) }
+ let!(:unit_test_4) { create(:ci_unit_test) }
+ let!(:unit_test_1_recent_failure) { create(:ci_unit_test_failure, unit_test: unit_test_1) }
+ let!(:unit_test_1_old_failure) { create(:ci_unit_test_failure, unit_test: unit_test_1, failed_at: 15.days.ago) }
+ let!(:unit_test_2_old_failure) { create(:ci_unit_test_failure, unit_test: unit_test_2, failed_at: 15.days.ago) }
+ let!(:unit_test_3_old_failure) { create(:ci_unit_test_failure, unit_test: unit_test_3, failed_at: 15.days.ago) }
+ let!(:unit_test_4_old_failure) { create(:ci_unit_test_failure, unit_test: unit_test_4, failed_at: 15.days.ago) }
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", 2)
+
+ described_class.new.execute
+ end
+
+ it 'does not delete unit test failures not older than 14 days' do
+ expect(unit_test_1_recent_failure.reload).to be_persisted
+ end
+
+ it 'deletes unit test failures older than 14 days' do
+ ids = [
+ unit_test_1_old_failure,
+ unit_test_2_old_failure,
+ unit_test_3_old_failure,
+ unit_test_4_old_failure
+ ].map(&:id)
+
+ result = Ci::UnitTestFailure.where(id: ids)
+
+ expect(result).to be_empty
+ end
+
+ it 'deletes unit tests that have no more associated unit test failures' do
+ ids = [
+ unit_test_2,
+ unit_test_3,
+ unit_test_4
+ ].map(&:id)
+
+ result = Ci::UnitTest.where(id: ids)
+
+ expect(result).to be_empty
+ end
+ end
+end
diff --git a/spec/services/ci/destroy_pipeline_service_spec.rb b/spec/services/ci/destroy_pipeline_service_spec.rb
index 6977c99e335..302233cea5a 100644
--- a/spec/services/ci/destroy_pipeline_service_spec.rb
+++ b/spec/services/ci/destroy_pipeline_service_spec.rb
@@ -3,7 +3,8 @@
require 'spec_helper'
RSpec.describe ::Ci::DestroyPipelineService do
- let(:project) { create(:project, :repository) }
+ let_it_be(:project) { create(:project, :repository) }
+
let!(:pipeline) { create(:ci_pipeline, :success, project: project, sha: project.commit.id) }
subject { described_class.new(project, user).execute(pipeline) }
@@ -17,13 +18,16 @@ RSpec.describe ::Ci::DestroyPipelineService do
expect { pipeline.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
- it 'clears the cache', :use_clean_rails_memory_store_caching do
+ it 'clears the cache', :use_clean_rails_redis_caching do
create(:commit_status, :success, pipeline: pipeline, ref: pipeline.ref)
expect(project.pipeline_status.has_status?).to be_truthy
subject
+ # We need to reset lazy_latest_pipeline cache to simulate a new request
+ BatchLoader::Executor.clear_current
+
# Need to use find to avoid memoization
expect(Project.find(project.id).pipeline_status.has_status?).to be_falsey
end
@@ -57,6 +61,10 @@ RSpec.describe ::Ci::DestroyPipelineService do
expect { artifact.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
+
+ it 'inserts deleted objects for object storage files' do
+ expect { subject }.to change { Ci::DeletedObject.count }
+ end
end
end
end
diff --git a/spec/services/ci/expire_pipeline_cache_service_spec.rb b/spec/services/ci/expire_pipeline_cache_service_spec.rb
index 3dbf2dbb8f1..613bbe45e68 100644
--- a/spec/services/ci/expire_pipeline_cache_service_spec.rb
+++ b/spec/services/ci/expire_pipeline_cache_service_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Ci::ExpirePipelineCacheService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
+
subject { described_class.new }
describe '#execute' do
@@ -14,12 +15,14 @@ RSpec.describe Ci::ExpirePipelineCacheService do
new_mr_pipelines_path = "/#{project.full_path}/-/merge_requests/new.json"
pipeline_path = "/#{project.full_path}/-/pipelines/#{pipeline.id}.json"
graphql_pipeline_path = "/api/graphql:pipelines/id/#{pipeline.id}"
+ graphql_pipeline_sha_path = "/api/graphql:pipelines/sha/#{pipeline.sha}"
expect_next_instance_of(Gitlab::EtagCaching::Store) do |store|
expect(store).to receive(:touch).with(pipelines_path)
expect(store).to receive(:touch).with(new_mr_pipelines_path)
expect(store).to receive(:touch).with(pipeline_path)
expect(store).to receive(:touch).with(graphql_pipeline_path)
+ expect(store).to receive(:touch).with(graphql_pipeline_sha_path)
end
subject.execute(pipeline)
@@ -49,7 +52,7 @@ RSpec.describe Ci::ExpirePipelineCacheService do
let(:project_with_repo) { create(:project, :repository) }
let!(:pipeline_with_commit) { create(:ci_pipeline, :success, project: project_with_repo, sha: project_with_repo.commit.id) }
- it 'clears the cache', :use_clean_rails_memory_store_caching do
+ it 'clears the cache', :use_clean_rails_redis_caching do
create(:commit_status, :success, pipeline: pipeline_with_commit, ref: pipeline_with_commit.ref)
# Sanity check
@@ -59,6 +62,9 @@ RSpec.describe Ci::ExpirePipelineCacheService do
pipeline_with_commit.destroy!
+ # We need to reset lazy_latest_pipeline cache to simulate a new request
+ BatchLoader::Executor.clear_current
+
# Need to use find to avoid memoization
expect(Project.find(project_with_repo.id).pipeline_status.has_status?).to be_falsey
end
diff --git a/spec/services/ci/external_pull_requests/create_pipeline_service_spec.rb b/spec/services/ci/external_pull_requests/create_pipeline_service_spec.rb
index 0cbeaa5446b..e25dd351bb3 100644
--- a/spec/services/ci/external_pull_requests/create_pipeline_service_spec.rb
+++ b/spec/services/ci/external_pull_requests/create_pipeline_service_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Ci::ExternalPullRequests::CreatePipelineService do
describe '#execute' do
let_it_be(:project) { create(:project, :auto_devops, :repository) }
let_it_be(:user) { create(:user) }
+
let(:pull_request) { create(:external_pull_request, project: project) }
before do
diff --git a/spec/services/ci/find_exposed_artifacts_service_spec.rb b/spec/services/ci/find_exposed_artifacts_service_spec.rb
index 287f5c4b929..32d96471f16 100644
--- a/spec/services/ci/find_exposed_artifacts_service_spec.rb
+++ b/spec/services/ci/find_exposed_artifacts_service_spec.rb
@@ -15,6 +15,7 @@ RSpec.describe Ci::FindExposedArtifactsService do
end
let_it_be(:project) { create(:project) }
+
let(:user) { nil }
after do
diff --git a/spec/services/ci/generate_codequality_mr_diff_report_service_spec.rb b/spec/services/ci/generate_codequality_mr_diff_report_service_spec.rb
index 5d747a09f2a..63bc7a1caf8 100644
--- a/spec/services/ci/generate_codequality_mr_diff_report_service_spec.rb
+++ b/spec/services/ci/generate_codequality_mr_diff_report_service_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe Ci::GenerateCodequalityMrDiffReportService do
subject { service.execute(base_pipeline, head_pipeline) }
context 'when head pipeline has codequality mr diff report' do
- let!(:merge_request) { create(:merge_request, :with_codequality_mr_diff_reports, source_project: project) }
+ let!(:merge_request) { create(:merge_request, :with_codequality_mr_diff_reports, source_project: project, id: 123456789) }
let!(:service) { described_class.new(project, nil, id: merge_request.id) }
let!(:head_pipeline) { merge_request.head_pipeline }
let!(:base_pipeline) { nil }
@@ -18,7 +18,7 @@ RSpec.describe Ci::GenerateCodequalityMrDiffReportService do
it 'returns status and data', :aggregate_failures do
expect_any_instance_of(Ci::PipelineArtifact) do |instance|
expect(instance).to receive(:present)
- expect(instance).to receive(:for_files).with(merge_request.new_paths).and_call_original
+ expect(instance).to receive(:for_files).with(merge_request).and_call_original
end
expect(subject[:status]).to eq(:parsed)
diff --git a/spec/services/ci/job_artifacts/create_service_spec.rb b/spec/services/ci/job_artifacts/create_service_spec.rb
index 22aa9e62c6f..97c65dc005e 100644
--- a/spec/services/ci/job_artifacts/create_service_spec.rb
+++ b/spec/services/ci/job_artifacts/create_service_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Ci::JobArtifacts::CreateService do
let_it_be(:project) { create(:project) }
+
let(:service) { described_class.new(job) }
let(:job) { create(:ci_build, project: project) }
let(:artifacts_sha256) { '0' * 64 }
diff --git a/spec/services/ci/job_artifacts/destroy_associations_service_spec.rb b/spec/services/ci/job_artifacts/destroy_associations_service_spec.rb
new file mode 100644
index 00000000000..b1a4741851b
--- /dev/null
+++ b/spec/services/ci/job_artifacts/destroy_associations_service_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::JobArtifacts::DestroyAssociationsService do
+ let(:artifacts) { Ci::JobArtifact.all }
+ let(:service) { described_class.new(artifacts) }
+
+ let_it_be(:artifact, refind: true) do
+ create(:ci_job_artifact)
+ end
+
+ before do
+ artifact.file = fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts.zip'), 'application/zip')
+ artifact.save!
+ end
+
+ describe '#destroy_records' do
+ it 'removes artifacts without updating statistics' do
+ expect(ProjectStatistics).not_to receive(:increment_statistic)
+
+ expect { service.destroy_records }.to change { Ci::JobArtifact.count }
+ end
+
+ context 'when there are no artifacts' do
+ let(:artifacts) { Ci::JobArtifact.none }
+
+ it 'does not raise error' do
+ expect { service.destroy_records }.not_to raise_error
+ end
+ end
+ end
+
+ describe '#update_statistics' do
+ before do
+ service.destroy_records
+ end
+
+ it 'updates project statistics' do
+ expect(ProjectStatistics).to receive(:increment_statistic).once
+ .with(artifact.project, :build_artifacts_size, -artifact.file.size)
+
+ service.update_statistics
+ end
+
+ context 'when there are no artifacts' do
+ let(:artifacts) { Ci::JobArtifact.none }
+
+ it 'does not raise error' do
+ expect { service.update_statistics }.not_to raise_error
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb b/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb
index 52aaf73d67e..2cedbf93d74 100644
--- a/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb
+++ b/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb
@@ -3,8 +3,6 @@
require 'spec_helper'
RSpec.describe Ci::JobArtifacts::DestroyBatchService do
- include ExclusiveLeaseHelpers
-
let(:artifacts) { Ci::JobArtifact.all }
let(:service) { described_class.new(artifacts, pick_up_at: Time.current) }
@@ -25,14 +23,6 @@ RSpec.describe Ci::JobArtifacts::DestroyBatchService do
expect { subject }.to change { Ci::DeletedObject.count }.by(1)
end
- it 'resets project statistics' do
- expect(ProjectStatistics).to receive(:increment_statistic).once
- .with(artifact.project, :build_artifacts_size, -artifact.file.size)
- .and_call_original
-
- execute
- end
-
it 'does not remove the files' do
expect { execute }.not_to change { artifact.file.exists? }
end
@@ -44,6 +34,29 @@ RSpec.describe Ci::JobArtifacts::DestroyBatchService do
execute
end
+
+ context 'ProjectStatistics' do
+ it 'resets project statistics' do
+ expect(ProjectStatistics).to receive(:increment_statistic).once
+ .with(artifact.project, :build_artifacts_size, -artifact.file.size)
+ .and_call_original
+
+ execute
+ end
+
+ context 'with update_stats: false' do
+ it 'does not update project statistics' do
+ expect(ProjectStatistics).not_to receive(:increment_statistic)
+
+ service.execute(update_stats: false)
+ end
+
+ it 'returns size statistics' do
+ expect(service.execute(update_stats: false)).to match(
+ a_hash_including(statistics_updates: { artifact.project => -artifact.file.size }))
+ end
+ end
+ end
end
context 'when failed to destroy artifact' do
@@ -65,16 +78,12 @@ RSpec.describe Ci::JobArtifacts::DestroyBatchService do
context 'when there are no artifacts' do
let(:artifacts) { Ci::JobArtifact.none }
- before do
- artifact.destroy!
- end
-
it 'does not raise error' do
expect { execute }.not_to raise_error
end
it 'reports the number of destroyed artifacts' do
- is_expected.to eq(destroyed_artifacts_count: 0, status: :success)
+ is_expected.to eq(destroyed_artifacts_count: 0, statistics_updates: {}, status: :success)
end
end
end
diff --git a/spec/services/ci/parse_dotenv_artifact_service_spec.rb b/spec/services/ci/parse_dotenv_artifact_service_spec.rb
index 91b81af9fd1..7536e04f2de 100644
--- a/spec/services/ci/parse_dotenv_artifact_service_spec.rb
+++ b/spec/services/ci/parse_dotenv_artifact_service_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Ci::ParseDotenvArtifactService do
let_it_be(:project) { create(:project) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
+
let(:build) { create(:ci_build, pipeline: pipeline, project: project) }
let(:service) { described_class.new(project, nil) }
@@ -24,7 +25,7 @@ RSpec.describe Ci::ParseDotenvArtifactService do
context 'when parse error happens' do
before do
- allow(service).to receive(:scan_line!) { raise described_class::ParserError.new('Invalid Format') }
+ allow(service).to receive(:scan_line!) { raise described_class::ParserError, 'Invalid Format' }
end
it 'returns error' do
diff --git a/spec/services/ci/pipeline_artifacts/create_code_quality_mr_diff_report_service_spec.rb b/spec/services/ci/pipeline_artifacts/create_code_quality_mr_diff_report_service_spec.rb
index 0c48f15d726..5568052e346 100644
--- a/spec/services/ci/pipeline_artifacts/create_code_quality_mr_diff_report_service_spec.rb
+++ b/spec/services/ci/pipeline_artifacts/create_code_quality_mr_diff_report_service_spec.rb
@@ -4,58 +4,76 @@ require 'spec_helper'
RSpec.describe ::Ci::PipelineArtifacts::CreateCodeQualityMrDiffReportService do
describe '#execute' do
- subject(:pipeline_artifact) { described_class.new.execute(pipeline) }
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.project }
+ let(:head_pipeline) { create(:ci_pipeline, :success, :with_codequality_reports, project: project, merge_requests_as_head_pipeline: [merge_request]) }
+ let(:base_pipeline) { create(:ci_pipeline, :success, project: project, ref: merge_request.target_branch, sha: merge_request.diff_base_sha) }
- context 'when pipeline has codequality reports' do
- let(:project) { create(:project, :repository) }
+ subject { described_class.new(head_pipeline).execute }
- describe 'pipeline completed status' do
- using RSpec::Parameterized::TableSyntax
+ context 'when there are codequality reports' do
+ context 'when pipeline passes' do
+ context 'when degradations are present' do
+ context 'when degradations already present in target branch pipeline' do
+ before do
+ create(:ci_build, :success, :codequality_reports, name: 'codequality', pipeline: base_pipeline, project: project)
+ end
- where(:status, :result) do
- :success | 1
- :failed | 1
- :canceled | 1
- :skipped | 1
- end
+ it "does not persist a pipeline artifact" do
+ expect { subject }.not_to change { Ci::PipelineArtifact.count }
+ end
+ end
+
+ context 'when degradation is not present in target branch pipeline' do
+ before do
+ create(:ci_build, :success, :codequality_reports_without_degradation, name: 'codequality', pipeline: base_pipeline, project: project)
+ end
- with_them do
- let(:pipeline) { create(:ci_pipeline, :with_codequality_reports, status: status, project: project) }
+ it 'persists a pipeline artifact' do
+ expect { subject }.to change { Ci::PipelineArtifact.count }.by(1)
+ end
- it 'creates a pipeline artifact' do
- expect { pipeline_artifact }.to change(Ci::PipelineArtifact, :count).by(result)
- end
+ it 'persists the default file name' do
+ subject
- it 'persists the default file name' do
- expect(pipeline_artifact.file.filename).to eq('code_quality_mr_diff.json')
- end
+ pipeline_artifact = Ci::PipelineArtifact.first
- it 'sets expire_at to 1 week' do
- freeze_time do
- expect(pipeline_artifact.expire_at).to eq(1.week.from_now)
+ expect(pipeline_artifact.file.filename).to eq('code_quality_mr_diff.json')
end
- end
- end
- end
- context 'when pipeline artifact has already been created' do
- let(:pipeline) { create(:ci_pipeline, :with_codequality_reports, project: project) }
+ it 'sets expire_at to 1 week' do
+ freeze_time do
+ subject
+
+ pipeline_artifact = Ci::PipelineArtifact.first
+
+ expect(pipeline_artifact.expire_at).to eq(1.week.from_now)
+ end
+ end
- it 'does not persist the same artifact twice' do
- 2.times { described_class.new.execute(pipeline) }
+ it 'does not persist the same artifact twice' do
+ 2.times { described_class.new(head_pipeline).execute }
- expect(Ci::PipelineArtifact.count).to eq(1)
+ expect { subject }.not_to change { Ci::PipelineArtifact.count }
+ end
+ end
end
end
end
- context 'when pipeline is not completed and codequality report does not exist' do
- let(:pipeline) { create(:ci_pipeline, :running) }
+ context 'when there are no codequality reports for head pipeline' do
+ let(:head_pipeline) { create(:ci_pipeline, :success, project: project, merge_requests_as_head_pipeline: [merge_request]) }
+
+ it "does not persist a pipeline artifact" do
+ expect { subject }.not_to change { Ci::PipelineArtifact.count }
+ end
+ end
- it 'does not persist data' do
- pipeline_artifact
+ context 'when there are no codequality reports for base pipeline' do
+ let(:head_pipeline) { create(:ci_pipeline, :success, project: project, merge_requests_as_head_pipeline: [merge_request]) }
- expect(Ci::PipelineArtifact.count).to eq(0)
+ it "does not persist a pipeline artifact" do
+ expect { subject }.not_to change { Ci::PipelineArtifact.count }
end
end
end
diff --git a/spec/services/ci/pipeline_artifacts/destroy_all_expired_service_spec.rb b/spec/services/ci/pipeline_artifacts/destroy_all_expired_service_spec.rb
index 3dc4f35df22..eb664043567 100644
--- a/spec/services/ci/pipeline_artifacts/destroy_all_expired_service_spec.rb
+++ b/spec/services/ci/pipeline_artifacts/destroy_all_expired_service_spec.rb
@@ -30,7 +30,7 @@ RSpec.describe Ci::PipelineArtifacts::DestroyAllExpiredService do
stub_const('::Ci::PipelineArtifacts::DestroyAllExpiredService::LOOP_LIMIT', 1)
stub_const('::Ci::PipelineArtifacts::DestroyAllExpiredService::BATCH_SIZE', 1)
- create_list(:ci_pipeline_artifact, 2, expire_at: 1.week.ago)
+ create_list(:ci_pipeline_artifact, 2, :unlocked, expire_at: 1.week.ago)
end
it 'destroys one artifact' do
@@ -46,7 +46,7 @@ RSpec.describe Ci::PipelineArtifacts::DestroyAllExpiredService do
before do
stub_const('Ci::PipelineArtifacts::DestroyAllExpiredService::BATCH_SIZE', 1)
- create_list(:ci_pipeline_artifact, 2, expire_at: 1.week.ago)
+ create_list(:ci_pipeline_artifact, 2, :unlocked, expire_at: 1.week.ago)
end
it 'destroys all expired artifacts' do
@@ -60,7 +60,21 @@ RSpec.describe Ci::PipelineArtifacts::DestroyAllExpiredService do
context 'when artifacts are not expired' do
before do
- create(:ci_pipeline_artifact, expire_at: 2.days.from_now)
+ create(:ci_pipeline_artifact, :unlocked, expire_at: 2.days.from_now)
+ end
+
+ it 'does not destroy pipeline artifacts' do
+ expect { subject }.not_to change { Ci::PipelineArtifact.count }
+ end
+
+ it 'reports the number of destroyed artifacts' do
+ is_expected.to eq(0)
+ end
+ end
+
+ context 'when pipeline is locked' do
+ before do
+ create(:ci_pipeline_artifact, expire_at: 2.weeks.ago)
end
it 'does not destroy pipeline artifacts' do
diff --git a/spec/services/ci/pipeline_bridge_status_service_spec.rb b/spec/services/ci/pipeline_bridge_status_service_spec.rb
index 584b23bb3aa..1346f68c952 100644
--- a/spec/services/ci/pipeline_bridge_status_service_spec.rb
+++ b/spec/services/ci/pipeline_bridge_status_service_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Ci::PipelineBridgeStatusService do
let(:user) { build(:user) }
let_it_be(:project) { create(:project) }
+
let(:pipeline) { build(:ci_pipeline, project: project) }
describe '#execute' do
diff --git a/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb b/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb
index bc8b6b2d113..a66d3898c5c 100644
--- a/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb
+++ b/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
require 'spec_helper'
-require_relative 'shared_processing_service.rb'
-require_relative 'shared_processing_service_tests_with_yaml.rb'
+require_relative 'shared_processing_service'
+require_relative 'shared_processing_service_tests_with_yaml'
RSpec.describe Ci::PipelineProcessing::AtomicProcessingService do
it_behaves_like 'Pipeline Processing Service'
diff --git a/spec/services/ci/pipeline_processing/test_cases/dag_test_manual_post_test_needs_deploy_is_stage.yml b/spec/services/ci/pipeline_processing/test_cases/dag_test_manual_post_test_needs_deploy_is_stage.yml
new file mode 100644
index 00000000000..03d5781395d
--- /dev/null
+++ b/spec/services/ci/pipeline_processing/test_cases/dag_test_manual_post_test_needs_deploy_is_stage.yml
@@ -0,0 +1,50 @@
+config:
+ stages: [build, test, post_test, deploy]
+
+ build:
+ stage: build
+ script: exit 0
+
+ test:
+ stage: test
+ script: exit 0
+ when: manual
+
+ post_test:
+ stage: post_test
+ script: exit 0
+ needs: [test]
+
+ deploy:
+ stage: deploy
+ script: exit 0
+
+init:
+ expect:
+ pipeline: pending
+ stages:
+ build: pending
+ test: created
+ post_test: created
+ deploy: created
+ jobs:
+ build: pending
+ test: created
+ post_test: created
+ deploy: created
+
+transitions:
+ - event: success
+ jobs: [build]
+ expect:
+ pipeline: running
+ stages:
+ build: success
+ test: skipped
+ post_test: skipped
+ deploy: pending
+ jobs:
+ build: success
+ test: manual
+ post_test: skipped
+ deploy: pending
diff --git a/spec/services/ci/pipeline_trigger_service_spec.rb b/spec/services/ci/pipeline_trigger_service_spec.rb
index 36055779a2e..080ca1cf0cd 100644
--- a/spec/services/ci/pipeline_trigger_service_spec.rb
+++ b/spec/services/ci/pipeline_trigger_service_spec.rb
@@ -13,12 +13,35 @@ RSpec.describe Ci::PipelineTriggerService do
describe '#execute' do
let_it_be(:user) { create(:user) }
+
let(:result) { described_class.new(project, user, params).execute }
before do
project.add_developer(user)
end
+ shared_examples 'detecting an unprocessable pipeline trigger' do
+ context 'when the pipeline was not created successfully' do
+ let(:fail_pipeline) do
+ receive(:execute).and_wrap_original do |original, *args|
+ pipeline = original.call(*args)
+ pipeline.update!(failure_reason: 'unknown_failure')
+ pipeline
+ end
+ end
+
+ before do
+ allow_next(Ci::CreatePipelineService).to fail_pipeline
+ end
+
+ it 'has the correct status code' do
+ expect { result }.to change { Ci::Pipeline.count }
+ expect(result).to be_error
+ expect(result.http_status).to eq(:unprocessable_entity)
+ end
+ end
+ end
+
context 'with a trigger token' do
let(:trigger) { create(:ci_trigger, project: project, owner: user) }
@@ -62,7 +85,7 @@ RSpec.describe Ci::PipelineTriggerService do
it 'ignores [ci skip] and create as general' do
expect { result }.to change { Ci::Pipeline.count }.by(1)
- expect(result[:status]).to eq(:success)
+ expect(result).to be_success
end
end
@@ -77,19 +100,22 @@ RSpec.describe Ci::PipelineTriggerService do
expect(result[:pipeline].trigger_requests.last.variables).to be_nil
end
end
+
+ it_behaves_like 'detecting an unprocessable pipeline trigger'
end
- context 'when params have a non-existsed ref' do
+ context 'when params have a non-existant ref' do
let(:params) { { token: trigger.token, ref: 'invalid-ref', variables: nil } }
it 'does not trigger a pipeline' do
expect { result }.not_to change { Ci::Pipeline.count }
- expect(result[:http_status]).to eq(400)
+ expect(result).to be_error
+ expect(result.http_status).to eq(:bad_request)
end
end
end
- context 'when params have a non-existsed trigger token' do
+ context 'when params have a non-existant trigger token' do
let(:params) { { token: 'invalid-token', ref: nil, variables: nil } }
it 'does not trigger a pipeline' do
@@ -172,14 +198,17 @@ RSpec.describe Ci::PipelineTriggerService do
expect(job.sourced_pipelines.last.pipeline_id).to eq(result[:pipeline].id)
end
end
+
+ it_behaves_like 'detecting an unprocessable pipeline trigger'
end
- context 'when params have a non-existsed ref' do
+ context 'when params have a non-existant ref' do
let(:params) { { token: job.token, ref: 'invalid-ref', variables: nil } }
- it 'does not job a pipeline' do
+ it 'does not trigger a job in the pipeline' do
expect { result }.not_to change { Ci::Pipeline.count }
- expect(result[:http_status]).to eq(400)
+ expect(result).to be_error
+ expect(result.http_status).to eq(:bad_request)
end
end
end
diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb
index 254bd19c808..b5bf0adadaf 100644
--- a/spec/services/ci/process_pipeline_service_spec.rb
+++ b/spec/services/ci/process_pipeline_service_spec.rb
@@ -3,8 +3,7 @@
require 'spec_helper'
RSpec.describe Ci::ProcessPipelineService do
- let(:user) { create(:user) }
- let(:project) { create(:project) }
+ let_it_be(:project) { create(:project) }
let(:pipeline) do
create(:ci_empty_pipeline, ref: 'master', project: project)
@@ -24,8 +23,6 @@ RSpec.describe Ci::ProcessPipelineService do
stub_ci_pipeline_to_return_yaml_file
stub_not_protect_default_branch
- project.add_developer(user)
-
allow(subject).to receive(:metrics).and_return(metrics)
end
@@ -69,6 +66,14 @@ RSpec.describe Ci::ProcessPipelineService do
subject.execute
end
+ it 'logs the project and pipeline id' do
+ expect(Gitlab::AppJsonLogger).to receive(:info).with(event: 'update_retried_is_used',
+ project_id: project.id,
+ pipeline_id: pipeline.id)
+
+ subject.execute
+ end
+
context 'when the previous build has already retried column true' do
before do
build_retried.update_columns(retried: true)
diff --git a/spec/services/ci/prometheus_metrics/observe_histograms_service_spec.rb b/spec/services/ci/prometheus_metrics/observe_histograms_service_spec.rb
index 2eef852b0f4..0b100af5902 100644
--- a/spec/services/ci/prometheus_metrics/observe_histograms_service_spec.rb
+++ b/spec/services/ci/prometheus_metrics/observe_histograms_service_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Ci::PrometheusMetrics::ObserveHistogramsService do
let_it_be(:project) { create(:project) }
+
let(:params) { {} }
subject(:execute) { described_class.new(project, params).execute }
@@ -54,32 +55,6 @@ RSpec.describe Ci::PrometheusMetrics::ObserveHistogramsService do
end
end
- context 'with feature flag disabled' do
- before do
- stub_feature_flags(ci_accept_frontend_prometheus_metrics: false)
- end
-
- let(:params) do
- {
- histograms: [
- { name: 'pipeline_graph_link_calculation_duration_seconds', value: '4' }
- ]
- }
- end
-
- it 'does not register the metrics' do
- execute
-
- expect(histogram_data).to be_nil
- end
-
- it 'returns an empty body and status code' do
- is_expected.to be_success
- expect(subject.http_status).to eq(:accepted)
- expect(subject.payload).to eq({})
- end
- end
-
def histogram_data(name = :pipeline_graph_link_calculation_duration_seconds)
Gitlab::Metrics.registry.get(name)&.get({})
end
diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb
index 02b48e8ba06..839a3c53f07 100644
--- a/spec/services/ci/register_job_service_spec.rb
+++ b/spec/services/ci/register_job_service_spec.rb
@@ -7,6 +7,7 @@ module Ci
let_it_be(:group) { create(:group) }
let_it_be(:project, reload: true) { create(:project, group: group, shared_runners_enabled: false, group_runners_enabled: false) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
+
let!(:shared_runner) { create(:ci_runner, :instance) }
let!(:specific_runner) { create(:ci_runner, :project, projects: [project]) }
let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
@@ -81,31 +82,69 @@ module Ci
let!(:build2_project2) { FactoryBot.create :ci_build, pipeline: pipeline2 }
let!(:build1_project3) { FactoryBot.create :ci_build, pipeline: pipeline3 }
- it 'prefers projects without builds first' do
- # it gets for one build from each of the projects
- expect(execute(shared_runner)).to eq(build1_project1)
- expect(execute(shared_runner)).to eq(build1_project2)
- expect(execute(shared_runner)).to eq(build1_project3)
-
- # then it gets a second build from each of the projects
- expect(execute(shared_runner)).to eq(build2_project1)
- expect(execute(shared_runner)).to eq(build2_project2)
+ context 'when using fair scheduling' do
+ context 'when all builds are pending' do
+ it 'prefers projects without builds first' do
+ # it gets for one build from each of the projects
+ expect(execute(shared_runner)).to eq(build1_project1)
+ expect(execute(shared_runner)).to eq(build1_project2)
+ expect(execute(shared_runner)).to eq(build1_project3)
+
+ # then it gets a second build from each of the projects
+ expect(execute(shared_runner)).to eq(build2_project1)
+ expect(execute(shared_runner)).to eq(build2_project2)
+
+ # in the end the third build
+ expect(execute(shared_runner)).to eq(build3_project1)
+ end
+ end
- # in the end the third build
- expect(execute(shared_runner)).to eq(build3_project1)
+ context 'when some builds transition to success' do
+ it 'equalises number of running builds' do
+ # after finishing the first build for project 1, get a second build from the same project
+ expect(execute(shared_runner)).to eq(build1_project1)
+ build1_project1.reload.success
+ expect(execute(shared_runner)).to eq(build2_project1)
+
+ expect(execute(shared_runner)).to eq(build1_project2)
+ build1_project2.reload.success
+ expect(execute(shared_runner)).to eq(build2_project2)
+ expect(execute(shared_runner)).to eq(build1_project3)
+ expect(execute(shared_runner)).to eq(build3_project1)
+ end
+ end
end
- it 'equalises number of running builds' do
- # after finishing the first build for project 1, get a second build from the same project
- expect(execute(shared_runner)).to eq(build1_project1)
- build1_project1.reload.success
- expect(execute(shared_runner)).to eq(build2_project1)
+ context 'when using DEFCON mode that disables fair scheduling' do
+ before do
+ stub_feature_flags(ci_queueing_disaster_recovery: true)
+ end
+
+ context 'when all builds are pending' do
+ it 'returns builds in order of creation (FIFO)' do
+ # it gets for one build from each of the projects
+ expect(execute(shared_runner)).to eq(build1_project1)
+ expect(execute(shared_runner)).to eq(build2_project1)
+ expect(execute(shared_runner)).to eq(build3_project1)
+ expect(execute(shared_runner)).to eq(build1_project2)
+ expect(execute(shared_runner)).to eq(build2_project2)
+ expect(execute(shared_runner)).to eq(build1_project3)
+ end
+ end
- expect(execute(shared_runner)).to eq(build1_project2)
- build1_project2.reload.success
- expect(execute(shared_runner)).to eq(build2_project2)
- expect(execute(shared_runner)).to eq(build1_project3)
- expect(execute(shared_runner)).to eq(build3_project1)
+ context 'when some builds transition to success' do
+ it 'returns builds in order of creation (FIFO)' do
+ expect(execute(shared_runner)).to eq(build1_project1)
+ build1_project1.reload.success
+ expect(execute(shared_runner)).to eq(build2_project1)
+
+ expect(execute(shared_runner)).to eq(build3_project1)
+ build2_project1.reload.success
+ expect(execute(shared_runner)).to eq(build1_project2)
+ expect(execute(shared_runner)).to eq(build2_project2)
+ expect(execute(shared_runner)).to eq(build1_project3)
+ end
+ end
end
end
@@ -477,10 +516,6 @@ module Ci
end
end
- before do
- stub_feature_flags(ci_validate_build_dependencies_override: false)
- end
-
let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0) }
let!(:pending_job) do
@@ -491,37 +526,7 @@ module Ci
subject { execute(specific_runner) }
- context 'when validates for dependencies is enabled' do
- before do
- stub_feature_flags(ci_validate_build_dependencies_override: false)
- end
-
- it_behaves_like 'validation is active'
-
- context 'when the main feature flag is enabled for a specific project' do
- before do
- stub_feature_flags(ci_validate_build_dependencies: pipeline.project)
- end
-
- it_behaves_like 'validation is active'
- end
-
- context 'when the main feature flag is enabled for a different project' do
- before do
- stub_feature_flags(ci_validate_build_dependencies: create(:project))
- end
-
- it_behaves_like 'validation is not active'
- end
- end
-
- context 'when validates for dependencies is disabled' do
- before do
- stub_feature_flags(ci_validate_build_dependencies_override: true)
- end
-
- it_behaves_like 'validation is not active'
- end
+ it_behaves_like 'validation is active'
end
context 'when build is degenerated' do
diff --git a/spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb b/spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb
index 6c69a7f3b11..a741e3b49e7 100644
--- a/spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb
+++ b/spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Ci::ResourceGroups::AssignResourceFromResourceGroupService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
+
let(:service) { described_class.new(project, user) }
describe '#execute' do
diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb
index 7dd3d963e56..86bda868625 100644
--- a/spec/services/ci/retry_build_service_spec.rb
+++ b/spec/services/ci/retry_build_service_spec.rb
@@ -18,6 +18,7 @@ RSpec.describe Ci::RetryBuildService do
end
let_it_be_with_refind(:build) { create(:ci_build, pipeline: pipeline, stage_id: stage.id) }
+
let(:user) { developer }
let(:service) do
diff --git a/spec/services/ci/stop_environments_service_spec.rb b/spec/services/ci/stop_environments_service_spec.rb
index 5a0b7f23556..d5ef67c871c 100644
--- a/spec/services/ci/stop_environments_service_spec.rb
+++ b/spec/services/ci/stop_environments_service_spec.rb
@@ -188,6 +188,7 @@ RSpec.describe Ci::StopEnvironmentsService do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
+
let(:environments) { Environment.available }
before_all do
diff --git a/spec/services/clusters/applications/prometheus_update_service_spec.rb b/spec/services/clusters/applications/prometheus_update_service_spec.rb
index 076ff0210c9..615bfc44045 100644
--- a/spec/services/clusters/applications/prometheus_update_service_spec.rb
+++ b/spec/services/clusters/applications/prometheus_update_service_spec.rb
@@ -9,83 +9,102 @@ RSpec.describe Clusters::Applications::PrometheusUpdateService do
let(:cluster) { create(:cluster, :provided_by_user, :with_installed_helm, projects: [project]) }
let(:application) { create(:clusters_applications_prometheus, :installed, cluster: cluster) }
let(:empty_alerts_values_update_yaml) { "---\nalertmanager:\n enabled: false\nserverFiles:\n alerts: {}\n" }
- let!(:patch_command) { application.patch_command(empty_alerts_values_update_yaml) }
let(:helm_client) { instance_double(::Gitlab::Kubernetes::Helm::API) }
subject(:service) { described_class.new(application, project) }
- before do
- allow(service).to receive(:patch_command).with(empty_alerts_values_update_yaml).and_return(patch_command)
- allow(service).to receive(:helm_api).and_return(helm_client)
+ context 'when prometheus is a Clusters::Integrations::Prometheus' do
+ let(:application) { create(:clusters_integrations_prometheus, cluster: cluster) }
+
+ it 'raises NotImplementedError' do
+ expect { service.execute }.to raise_error(NotImplementedError)
+ end
end
- context 'when there are no errors' do
- before do
- expect(helm_client).to receive(:update).with(patch_command)
+ context 'when prometheus is externally installed' do
+ let(:application) { create(:clusters_applications_prometheus, :externally_installed, cluster: cluster) }
- allow(::ClusterWaitForAppUpdateWorker)
- .to receive(:perform_in)
- .and_return(nil)
+ it 'raises NotImplementedError' do
+ expect { service.execute }.to raise_error(NotImplementedError)
end
+ end
- it 'make the application updating' do
- expect(application.cluster).not_to be_nil
-
- service.execute
+ context 'when prometheus is a Clusters::Applications::Prometheus' do
+ let!(:patch_command) { application.patch_command(empty_alerts_values_update_yaml) }
- expect(application).to be_updating
+ before do
+ allow(service).to receive(:patch_command).with(empty_alerts_values_update_yaml).and_return(patch_command)
+ allow(service).to receive(:helm_api).and_return(helm_client)
end
- it 'updates current config' do
- prometheus_config_service = spy(:prometheus_config_service)
+ context 'when there are no errors' do
+ before do
+ expect(helm_client).to receive(:update).with(patch_command)
- expect(Clusters::Applications::PrometheusConfigService)
- .to receive(:new)
- .with(project, cluster, application)
- .and_return(prometheus_config_service)
+ allow(::ClusterWaitForAppUpdateWorker)
+ .to receive(:perform_in)
+ .and_return(nil)
+ end
- expect(prometheus_config_service)
- .to receive(:execute)
- .and_return(YAML.safe_load(empty_alerts_values_update_yaml))
+ it 'make the application updating' do
+ expect(application.cluster).not_to be_nil
- service.execute
- end
+ service.execute
- it 'schedules async update status check' do
- expect(::ClusterWaitForAppUpdateWorker).to receive(:perform_in).once
+ expect(application).to be_updating
+ end
- service.execute
- end
- end
+ it 'updates current config' do
+ prometheus_config_service = spy(:prometheus_config_service)
- context 'when k8s cluster communication fails' do
- before do
- error = ::Kubeclient::HttpError.new(500, 'system failure', nil)
- allow(helm_client).to receive(:update).and_raise(error)
- end
+ expect(Clusters::Applications::PrometheusConfigService)
+ .to receive(:new)
+ .with(project, cluster, application)
+ .and_return(prometheus_config_service)
+
+ expect(prometheus_config_service)
+ .to receive(:execute)
+ .and_return(YAML.safe_load(empty_alerts_values_update_yaml))
- it 'make the application update errored' do
- service.execute
+ service.execute
+ end
- expect(application).to be_update_errored
- expect(application.status_reason).to match(/kubernetes error:/i)
+ it 'schedules async update status check' do
+ expect(::ClusterWaitForAppUpdateWorker).to receive(:perform_in).once
+
+ service.execute
+ end
end
- end
- context 'when application cannot be persisted' do
- let(:application) { build(:clusters_applications_prometheus, :installed) }
+ context 'when k8s cluster communication fails' do
+ before do
+ error = ::Kubeclient::HttpError.new(500, 'system failure', nil)
+ allow(helm_client).to receive(:update).and_raise(error)
+ end
- before do
- allow(application).to receive(:make_updating!).once
- .and_raise(ActiveRecord::RecordInvalid.new(application))
+ it 'make the application update errored' do
+ service.execute
+
+ expect(application).to be_update_errored
+ expect(application.status_reason).to match(/kubernetes error:/i)
+ end
end
- it 'make the application update errored' do
- expect(helm_client).not_to receive(:update)
+ context 'when application cannot be persisted' do
+ let(:application) { build(:clusters_applications_prometheus, :installed) }
+
+ before do
+ allow(application).to receive(:make_updating!).once
+ .and_raise(ActiveRecord::RecordInvalid.new(application))
+ end
+
+ it 'make the application update errored' do
+ expect(helm_client).not_to receive(:update)
- service.execute
+ service.execute
- expect(application).to be_update_errored
+ expect(application).to be_update_errored
+ end
end
end
end
diff --git a/spec/services/clusters/applications/schedule_update_service_spec.rb b/spec/services/clusters/applications/schedule_update_service_spec.rb
index 01a75a334e6..2cbcb861938 100644
--- a/spec/services/clusters/applications/schedule_update_service_spec.rb
+++ b/spec/services/clusters/applications/schedule_update_service_spec.rb
@@ -10,6 +10,32 @@ RSpec.describe Clusters::Applications::ScheduleUpdateService do
freeze_time { example.run }
end
+ context 'when the application is a Clusters::Integrations::Prometheus' do
+ let(:application) { create(:clusters_integrations_prometheus) }
+
+ it 'does nothing' do
+ service = described_class.new(application, project)
+
+ expect(::ClusterUpdateAppWorker).not_to receive(:perform_in)
+ expect(::ClusterUpdateAppWorker).not_to receive(:perform_async)
+
+ service.execute
+ end
+ end
+
+ context 'when the application is externally installed' do
+ let(:application) { create(:clusters_applications_prometheus, :externally_installed) }
+
+ it 'does nothing' do
+ service = described_class.new(application, project)
+
+ expect(::ClusterUpdateAppWorker).not_to receive(:perform_in)
+ expect(::ClusterUpdateAppWorker).not_to receive(:perform_async)
+
+ service.execute
+ end
+ end
+
context 'when application is able to be updated' do
context 'when the application was recently scheduled' do
it 'schedules worker with a backoff delay' do
diff --git a/spec/services/clusters/integrations/create_service_spec.rb b/spec/services/clusters/integrations/create_service_spec.rb
index cfc0943b6ad..14653236ab1 100644
--- a/spec/services/clusters/integrations/create_service_spec.rb
+++ b/spec/services/clusters/integrations/create_service_spec.rb
@@ -6,79 +6,64 @@ RSpec.describe Clusters::Integrations::CreateService, '#execute' do
let_it_be(:project) { create(:project) }
let_it_be_with_reload(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
- let(:params) do
- { application_type: 'prometheus', enabled: true }
- end
-
let(:service) do
described_class.new(container: project, cluster: cluster, current_user: project.owner, params: params)
end
- it 'creates a new Prometheus instance' do
- expect(service.execute).to be_success
-
- expect(cluster.integration_prometheus).to be_present
- expect(cluster.integration_prometheus).to be_persisted
- expect(cluster.integration_prometheus).to be_enabled
- end
-
- context 'enabled param is false' do
- let(:params) do
- { application_type: 'prometheus', enabled: false }
- end
-
- it 'creates a new uninstalled Prometheus instance' do
- expect(service.execute).to be_success
+ shared_examples_for 'a cluster integration' do |application_type|
+ let(:integration) { cluster.public_send("integration_#{application_type}") }
- expect(cluster.integration_prometheus).to be_present
- expect(cluster.integration_prometheus).to be_persisted
- expect(cluster.integration_prometheus).not_to be_enabled
- end
- end
+ context 'when enabled param is true' do
+ let(:params) do
+ { application_type: application_type, enabled: true }
+ end
- context 'unauthorized user' do
- let(:service) do
- unauthorized_user = create(:user)
+ it 'creates a new enabled integration' do
+ expect(service.execute).to be_success
- described_class.new(container: project, cluster: cluster, current_user: unauthorized_user, params: params)
+ expect(integration).to be_present
+ expect(integration).to be_persisted
+ expect(integration).to be_enabled
+ end
end
- it 'does not create a new Prometheus instance' do
- expect(service.execute).to be_error
+ context 'when enabled param is false' do
+ let(:params) do
+ { application_type: application_type, enabled: false }
+ end
- expect(cluster.integration_prometheus).to be_nil
- end
- end
+ it 'creates a new disabled integration' do
+ expect(service.execute).to be_success
- context 'prometheus record exists' do
- before do
- create(:clusters_integrations_prometheus, cluster: cluster)
+ expect(integration).to be_present
+ expect(integration).to be_persisted
+ expect(integration).not_to be_enabled
+ end
end
- it 'updates the Prometheus instance' do
- expect(service.execute).to be_success
-
- expect(cluster.integration_prometheus).to be_present
- expect(cluster.integration_prometheus).to be_persisted
- expect(cluster.integration_prometheus).to be_enabled
- end
+ context 'when integration already exists' do
+ before do
+ create(:"clusters_integrations_#{application_type}", cluster: cluster, enabled: false)
+ end
- context 'enabled param is false' do
let(:params) do
- { application_type: 'prometheus', enabled: false }
+ { application_type: application_type, enabled: true }
end
- it 'updates the Prometheus instance as uninstalled' do
+ it 'updates the integration' do
+ expect(integration).not_to be_enabled
+
expect(service.execute).to be_success
- expect(cluster.integration_prometheus).to be_present
- expect(cluster.integration_prometheus).to be_persisted
- expect(cluster.integration_prometheus).not_to be_enabled
+ expect(integration.reload).to be_enabled
end
end
end
- context 'for an un-supported application type' do
+ it_behaves_like 'a cluster integration', 'prometheus'
+ it_behaves_like 'a cluster integration', 'elastic_stack'
+
+ context 'when application_type is invalid' do
let(:params) do
{ application_type: 'something_else', enabled: true }
end
@@ -87,4 +72,22 @@ RSpec.describe Clusters::Integrations::CreateService, '#execute' do
expect { service.execute}.to raise_error(ArgumentError)
end
end
+
+ context 'when user is unauthorized' do
+ let(:params) do
+ { application_type: 'prometheus', enabled: true }
+ end
+
+ let(:service) do
+ unauthorized_user = create(:user)
+
+ described_class.new(container: project, cluster: cluster, current_user: unauthorized_user, params: params)
+ end
+
+ it 'returns error and does not create a new integration record' do
+ expect(service.execute).to be_error
+
+ expect(cluster.integration_prometheus).to be_nil
+ end
+ end
end
diff --git a/spec/services/clusters/management/create_project_service_spec.rb b/spec/services/clusters/management/create_project_service_spec.rb
deleted file mode 100644
index 5d8cc71faa4..00000000000
--- a/spec/services/clusters/management/create_project_service_spec.rb
+++ /dev/null
@@ -1,126 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Clusters::Management::CreateProjectService do
- let(:cluster) { create(:cluster, :project) }
- let(:current_user) { create(:user) }
-
- subject { described_class.new(cluster, current_user: current_user).execute }
-
- shared_examples 'management project is not required' do
- it 'does not create a project' do
- expect { subject }.not_to change(cluster, :management_project)
- end
- end
-
- context ':auto_create_cluster_management_project feature flag is disabled' do
- before do
- stub_feature_flags(auto_create_cluster_management_project: false)
- end
-
- include_examples 'management project is not required'
- end
-
- context 'cluster already has a management project' do
- let(:cluster) { create(:cluster, :management_project) }
-
- include_examples 'management project is not required'
- end
-
- shared_examples 'creates a management project' do
- let(:project_params) do
- {
- name: "#{cluster.name} Cluster Management",
- description: 'This project is automatically generated and will be used to manage your Kubernetes cluster. [More information](/help/user/clusters/management_project)',
- namespace_id: namespace&.id,
- visibility_level: Gitlab::VisibilityLevel::PRIVATE
- }
- end
-
- it 'creates a management project' do
- expect(Projects::CreateService).to receive(:new)
- .with(current_user, project_params)
- .and_call_original
-
- subject
-
- management_project = cluster.management_project
-
- expect(management_project).to be_present
- expect(management_project).to be_private
- expect(management_project.name).to eq "#{cluster.name} Cluster Management"
- expect(management_project.namespace).to eq namespace
- end
- end
-
- context 'project cluster' do
- let(:cluster) { create(:cluster, projects: [project]) }
- let(:project) { create(:project, namespace: current_user.namespace) }
- let(:namespace) { project.namespace }
-
- include_examples 'creates a management project'
- end
-
- context 'group cluster' do
- let(:cluster) { create(:cluster, :group, user: current_user) }
- let(:namespace) { cluster.group }
-
- before do
- namespace.add_user(current_user, Gitlab::Access::MAINTAINER)
- end
-
- include_examples 'creates a management project'
- end
-
- context 'instance cluster' do
- let(:cluster) { create(:cluster, :instance, user: current_user) }
- let(:namespace) { create(:group) }
-
- before do
- stub_application_setting(instance_administrators_group: namespace)
-
- namespace.add_user(current_user, Gitlab::Access::MAINTAINER)
- end
-
- include_examples 'creates a management project'
- end
-
- describe 'error handling' do
- let(:project) { cluster.project }
-
- before do
- allow(Projects::CreateService).to receive(:new)
- .and_return(double(execute: project))
- end
-
- context 'project is invalid' do
- let(:errors) { double(full_messages: ["Error message"]) }
- let(:project) { instance_double(Project, errors: errors) }
-
- it { expect { subject }.to raise_error(described_class::CreateError, /Failed to create project/) }
- end
-
- context 'instance administrators group is missing' do
- let(:cluster) { create(:cluster, :instance) }
-
- it { expect { subject }.to raise_error(described_class::CreateError, /Instance administrators group not found/) }
- end
-
- context 'cluster is invalid' do
- before do
- allow(cluster).to receive(:update).and_return(false)
- end
-
- it { expect { subject }.to raise_error(described_class::CreateError, /Failed to update cluster/) }
- end
-
- context 'unknown cluster type' do
- before do
- allow(cluster).to receive(:cluster_type).and_return("unknown_type")
- end
-
- it { expect { subject }.to raise_error(NotImplementedError) }
- end
- end
-end
diff --git a/spec/services/container_expiration_policies/cleanup_service_spec.rb b/spec/services/container_expiration_policies/cleanup_service_spec.rb
index 746e3464427..c6faae7449d 100644
--- a/spec/services/container_expiration_policies/cleanup_service_spec.rb
+++ b/spec/services/container_expiration_policies/cleanup_service_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe ContainerExpirationPolicies::CleanupService do
- let_it_be(:repository, reload: true) { create(:container_repository) }
+ let_it_be(:repository, reload: true) { create(:container_repository, expiration_policy_started_at: 30.minutes.ago) }
let_it_be(:project) { repository.project }
let(:service) { described_class.new(repository) }
@@ -11,59 +11,35 @@ RSpec.describe ContainerExpirationPolicies::CleanupService do
describe '#execute' do
subject { service.execute }
- context 'with a successful cleanup tags service execution' do
- let(:cleanup_tags_service_params) { project.container_expiration_policy.policy_params.merge('container_expiration_policy' => true) }
- let(:cleanup_tags_service) { instance_double(Projects::ContainerRepository::CleanupTagsService) }
+ shared_examples 'cleaning up a container repository' do
+ context 'with a successful cleanup tags service execution' do
+ let(:cleanup_tags_service_params) { project.container_expiration_policy.policy_params.merge('container_expiration_policy' => true) }
+ let(:cleanup_tags_service) { instance_double(Projects::ContainerRepository::CleanupTagsService) }
- it 'completely clean up the repository' do
- expect(Projects::ContainerRepository::CleanupTagsService)
- .to receive(:new).with(project, nil, cleanup_tags_service_params).and_return(cleanup_tags_service)
- expect(cleanup_tags_service).to receive(:execute).with(repository).and_return(status: :success)
+ it 'completely clean up the repository' do
+ expect(Projects::ContainerRepository::CleanupTagsService)
+ .to receive(:new).with(project, nil, cleanup_tags_service_params).and_return(cleanup_tags_service)
+ expect(cleanup_tags_service).to receive(:execute).with(repository).and_return(status: :success)
- response = subject
+ response = subject
- aggregate_failures "checking the response and container repositories" do
- expect(response.success?).to eq(true)
- expect(response.payload).to include(cleanup_status: :finished, container_repository_id: repository.id)
- expect(ContainerRepository.waiting_for_cleanup.count).to eq(0)
- expect(repository.reload.cleanup_unscheduled?).to be_truthy
- expect(repository.expiration_policy_started_at).to eq(nil)
- expect(repository.expiration_policy_completed_at).not_to eq(nil)
+ aggregate_failures "checking the response and container repositories" do
+ expect(response.success?).to eq(true)
+ expect(response.payload).to include(cleanup_status: :finished, container_repository_id: repository.id)
+ expect(ContainerRepository.waiting_for_cleanup.count).to eq(0)
+ expect(repository.reload.cleanup_unscheduled?).to be_truthy
+ expect(repository.expiration_policy_completed_at).not_to eq(nil)
+ expect(repository.expiration_policy_started_at).not_to eq(nil)
+ end
end
end
- end
-
- context 'without a successful cleanup tags service execution' do
- let(:cleanup_tags_service_response) { { status: :error, message: 'timeout' } }
-
- before do
- expect(Projects::ContainerRepository::CleanupTagsService)
- .to receive(:new).and_return(double(execute: cleanup_tags_service_response))
- end
- it 'partially clean up the repository' do
- response = subject
+ context 'without a successful cleanup tags service execution' do
+ let(:cleanup_tags_service_response) { { status: :error, message: 'timeout' } }
- aggregate_failures "checking the response and container repositories" do
- expect(response.success?).to eq(true)
- expect(response.payload).to include(cleanup_status: :unfinished, container_repository_id: repository.id)
- expect(ContainerRepository.waiting_for_cleanup.count).to eq(1)
- expect(repository.reload.cleanup_unfinished?).to be_truthy
- expect(repository.expiration_policy_started_at).not_to eq(nil)
- expect(repository.expiration_policy_completed_at).to eq(nil)
- end
- end
-
- context 'with a truncated cleanup tags service response' do
- let(:cleanup_tags_service_response) do
- {
- status: :error,
- original_size: 1000,
- before_truncate_size: 800,
- after_truncate_size: 200,
- before_delete_size: 100,
- deleted_size: 100
- }
+ before do
+ expect(Projects::ContainerRepository::CleanupTagsService)
+ .to receive(:new).and_return(double(execute: cleanup_tags_service_response))
end
it 'partially clean up the repository' do
@@ -71,49 +47,179 @@ RSpec.describe ContainerExpirationPolicies::CleanupService do
aggregate_failures "checking the response and container repositories" do
expect(response.success?).to eq(true)
- expect(response.payload)
- .to include(
- cleanup_status: :unfinished,
- container_repository_id: repository.id,
- cleanup_tags_service_original_size: 1000,
- cleanup_tags_service_before_truncate_size: 800,
- cleanup_tags_service_after_truncate_size: 200,
- cleanup_tags_service_before_delete_size: 100,
- cleanup_tags_service_deleted_size: 100
- )
+ expect(response.payload).to include(cleanup_status: :unfinished, container_repository_id: repository.id)
expect(ContainerRepository.waiting_for_cleanup.count).to eq(1)
expect(repository.reload.cleanup_unfinished?).to be_truthy
expect(repository.expiration_policy_started_at).not_to eq(nil)
expect(repository.expiration_policy_completed_at).to eq(nil)
end
end
+
+ context 'with a truncated cleanup tags service response' do
+ let(:cleanup_tags_service_response) do
+ {
+ status: :error,
+ original_size: 1000,
+ before_truncate_size: 800,
+ after_truncate_size: 200,
+ before_delete_size: 100,
+ deleted_size: 100
+ }
+ end
+
+ it 'partially clean up the repository' do
+ response = subject
+
+ aggregate_failures "checking the response and container repositories" do
+ expect(response.success?).to eq(true)
+ expect(response.payload)
+ .to include(
+ cleanup_status: :unfinished,
+ container_repository_id: repository.id,
+ cleanup_tags_service_original_size: 1000,
+ cleanup_tags_service_before_truncate_size: 800,
+ cleanup_tags_service_after_truncate_size: 200,
+ cleanup_tags_service_before_delete_size: 100,
+ cleanup_tags_service_deleted_size: 100
+ )
+ expect(ContainerRepository.waiting_for_cleanup.count).to eq(1)
+ expect(repository.reload.cleanup_unfinished?).to be_truthy
+ expect(repository.expiration_policy_started_at).not_to eq(nil)
+ expect(repository.expiration_policy_completed_at).to eq(nil)
+ end
+ end
+ end
end
- end
- context 'with no repository' do
- let(:service) { described_class.new(nil) }
+ context 'with no repository' do
+ let(:service) { described_class.new(nil) }
+
+ it 'returns an error response' do
+ expect(subject.success?).to eq(false)
+ expect(subject.message).to eq('no repository')
+ end
+ end
- it 'returns an error response' do
- response = subject
+ context 'with an invalid policy' do
+ let(:policy) { repository.project.container_expiration_policy }
- expect(response.success?).to eq(false)
+ before do
+ policy.name_regex = nil
+ policy.enabled = true
+ repository.expiration_policy_cleanup_status = :cleanup_ongoing
+ end
+
+ it 'returns an error response' do
+ expect { subject }.to change { repository.expiration_policy_cleanup_status }.from('cleanup_ongoing').to('cleanup_unscheduled')
+ expect(subject.success?).to eq(false)
+ expect(subject.message).to eq('invalid policy')
+ expect(policy).not_to be_enabled
+ end
+ end
+
+ context 'with a network error' do
+ before do
+ expect(Projects::ContainerRepository::CleanupTagsService)
+ .to receive(:new).and_raise(Faraday::TimeoutError)
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(Faraday::TimeoutError)
+
+ expect(ContainerRepository.waiting_for_cleanup.count).to eq(1)
+ expect(repository.reload.cleanup_unfinished?).to be_truthy
+ expect(repository.expiration_policy_started_at).not_to eq(nil)
+ expect(repository.expiration_policy_completed_at).to eq(nil)
+ end
end
end
- context 'with a network error' do
+ context 'with loopless enabled' do
+ let(:policy) { repository.project.container_expiration_policy }
+
before do
- expect(Projects::ContainerRepository::CleanupTagsService)
- .to receive(:new).and_raise(Faraday::TimeoutError)
+ policy.update!(enabled: true)
+ policy.update_column(:next_run_at, 5.minutes.ago)
end
- it 'raises an error' do
- expect { subject }.to raise_error(Faraday::TimeoutError)
+ it_behaves_like 'cleaning up a container repository'
+
+ context 'next run scheduling' do
+ let_it_be_with_reload(:repository2) { create(:container_repository, project: project) }
+ let_it_be_with_reload(:repository3) { create(:container_repository, project: project) }
+
+ before do
+ cleanup_tags_service = instance_double(Projects::ContainerRepository::CleanupTagsService)
+ allow(Projects::ContainerRepository::CleanupTagsService)
+ .to receive(:new).and_return(cleanup_tags_service)
+ allow(cleanup_tags_service).to receive(:execute).and_return(status: :success)
+ end
+
+ shared_examples 'not scheduling the next run' do
+ it 'does not scheduled the next run' do
+ expect(policy).not_to receive(:schedule_next_run!)
+
+ expect { subject }.not_to change { policy.reload.next_run_at }
+ end
+ end
+
+ shared_examples 'scheduling the next run' do
+ it 'schedules the next run' do
+ expect(policy).to receive(:schedule_next_run!).and_call_original
+
+ expect { subject }.to change { policy.reload.next_run_at }
+ end
+ end
+
+ context 'with cleanups started_at before policy next_run_at' do
+ before do
+ ContainerRepository.update_all(expiration_policy_started_at: 10.minutes.ago)
+ end
+
+ it_behaves_like 'not scheduling the next run'
+ end
+
+ context 'with cleanups started_at around policy next_run_at' do
+ before do
+ repository3.update!(expiration_policy_started_at: policy.next_run_at + 10.minutes.ago)
+ end
- expect(ContainerRepository.waiting_for_cleanup.count).to eq(1)
- expect(repository.reload.cleanup_unfinished?).to be_truthy
- expect(repository.expiration_policy_started_at).not_to eq(nil)
- expect(repository.expiration_policy_completed_at).to eq(nil)
+ it_behaves_like 'not scheduling the next run'
+ end
+
+ context 'with only the current repository started_at before the policy next_run_at' do
+ before do
+ repository2.update!(expiration_policy_started_at: policy.next_run_at + 10.minutes)
+ repository3.update!(expiration_policy_started_at: policy.next_run_at + 12.minutes)
+ end
+
+ it_behaves_like 'scheduling the next run'
+ end
+
+ context 'with cleanups started_at after policy next_run_at' do
+ before do
+ ContainerRepository.update_all(expiration_policy_started_at: policy.next_run_at + 10.minutes)
+ end
+
+ it_behaves_like 'scheduling the next run'
+ end
+
+ context 'with a future policy next_run_at' do
+ before do
+ policy.update_column(:next_run_at, 5.minutes.from_now)
+ end
+
+ it_behaves_like 'not scheduling the next run'
+ end
end
end
+
+ context 'with loopless disabled' do
+ before do
+ stub_feature_flags(container_registry_expiration_policies_loopless: false)
+ end
+
+ it_behaves_like 'cleaning up a container repository'
+ end
end
end
diff --git a/spec/services/deployments/create_service_spec.rb b/spec/services/deployments/create_service_spec.rb
index 0bb5949ddb1..0f2a6ce32e1 100644
--- a/spec/services/deployments/create_service_spec.rb
+++ b/spec/services/deployments/create_service_spec.rb
@@ -21,7 +21,7 @@ RSpec.describe Deployments::CreateService do
expect(Deployments::UpdateEnvironmentWorker).to receive(:perform_async)
expect(Deployments::LinkMergeRequestWorker).to receive(:perform_async)
- expect(Deployments::ExecuteHooksWorker).to receive(:perform_async)
+ expect(Deployments::HooksWorker).to receive(:perform_async)
expect(service.execute).to be_persisted
end
@@ -37,7 +37,7 @@ RSpec.describe Deployments::CreateService do
expect(Deployments::UpdateEnvironmentWorker).not_to receive(:perform_async)
expect(Deployments::LinkMergeRequestWorker).not_to receive(:perform_async)
- expect(Deployments::ExecuteHooksWorker).not_to receive(:perform_async)
+ expect(Deployments::HooksWorker).not_to receive(:perform_async)
expect(service.execute).to be_persisted
end
@@ -57,7 +57,7 @@ RSpec.describe Deployments::CreateService do
expect(Deployments::UpdateEnvironmentWorker).not_to receive(:perform_async)
expect(Deployments::LinkMergeRequestWorker).not_to receive(:perform_async)
- expect(Deployments::ExecuteHooksWorker).not_to receive(:perform_async)
+ expect(Deployments::HooksWorker).not_to receive(:perform_async)
described_class.new(environment.reload, user, params).execute
end
diff --git a/spec/services/deployments/update_environment_service_spec.rb b/spec/services/deployments/update_environment_service_spec.rb
index 372805cc0fd..4d15258a186 100644
--- a/spec/services/deployments/update_environment_service_spec.rb
+++ b/spec/services/deployments/update_environment_service_spec.rb
@@ -33,7 +33,7 @@ RSpec.describe Deployments::UpdateEnvironmentService do
before do
allow(Deployments::LinkMergeRequestWorker).to receive(:perform_async)
- allow(Deployments::ExecuteHooksWorker).to receive(:perform_async)
+ allow(Deployments::HooksWorker).to receive(:perform_async)
job.success! # Create/Succeed deployment
end
@@ -161,6 +161,7 @@ RSpec.describe Deployments::UpdateEnvironmentService do
context 'when deployment was created by an external CD system' do
before do
deployment.update_column(:deployable_id, nil)
+ deployment.reload
end
it 'guesses the deployment tier' do
diff --git a/spec/services/discussions/capture_diff_note_positions_service_spec.rb b/spec/services/discussions/capture_diff_note_positions_service_spec.rb
index be53b02a4c1..25e5f549bee 100644
--- a/spec/services/discussions/capture_diff_note_positions_service_spec.rb
+++ b/spec/services/discussions/capture_diff_note_positions_service_spec.rb
@@ -55,7 +55,7 @@ RSpec.describe Discussions::CaptureDiffNotePositionsService do
context 'and position of the discussion changed on target branch head' do
it 'diff positions are created for the first notes of the discussions' do
- MergeRequests::MergeToRefService.new(project, merge_request.author).execute(merge_request)
+ MergeRequests::MergeToRefService.new(project: project, current_user: merge_request.author).execute(merge_request)
service.execute
verify_diff_note_position!(first_discussion_note, new_line: first_new_line)
diff --git a/spec/services/draft_notes/publish_service_spec.rb b/spec/services/draft_notes/publish_service_spec.rb
index f93622dc25a..2e1de367da3 100644
--- a/spec/services/draft_notes/publish_service_spec.rb
+++ b/spec/services/draft_notes/publish_service_spec.rb
@@ -202,7 +202,7 @@ RSpec.describe DraftNotes::PublishService do
expect(newrev).to be_present
# Generates new MR revision at DB level
- refresh = MergeRequests::RefreshService.new(project, user)
+ refresh = MergeRequests::RefreshService.new(project: project, current_user: user)
refresh.execute(oldrev, newrev, merge_request.source_branch_ref)
expect { publish(draft: draft) }.to change { Suggestion.count }.by(1)
diff --git a/spec/services/feature_flags/create_service_spec.rb b/spec/services/feature_flags/create_service_spec.rb
index 128fab114fe..2e0c162ebc1 100644
--- a/spec/services/feature_flags/create_service_spec.rb
+++ b/spec/services/feature_flags/create_service_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe FeatureFlags::CreateService do
let_it_be(:project) { create(:project) }
let_it_be(:developer) { create(:user) }
let_it_be(:reporter) { create(:user) }
+
let(:user) { developer }
before_all do
diff --git a/spec/services/feature_flags/destroy_service_spec.rb b/spec/services/feature_flags/destroy_service_spec.rb
index b35de02c628..ee30474873c 100644
--- a/spec/services/feature_flags/destroy_service_spec.rb
+++ b/spec/services/feature_flags/destroy_service_spec.rb
@@ -8,6 +8,7 @@ RSpec.describe FeatureFlags::DestroyService do
let_it_be(:project) { create(:project) }
let_it_be(:developer) { create(:user) }
let_it_be(:reporter) { create(:user) }
+
let(:user) { developer }
let!(:feature_flag) { create(:operations_feature_flag, project: project) }
diff --git a/spec/services/feature_flags/disable_service_spec.rb b/spec/services/feature_flags/disable_service_spec.rb
index de0f70bf552..4b2137be35c 100644
--- a/spec/services/feature_flags/disable_service_spec.rb
+++ b/spec/services/feature_flags/disable_service_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe FeatureFlags::DisableService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
+
let(:params) { {} }
let(:service) { described_class.new(project, user, params) }
diff --git a/spec/services/feature_flags/enable_service_spec.rb b/spec/services/feature_flags/enable_service_spec.rb
index 88c8028f6c5..c0008b1933f 100644
--- a/spec/services/feature_flags/enable_service_spec.rb
+++ b/spec/services/feature_flags/enable_service_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe FeatureFlags::EnableService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
+
let(:params) { {} }
let(:service) { described_class.new(project, user, params) }
diff --git a/spec/services/feature_flags/update_service_spec.rb b/spec/services/feature_flags/update_service_spec.rb
index 9639cf3081d..1a127a0d472 100644
--- a/spec/services/feature_flags/update_service_spec.rb
+++ b/spec/services/feature_flags/update_service_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe FeatureFlags::UpdateService do
let_it_be(:project) { create(:project) }
let_it_be(:developer) { create(:user) }
let_it_be(:reporter) { create(:user) }
+
let(:user) { developer }
let(:feature_flag) { create(:operations_feature_flag, project: project, active: true) }
diff --git a/spec/services/git/branch_hooks_service_spec.rb b/spec/services/git/branch_hooks_service_spec.rb
index 52df21897b9..19694a0a354 100644
--- a/spec/services/git/branch_hooks_service_spec.rb
+++ b/spec/services/git/branch_hooks_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Git::BranchHooksService do
+RSpec.describe Git::BranchHooksService, :clean_gitlab_redis_shared_state do
include RepoHelpers
include ProjectForksHelper
@@ -116,8 +116,6 @@ RSpec.describe Git::BranchHooksService do
allow_next_instance_of(Gitlab::Git::Diff) do |diff|
allow(diff).to receive(:new_path).and_return('.gitlab-ci.yml')
end
-
- allow(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event)
end
let!(:commit_author) { create(:user, email: sample_commit.author_email) }
@@ -127,23 +125,11 @@ RSpec.describe Git::BranchHooksService do
end
it 'tracks the event' do
- execute_service
-
- expect(Gitlab::UsageDataCounters::HLLRedisCounter)
- .to have_received(:track_event).with(*tracking_params)
- end
-
- context 'when the FF usage_data_unique_users_committing_ciconfigfile is disabled' do
- before do
- stub_feature_flags(usage_data_unique_users_committing_ciconfigfile: false)
- end
+ time = Time.zone.now
- it 'does not track the event' do
- execute_service
+ execute_service
- expect(Gitlab::UsageDataCounters::HLLRedisCounter)
- .not_to have_received(:track_event).with(*tracking_params)
- end
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: 'o_pipeline_authoring_unique_users_committing_ciconfigfile', start_date: time, end_date: time + 7.days)).to eq(1)
end
context 'when usage ping is disabled' do
@@ -155,7 +141,7 @@ RSpec.describe Git::BranchHooksService do
execute_service
expect(Gitlab::UsageDataCounters::HLLRedisCounter)
- .not_to have_received(:track_event).with(*tracking_params)
+ .not_to receive(:track_event).with(*tracking_params)
end
end
@@ -166,7 +152,7 @@ RSpec.describe Git::BranchHooksService do
execute_service
expect(Gitlab::UsageDataCounters::HLLRedisCounter)
- .not_to have_received(:track_event).with(*tracking_params)
+ .not_to receive(:track_event).with(*tracking_params)
end
end
@@ -179,7 +165,7 @@ RSpec.describe Git::BranchHooksService do
execute_service
expect(Gitlab::UsageDataCounters::HLLRedisCounter)
- .not_to have_received(:track_event).with(*tracking_params)
+ .not_to receive(:track_event).with(*tracking_params)
end
end
end
diff --git a/spec/services/git/wiki_push_service_spec.rb b/spec/services/git/wiki_push_service_spec.rb
index df9a48d7b1c..151c2a1d014 100644
--- a/spec/services/git/wiki_push_service_spec.rb
+++ b/spec/services/git/wiki_push_service_spec.rb
@@ -51,7 +51,7 @@ RSpec.describe Git::WikiPushService, services: true do
process_changes do
write_new_page
update_page(wiki_page_a.title)
- delete_page(wiki_page_b.page.path)
+ delete_page(wiki_page_b.page)
end
end
@@ -198,7 +198,7 @@ RSpec.describe Git::WikiPushService, services: true do
context 'when a page we do not know about has been deleted' do
def run_service
wiki_page = create(:wiki_page, wiki: wiki)
- process_changes { delete_page(wiki_page.page.path) }
+ process_changes { delete_page(wiki_page.page) }
end
it 'create a new meta-data record' do
@@ -350,8 +350,8 @@ RSpec.describe Git::WikiPushService, services: true do
git_wiki.update_page(page.path, title, 'markdown', 'Hey', commit_details)
end
- def delete_page(path)
- git_wiki.delete_page(path, commit_details)
+ def delete_page(page)
+ wiki.delete_page(page, 'commit message')
end
def commit_details
diff --git a/spec/services/groups/autocomplete_service_spec.rb b/spec/services/groups/autocomplete_service_spec.rb
new file mode 100644
index 00000000000..00d0ad3b347
--- /dev/null
+++ b/spec/services/groups/autocomplete_service_spec.rb
@@ -0,0 +1,119 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Groups::AutocompleteService do
+ let_it_be(:group, refind: true) { create(:group, :nested, :private, avatar: fixture_file_upload('spec/fixtures/dk.png')) }
+ let_it_be(:sub_group) { create(:group, :private, parent: group) }
+
+ let(:user) { create(:user) }
+
+ subject { described_class.new(group, user) }
+
+ before do
+ group.add_developer(user)
+ end
+
+ def expect_labels_to_equal(labels, expected_labels)
+ extract_title = lambda { |label| label['title'] }
+ expect(labels.map(&extract_title)).to match_array(expected_labels.map(&extract_title))
+ end
+
+ describe '#labels_as_hash' do
+ let!(:label1) { create(:group_label, group: group) }
+ let!(:label2) { create(:group_label, group: group) }
+ let!(:sub_group_label) { create(:group_label, group: sub_group) }
+ let!(:parent_group_label) { create(:group_label, group: group.parent) }
+
+ it 'returns labels from own group and ancestor groups' do
+ results = subject.labels_as_hash(nil)
+
+ expected_labels = [label1, label2, parent_group_label]
+
+ expect_labels_to_equal(results, expected_labels)
+ end
+ end
+
+ describe '#issues' do
+ let(:project) { create(:project, group: group) }
+ let(:sub_group_project) { create(:project, group: sub_group) }
+
+ let!(:project_issue) { create(:issue, project: project) }
+ let!(:sub_group_project_issue) { create(:issue, confidential: true, project: sub_group_project) }
+
+ it 'returns issues in group and subgroups' do
+ issues = subject.issues
+
+ expect(issues.map(&:iid)).to contain_exactly(project_issue.iid, sub_group_project_issue.iid)
+ expect(issues.map(&:title)).to contain_exactly(project_issue.title, sub_group_project_issue.title)
+ end
+
+ it 'returns only confidential issues if confidential_only is true' do
+ issues = subject.issues(confidential_only: true)
+
+ expect(issues.map(&:iid)).to contain_exactly(sub_group_project_issue.iid)
+ expect(issues.map(&:title)).to contain_exactly(sub_group_project_issue.title)
+ end
+ end
+
+ describe '#merge_requests' do
+ let(:project) { create(:project, :repository, group: group) }
+ let(:sub_group_project) { create(:project, :repository, group: sub_group) }
+
+ let!(:project_mr) { create(:merge_request, source_project: project) }
+ let!(:sub_group_project_mr) { create(:merge_request, source_project: sub_group_project) }
+
+ it 'returns merge requests in group and subgroups' do
+ expect(subject.merge_requests.map(&:iid)).to contain_exactly(project_mr.iid, sub_group_project_mr.iid)
+ expect(subject.merge_requests.map(&:title)).to contain_exactly(project_mr.title, sub_group_project_mr.title)
+ end
+ end
+
+ describe '#milestones' do
+ let!(:group_milestone) { create(:milestone, group: group) }
+ let!(:subgroup_milestone) { create(:milestone, group: sub_group) }
+
+ before do
+ sub_group.add_maintainer(user)
+ end
+
+ context 'when group is public' do
+ let(:public_group) { create(:group, :public) }
+ let(:public_subgroup) { create(:group, :public, parent: public_group) }
+
+ before do
+ group_milestone.update!(group: public_group)
+ subgroup_milestone.update!(group: public_subgroup)
+ end
+
+ it 'returns milestones from groups and subgroups' do
+ subject = described_class.new(public_subgroup, user)
+
+ expect(subject.milestones.map(&:iid)).to contain_exactly(group_milestone.iid, subgroup_milestone.iid)
+ expect(subject.milestones.map(&:title)).to contain_exactly(group_milestone.title, subgroup_milestone.title)
+ end
+ end
+
+ it 'returns milestones from group' do
+ expect(subject.milestones.map(&:iid)).to contain_exactly(group_milestone.iid)
+ expect(subject.milestones.map(&:title)).to contain_exactly(group_milestone.title)
+ end
+
+ it 'returns milestones from groups and subgroups' do
+ milestones = described_class.new(sub_group, user).milestones
+
+ expect(milestones.map(&:iid)).to contain_exactly(group_milestone.iid, subgroup_milestone.iid)
+ expect(milestones.map(&:title)).to contain_exactly(group_milestone.title, subgroup_milestone.title)
+ end
+
+ it 'returns only milestones that user can read' do
+ user = create(:user)
+ sub_group.add_guest(user)
+
+ milestones = described_class.new(sub_group, user).milestones
+
+ expect(milestones.map(&:iid)).to contain_exactly(subgroup_milestone.iid)
+ expect(milestones.map(&:title)).to contain_exactly(subgroup_milestone.title)
+ end
+ end
+end
diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb
index f0cd42c1948..dca5497de06 100644
--- a/spec/services/groups/create_service_spec.rb
+++ b/spec/services/groups/create_service_spec.rb
@@ -164,9 +164,9 @@ RSpec.describe Groups::CreateService, '#execute' do
let!(:instance_integration) { create(:prometheus_service, :instance, api_url: 'https://prometheus.instance.com/') }
it 'creates a service from the instance-level integration' do
- expect(created_group.services.count).to eq(1)
- expect(created_group.services.first.api_url).to eq(instance_integration.api_url)
- expect(created_group.services.first.inherit_from_id).to eq(instance_integration.id)
+ expect(created_group.integrations.count).to eq(1)
+ expect(created_group.integrations.first.api_url).to eq(instance_integration.api_url)
+ expect(created_group.integrations.first.inherit_from_id).to eq(instance_integration.id)
end
context 'with an active group-level integration' do
@@ -179,9 +179,9 @@ RSpec.describe Groups::CreateService, '#execute' do
end
it 'creates a service from the group-level integration' do
- expect(created_group.services.count).to eq(1)
- expect(created_group.services.first.api_url).to eq(group_integration.api_url)
- expect(created_group.services.first.inherit_from_id).to eq(group_integration.id)
+ expect(created_group.integrations.count).to eq(1)
+ expect(created_group.integrations.first.api_url).to eq(group_integration.api_url)
+ expect(created_group.integrations.first.inherit_from_id).to eq(group_integration.id)
end
context 'with an active subgroup' do
@@ -194,9 +194,9 @@ RSpec.describe Groups::CreateService, '#execute' do
end
it 'creates a service from the subgroup-level integration' do
- expect(created_group.services.count).to eq(1)
- expect(created_group.services.first.api_url).to eq(subgroup_integration.api_url)
- expect(created_group.services.first.inherit_from_id).to eq(subgroup_integration.id)
+ expect(created_group.integrations.count).to eq(1)
+ expect(created_group.integrations.first.api_url).to eq(subgroup_integration.api_url)
+ expect(created_group.integrations.first.inherit_from_id).to eq(subgroup_integration.id)
end
end
end
diff --git a/spec/services/groups/open_issues_count_service_spec.rb b/spec/services/groups/open_issues_count_service_spec.rb
index 740e9846119..fca09bfdebe 100644
--- a/spec/services/groups/open_issues_count_service_spec.rb
+++ b/spec/services/groups/open_issues_count_service_spec.rb
@@ -57,4 +57,15 @@ RSpec.describe Groups::OpenIssuesCountService, :use_clean_rails_memory_store_cac
it_behaves_like 'a counter caching service with threshold'
end
end
+
+ describe '#clear_all_cache_keys' do
+ it 'calls `Rails.cache.delete` with the correct keys' do
+ expect(Rails.cache).to receive(:delete)
+ .with(['groups', 'open_issues_count_service', 1, group.id, described_class::PUBLIC_COUNT_KEY])
+ expect(Rails.cache).to receive(:delete)
+ .with(['groups', 'open_issues_count_service', 1, group.id, described_class::TOTAL_COUNT_KEY])
+
+ subject.clear_all_cache_keys
+ end
+ end
end
diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb
index 3a1197970f4..2fbd5eeef5f 100644
--- a/spec/services/groups/transfer_service_spec.rb
+++ b/spec/services/groups/transfer_service_spec.rb
@@ -240,6 +240,7 @@ RSpec.describe Groups::TransferService do
end
context 'when the group is allowed to be transferred' do
+ let_it_be(:new_parent_group, reload: true) { create(:group, :public) }
let_it_be(:new_parent_group_integration) { create(:slack_service, group: new_parent_group, project: nil, webhook: 'http://new-group.slack.com') }
before do
@@ -273,17 +274,16 @@ RSpec.describe Groups::TransferService do
end
context 'with a group integration' do
- let_it_be(:instance_integration) { create(:slack_service, :instance, webhook: 'http://project.slack.com') }
-
- let(:new_created_integration) { Service.find_by(group: group) }
+ let(:new_created_integration) { Integration.find_by(group: group) }
context 'with an inherited integration' do
+ let_it_be(:instance_integration) { create(:slack_service, :instance, webhook: 'http://project.slack.com') }
let_it_be(:group_integration) { create(:slack_service, group: group, project: nil, webhook: 'http://group.slack.com', inherit_from_id: instance_integration.id) }
it 'replaces inherited integrations', :aggregate_failures do
expect(new_created_integration.webhook).to eq(new_parent_group_integration.webhook)
expect(PropagateIntegrationWorker).to have_received(:perform_async).with(new_created_integration.id)
- expect(Service.count).to eq(3)
+ expect(Integration.count).to eq(3)
end
end
@@ -603,6 +603,7 @@ RSpec.describe Groups::TransferService do
create(:group_member, :owner, group: new_parent_group, user: user)
create(:group, :private, parent: group, require_two_factor_authentication: true)
group.update!(require_two_factor_authentication: true)
+ new_parent_group.reload # make sure traversal_ids are reloaded
end
it 'does not update group two factor authentication setting' do
diff --git a/spec/services/import/gitlab_projects/create_project_from_remote_file_service_spec.rb b/spec/services/import/gitlab_projects/create_project_from_remote_file_service_spec.rb
new file mode 100644
index 00000000000..3c461c91ff0
--- /dev/null
+++ b/spec/services/import/gitlab_projects/create_project_from_remote_file_service_spec.rb
@@ -0,0 +1,182 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Import::GitlabProjects::CreateProjectFromRemoteFileService do
+ let(:remote_url) { 'https://external.file.path/file' }
+
+ let(:params) do
+ {
+ path: 'path',
+ namespace: user.namespace,
+ name: 'name',
+ remote_import_url: remote_url
+ }
+ end
+
+ let_it_be(:user) { create(:user) }
+
+ subject { described_class.new(user, params) }
+
+ it 'creates a project and returns a successful response' do
+ stub_headers_for(remote_url, {
+ 'content-type' => 'application/gzip',
+ 'content-length' => '10'
+ })
+
+ response = nil
+ expect { response = subject.execute }
+ .to change(Project, :count).by(1)
+
+ expect(response).to be_success
+ expect(response.http_status).to eq(:ok)
+ expect(response.payload).to be_instance_of(Project)
+ expect(response.payload.name).to eq('name')
+ expect(response.payload.path).to eq('path')
+ expect(response.payload.namespace).to eq(user.namespace)
+ end
+
+ context 'when the file url is invalid' do
+ it 'returns an erred response with the reason of the failure' do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: false)
+
+ params[:remote_import_url] = 'https://localhost/file'
+
+ response = nil
+ expect { response = subject.execute }
+ .not_to change(Project, :count)
+
+ expect(response).not_to be_success
+ expect(response.http_status).to eq(:bad_request)
+ expect(response.message).to eq('Requests to localhost are not allowed')
+ end
+ end
+
+ context 'validate file type' do
+ it 'returns erred response when the file type is not informed' do
+ stub_headers_for(remote_url, { 'content-length' => '10' })
+
+ response = nil
+ expect { response = subject.execute }
+ .not_to change(Project, :count)
+
+ expect(response).not_to be_success
+ expect(response.http_status).to eq(:bad_request)
+ expect(response.message)
+ .to eq("Missing 'ContentType' header")
+ end
+
+ it 'returns erred response when the file type is not allowed' do
+ stub_headers_for(remote_url, {
+ 'content-type' => 'application/js',
+ 'content-length' => '10'
+ })
+
+ response = nil
+ expect { response = subject.execute }
+ .not_to change(Project, :count)
+
+ expect(response).not_to be_success
+ expect(response.http_status).to eq(:bad_request)
+ expect(response.message)
+ .to eq("Remote file content type 'application/js' not allowed. (Allowed content types: application/gzip)")
+ end
+ end
+
+ context 'validate content type' do
+ it 'returns erred response when the file size is not informed' do
+ stub_headers_for(remote_url, { 'content-type' => 'application/gzip' })
+
+ response = nil
+ expect { response = subject.execute }
+ .not_to change(Project, :count)
+
+ expect(response).not_to be_success
+ expect(response.http_status).to eq(:bad_request)
+ expect(response.message)
+ .to eq("Missing 'ContentLength' header")
+ end
+
+ it 'returns error response when the file size is a text' do
+ stub_headers_for(remote_url, {
+ 'content-type' => 'application/gzip',
+ 'content-length' => 'some text'
+ })
+
+ response = nil
+ expect { response = subject.execute }
+ .not_to change(Project, :count)
+
+ expect(response).not_to be_success
+ expect(response.http_status).to eq(:bad_request)
+ expect(response.message)
+ .to eq("Missing 'ContentLength' header")
+ end
+
+ it 'returns erred response when the file is larger then allowed' do
+ stub_headers_for(remote_url, {
+ 'content-type' => 'application/gzip',
+ 'content-length' => 11.gigabytes.to_s
+ })
+
+ response = nil
+ expect { response = subject.execute }
+ .not_to change(Project, :count)
+
+ expect(response).not_to be_success
+ expect(response.http_status).to eq(:bad_request)
+ expect(response.message)
+ .to eq('Remote file larger than limit. (limit 10 GB)')
+ end
+ end
+
+ context 'when required parameters are not provided' do
+ let(:params) { {} }
+
+ it 'returns an erred response with the reason of the failure' do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: false)
+
+ response = nil
+ expect { response = subject.execute }
+ .not_to change(Project, :count)
+
+ expect(response).not_to be_success
+ expect(response.http_status).to eq(:bad_request)
+ expect(response.message).to eq("Parameter 'path' is required")
+
+ expect(subject.errors.full_messages).to match_array([
+ "Missing 'ContentLength' header",
+ "Missing 'ContentType' header",
+ "Parameter 'namespace' is required",
+ "Parameter 'path' is required",
+ "Parameter 'remote_import_url' is required"
+ ])
+ end
+ end
+
+ context 'when the project is invalid' do
+ it 'returns an erred response with the reason of the failure' do
+ create(:project, namespace: user.namespace, path: 'path')
+
+ stub_headers_for(remote_url, {
+ 'content-type' => 'application/gzip',
+ 'content-length' => '10'
+ })
+
+ response = nil
+ expect { response = subject.execute }
+ .not_to change(Project, :count)
+
+ expect(response).not_to be_success
+ expect(response.http_status).to eq(:bad_request)
+ expect(response.message).to eq('Path has already been taken')
+ end
+ end
+
+ def stub_headers_for(url, headers = {})
+ allow(Gitlab::HTTP)
+ .to receive(:head)
+ .with(url)
+ .and_return(double(headers: headers))
+ end
+end
diff --git a/spec/services/import/gitlab_projects/create_project_from_uploaded_file_service_spec.rb b/spec/services/import/gitlab_projects/create_project_from_uploaded_file_service_spec.rb
new file mode 100644
index 00000000000..a0e04a9a696
--- /dev/null
+++ b/spec/services/import/gitlab_projects/create_project_from_uploaded_file_service_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Import::GitlabProjects::CreateProjectFromUploadedFileService do
+ let(:file_upload) do
+ fixture_file_upload('spec/features/projects/import_export/test_project_export.tar.gz')
+ end
+
+ let(:params) do
+ {
+ path: 'path',
+ namespace: user.namespace,
+ name: 'name',
+ file: file_upload
+ }
+ end
+
+ let_it_be(:user) { create(:user) }
+
+ subject { described_class.new(user, params) }
+
+ it 'creates a project and returns a successful response' do
+ response = nil
+ expect { response = subject.execute }
+ .to change(Project, :count).by(1)
+
+ expect(response).to be_success
+ expect(response.http_status).to eq(:ok)
+ expect(response.payload).to be_instance_of(Project)
+ expect(response.payload.name).to eq('name')
+ expect(response.payload.path).to eq('path')
+ expect(response.payload.namespace).to eq(user.namespace)
+ end
+
+ context 'when required parameters are not provided' do
+ let(:params) { {} }
+
+ it 'returns an erred response with the reason of the failure' do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: false)
+
+ response = nil
+ expect { response = subject.execute }
+ .not_to change(Project, :count)
+
+ expect(response).not_to be_success
+ expect(response.http_status).to eq(:bad_request)
+ expect(response.message).to eq("Parameter 'path' is required")
+
+ expect(subject.errors.full_messages).to match_array([
+ "Parameter 'namespace' is required",
+ "Parameter 'path' is required",
+ "Parameter 'file' is required"
+ ])
+ end
+ end
+
+ context 'when the project is invalid' do
+ it 'returns an erred response with the reason of the failure' do
+ create(:project, namespace: user.namespace, path: 'path')
+
+ response = nil
+ expect { response = subject.execute }
+ .not_to change(Project, :count)
+
+ expect(response).not_to be_success
+ expect(response.http_status).to eq(:bad_request)
+ expect(response.message).to eq('Path has already been taken')
+ end
+ end
+end
diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb
index c749f282cd3..dfdfb57111c 100644
--- a/spec/services/issuable/bulk_update_service_spec.rb
+++ b/spec/services/issuable/bulk_update_service_spec.rb
@@ -101,6 +101,22 @@ RSpec.describe Issuable::BulkUpdateService do
end
end
+ shared_examples 'scheduling cached group count clear' do
+ it 'schedules worker' do
+ expect(Issuables::ClearGroupsIssueCounterWorker).to receive(:perform_async)
+
+ bulk_update(issuables, params)
+ end
+ end
+
+ shared_examples 'not scheduling cached group count clear' do
+ it 'does not schedule worker' do
+ expect(Issuables::ClearGroupsIssueCounterWorker).not_to receive(:perform_async)
+
+ bulk_update(issuables, params)
+ end
+ end
+
context 'with issuables at a project level' do
let(:parent) { project }
@@ -131,6 +147,11 @@ RSpec.describe Issuable::BulkUpdateService do
expect(project.issues.opened).to be_empty
expect(project.issues.closed).not_to be_empty
end
+
+ it_behaves_like 'scheduling cached group count clear' do
+ let(:issuables) { issues }
+ let(:params) { { state_event: 'close' } }
+ end
end
describe 'reopen issues' do
@@ -149,6 +170,11 @@ RSpec.describe Issuable::BulkUpdateService do
expect(project.issues.closed).to be_empty
expect(project.issues.opened).not_to be_empty
end
+
+ it_behaves_like 'scheduling cached group count clear' do
+ let(:issuables) { issues }
+ let(:params) { { state_event: 'reopen' } }
+ end
end
describe 'updating merge request assignee' do
@@ -231,6 +257,10 @@ RSpec.describe Issuable::BulkUpdateService do
let(:milestone) { create(:milestone, project: project) }
it_behaves_like 'updates milestones'
+
+ it_behaves_like 'not scheduling cached group count clear' do
+ let(:params) { { milestone_id: milestone.id } }
+ end
end
describe 'updating labels' do
diff --git a/spec/services/issuable/common_system_notes_service_spec.rb b/spec/services/issuable/common_system_notes_service_spec.rb
index a988ab81754..1426ef2a1f6 100644
--- a/spec/services/issuable/common_system_notes_service_spec.rb
+++ b/spec/services/issuable/common_system_notes_service_spec.rb
@@ -23,7 +23,7 @@ RSpec.describe Issuable::CommonSystemNotesService do
end
it 'creates a resource label event' do
- described_class.new(project, user).execute(issuable, old_labels: [])
+ described_class.new(project: project, current_user: user).execute(issuable, old_labels: [])
event = issuable.reload.resource_label_events.last
expect(event).not_to be_nil
@@ -66,7 +66,7 @@ RSpec.describe Issuable::CommonSystemNotesService do
context 'on issuable create' do
let(:issuable) { build(:issue, project: project) }
- subject { described_class.new(project, user).execute(issuable, old_labels: [], is_update: false) }
+ subject { described_class.new(project: project, current_user: user).execute(issuable, old_labels: [], is_update: false) }
it 'does not create system note for title and description' do
issuable.save!
diff --git a/spec/services/issuable/destroy_label_links_service_spec.rb b/spec/services/issuable/destroy_label_links_service_spec.rb
new file mode 100644
index 00000000000..bbc69e266c9
--- /dev/null
+++ b/spec/services/issuable/destroy_label_links_service_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Issuable::DestroyLabelLinksService do
+ describe '#execute' do
+ context 'when target is an Issue' do
+ let_it_be(:target) { create(:issue) }
+
+ it_behaves_like 'service deleting label links of an issuable'
+ end
+
+ context 'when target is a MergeRequest' do
+ let_it_be(:target) { create(:merge_request) }
+
+ it_behaves_like 'service deleting label links of an issuable'
+ end
+ end
+end
diff --git a/spec/services/issuable/destroy_service_spec.rb b/spec/services/issuable/destroy_service_spec.rb
index fa4902e5237..c72d48d5b77 100644
--- a/spec/services/issuable/destroy_service_spec.rb
+++ b/spec/services/issuable/destroy_service_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Issuable::DestroyService do
let(:group) { create(:group, :public) }
let(:project) { create(:project, :public, group: group) }
- subject(:service) { described_class.new(project, user) }
+ subject(:service) { described_class.new(project: project, current_user: user) }
describe '#execute' do
context 'when issuable is an issue' do
@@ -31,6 +31,10 @@ RSpec.describe Issuable::DestroyService do
it_behaves_like 'service deleting todos' do
let(:issuable) { issue }
end
+
+ it_behaves_like 'service deleting label links' do
+ let(:issuable) { issue }
+ end
end
context 'when issuable is a merge request' do
@@ -54,6 +58,10 @@ RSpec.describe Issuable::DestroyService do
it_behaves_like 'service deleting todos' do
let(:issuable) { merge_request }
end
+
+ it_behaves_like 'service deleting label links' do
+ let(:issuable) { merge_request }
+ end
end
end
end
diff --git a/spec/services/issue_rebalancing_service_spec.rb b/spec/services/issue_rebalancing_service_spec.rb
index 7b3d4213b24..1c7f74264b7 100644
--- a/spec/services/issue_rebalancing_service_spec.rb
+++ b/spec/services/issue_rebalancing_service_spec.rb
@@ -3,31 +3,35 @@
require 'spec_helper'
RSpec.describe IssueRebalancingService do
- let_it_be(:project) { create(:project) }
+ let_it_be(:project, reload: true) { create(:project) }
let_it_be(:user) { project.creator }
let_it_be(:start) { RelativePositioning::START_POSITION }
let_it_be(:max_pos) { RelativePositioning::MAX_POSITION }
let_it_be(:min_pos) { RelativePositioning::MIN_POSITION }
let_it_be(:clump_size) { 300 }
- let_it_be(:unclumped) do
- (0..clump_size).to_a.map do |i|
+ let_it_be(:unclumped, reload: true) do
+ (1..clump_size).to_a.map do |i|
create(:issue, project: project, author: user, relative_position: start + (1024 * i))
end
end
- let_it_be(:end_clump) do
- (0..clump_size).to_a.map do |i|
+ let_it_be(:end_clump, reload: true) do
+ (1..clump_size).to_a.map do |i|
create(:issue, project: project, author: user, relative_position: max_pos - i)
end
end
- let_it_be(:start_clump) do
- (0..clump_size).to_a.map do |i|
+ let_it_be(:start_clump, reload: true) do
+ (1..clump_size).to_a.map do |i|
create(:issue, project: project, author: user, relative_position: min_pos + i)
end
end
+ before do
+ stub_feature_flags(issue_rebalancing_with_retry: false)
+ end
+
def issues_in_position_order
project.reload.issues.reorder(relative_position: :asc).to_a
end
@@ -101,19 +105,70 @@ RSpec.describe IssueRebalancingService do
end
end
+ shared_examples 'rebalancing is retried on statement timeout exceptions' do
+ subject { described_class.new(project.issues.first) }
+
+ it 'retries update statement' do
+ call_count = 0
+ allow(subject).to receive(:run_update_query) do
+ call_count += 1
+ if call_count < 13
+ raise(ActiveRecord::QueryCanceled)
+ else
+ call_count = 0 if call_count == 13 + 16 # 16 = 17 sub-batches - 1 call that succeeded as part of 5th batch
+ true
+ end
+ end
+
+ # call math:
+ # batches start at 100 and are split in half after every 3 retries if ActiveRecord::StatementTimeout exception is raised.
+ # We raise ActiveRecord::StatementTimeout exception for 13 calls:
+ # 1. 100 => 3 calls
+ # 2. 100/2=50 => 3 calls + 3 above = 6 calls, raise ActiveRecord::StatementTimeout
+ # 3. 50/2=25 => 3 calls + 6 above = 9 calls, raise ActiveRecord::StatementTimeout
+ # 4. 25/2=12 => 3 calls + 9 above = 12 calls, raise ActiveRecord::StatementTimeout
+ # 5. 12/2=6 => 1 call + 12 above = 13 calls, run successfully
+ #
+ # so out of 100 elements we created batches of 6 items => 100/6 = 17 sub-batches of 6 or less elements
+ #
+ # project.issues.count: 900 issues, so 9 batches of 100 => 9 * (13+16) = 261
+ expect(subject).to receive(:update_positions).exactly(261).times.and_call_original
+
+ subject.execute
+ end
+ end
+
context 'when issue_rebalancing_optimization feature flag is on' do
before do
stub_feature_flags(issue_rebalancing_optimization: true)
end
it_behaves_like 'IssueRebalancingService shared examples'
+
+ context 'when issue_rebalancing_with_retry feature flag is on' do
+ before do
+ stub_feature_flags(issue_rebalancing_with_retry: true)
+ end
+
+ it_behaves_like 'IssueRebalancingService shared examples'
+ it_behaves_like 'rebalancing is retried on statement timeout exceptions'
+ end
end
- context 'when issue_rebalancing_optimization feature flag is on' do
+ context 'when issue_rebalancing_optimization feature flag is off' do
before do
stub_feature_flags(issue_rebalancing_optimization: false)
end
it_behaves_like 'IssueRebalancingService shared examples'
+
+ context 'when issue_rebalancing_with_retry feature flag is on' do
+ before do
+ stub_feature_flags(issue_rebalancing_with_retry: true)
+ end
+
+ it_behaves_like 'IssueRebalancingService shared examples'
+ it_behaves_like 'rebalancing is retried on statement timeout exceptions'
+ end
end
end
diff --git a/spec/services/issues/after_create_service_spec.rb b/spec/services/issues/after_create_service_spec.rb
index bc9be3211d3..6b720d6e687 100644
--- a/spec/services/issues/after_create_service_spec.rb
+++ b/spec/services/issues/after_create_service_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe Issues::AfterCreateService do
let_it_be(:milestone) { create(:milestone, project: project) }
let_it_be(:issue) { create(:issue, project: project, author: current_user, milestone: milestone, assignee_ids: [assignee.id]) }
- subject(:after_create_service) { described_class.new(project, current_user) }
+ subject(:after_create_service) { described_class.new(project: project, current_user: current_user) }
describe '#execute' do
it 'creates a pending todo for new assignee' do
diff --git a/spec/services/issues/build_service_spec.rb b/spec/services/issues/build_service_spec.rb
index 80fe2474ecd..3f506ec58b0 100644
--- a/spec/services/issues/build_service_spec.rb
+++ b/spec/services/issues/build_service_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'spec_helper.rb'
+require 'spec_helper'
RSpec.describe Issues::BuildService do
let_it_be(:project) { create(:project, :repository) }
@@ -15,7 +15,7 @@ RSpec.describe Issues::BuildService do
end
def build_issue(issue_params = {})
- described_class.new(project, user, issue_params).execute
+ described_class.new(project: project, current_user: user, params: issue_params).execute
end
context 'for a single discussion' do
@@ -41,7 +41,7 @@ RSpec.describe Issues::BuildService do
describe '#items_for_discussions' do
it 'has an item for each discussion' do
create(:diff_note_on_merge_request, noteable: merge_request, project: merge_request.source_project, line_number: 13)
- service = described_class.new(project, user, merge_request_to_resolve_discussions_of: merge_request.iid)
+ service = described_class.new(project: project, current_user: user, params: { merge_request_to_resolve_discussions_of: merge_request.iid })
service.execute
@@ -50,7 +50,7 @@ RSpec.describe Issues::BuildService do
end
describe '#item_for_discussion' do
- let(:service) { described_class.new(project, user, merge_request_to_resolve_discussions_of: merge_request.iid) }
+ let(:service) { described_class.new(project: project, current_user: user, params: { merge_request_to_resolve_discussions_of: merge_request.iid }) }
it 'mentions the author of the note' do
discussion = create(:diff_note_on_merge_request, author: create(:user, username: 'author')).to_discussion
@@ -184,9 +184,9 @@ RSpec.describe Issues::BuildService do
end
it 'cannot set invalid type' do
- expect do
- build_issue(issue_type: 'invalid type')
- end.to raise_error(ArgumentError, "'invalid type' is not a valid issue_type")
+ issue = build_issue(issue_type: 'invalid type')
+
+ expect(issue).to be_issue
end
end
end
diff --git a/spec/services/issues/clone_service_spec.rb b/spec/services/issues/clone_service_spec.rb
index 44180a322ca..abbcb1c1d48 100644
--- a/spec/services/issues/clone_service_spec.rb
+++ b/spec/services/issues/clone_service_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe Issues::CloneService do
let(:with_notes) { false }
subject(:clone_service) do
- described_class.new(old_project, user)
+ described_class.new(project: old_project, current_user: user)
end
shared_context 'user can clone issue' do
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index 3cf45143594..8950bdd465f 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -3,24 +3,52 @@
require 'spec_helper'
RSpec.describe Issues::CloseService do
- let(:project) { create(:project, :repository) }
- let(:user) { create(:user, email: "user@example.com") }
- let(:user2) { create(:user, email: "user2@example.com") }
- let(:guest) { create(:user) }
- let(:issue) { create(:issue, title: "My issue", project: project, assignees: [user2], author: create(:user)) }
+ subject(:close_issue) { described_class.new(project: project, current_user: user).close_issue(issue) }
+
+ let_it_be(:project, refind: true) { create(:project, :repository) }
+ let_it_be(:label1) { create(:label, project: project) }
+ let_it_be(:label2) { create(:label, project: project, remove_on_close: true) }
+ let_it_be(:author) { create(:user) }
+ let_it_be(:user) { create(:user, email: "user@example.com") }
+ let_it_be(:user2) { create(:user, email: "user2@example.com") }
+ let_it_be(:guest) { create(:user) }
+ let_it_be(:closing_merge_request) { create(:merge_request, source_project: project) }
+
let(:external_issue) { ExternalIssue.new('JIRA-123', project) }
- let(:closing_merge_request) { create(:merge_request, source_project: project) }
- let(:closing_commit) { create(:commit, project: project) }
- let!(:todo) { create(:todo, :assigned, user: user, project: project, target: issue, author: user2) }
+ let!(:issue) { create(:issue, title: "My issue", project: project, assignees: [user2], author: author) }
- before do
+ before_all do
project.add_maintainer(user)
project.add_developer(user2)
project.add_guest(guest)
end
+ shared_examples 'removes labels marked for removal from issue when closed' do
+ before do
+ issue.update!(label_ids: [label1.id, label2.id])
+ end
+
+ it 'removes labels marked for removal' do
+ expect do
+ close_issue
+ end.to change { issue.reload.label_ids }.from(containing_exactly(label1.id, label2.id)).to(containing_exactly(label1.id))
+ end
+
+ it 'creates system notes for the removed labels' do
+ expect do
+ close_issue
+ end.to change(ResourceLabelEvent, :count).by(1)
+
+ expect(ResourceLabelEvent.last.slice(:action, :issue_id, :label_id)).to eq(
+ 'action' => 'remove',
+ 'issue_id' => issue.id,
+ 'label_id' => label2.id
+ )
+ end
+ end
+
describe '#execute' do
- let(:service) { described_class.new(project, user) }
+ let(:service) { described_class.new(project: project, current_user: user) }
it 'checks if the user is authorized to update the issue' do
expect(service).to receive(:can?).with(user, :update_issue, issue)
@@ -87,18 +115,18 @@ RSpec.describe Issues::CloseService do
project.reload
expect(project.external_issue_tracker).to receive(:close_issue)
- described_class.new(project, user).close_issue(external_issue)
+ described_class.new(project: project, current_user: user).close_issue(external_issue)
end
end
- context 'with innactive external issue tracker supporting close_issue' do
+ context 'with inactive external issue tracker supporting close_issue' do
let!(:external_issue_tracker) { create(:jira_service, project: project, active: false) }
it 'does not close the issue on the external issue tracker' do
project.reload
expect(project.external_issue_tracker).not_to receive(:close_issue)
- described_class.new(project, user).close_issue(external_issue)
+ described_class.new(project: project, current_user: user).close_issue(external_issue)
end
end
@@ -109,7 +137,7 @@ RSpec.describe Issues::CloseService do
project.reload
expect(project.external_issue_tracker).not_to receive(:close_issue)
- described_class.new(project, user).close_issue(external_issue)
+ described_class.new(project: project, current_user: user).close_issue(external_issue)
end
end
end
@@ -117,10 +145,12 @@ RSpec.describe Issues::CloseService do
context "closed by a merge request", :sidekiq_might_not_need_inline do
subject(:close_issue) do
perform_enqueued_jobs do
- described_class.new(project, user).close_issue(issue, closed_via: closing_merge_request)
+ described_class.new(project: project, current_user: user).close_issue(issue, closed_via: closing_merge_request)
end
end
+ it_behaves_like 'removes labels marked for removal from issue when closed'
+
it 'mentions closure via a merge request' do
close_issue
@@ -184,10 +214,18 @@ RSpec.describe Issues::CloseService do
end
context "closed by a commit", :sidekiq_might_not_need_inline do
- it 'mentions closure via a commit' do
+ subject(:close_issue) do
perform_enqueued_jobs do
- described_class.new(project, user).close_issue(issue, closed_via: closing_commit)
+ described_class.new(project: project, current_user: user).close_issue(issue, closed_via: closing_commit)
end
+ end
+
+ let(:closing_commit) { create(:commit, project: project) }
+
+ it_behaves_like 'removes labels marked for removal from issue when closed'
+
+ it 'mentions closure via a commit' do
+ close_issue
email = ActionMailer::Base.deliveries.last
@@ -199,9 +237,8 @@ RSpec.describe Issues::CloseService do
context 'when user cannot read the commit' do
it 'does not mention the commit id' do
project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED)
- perform_enqueued_jobs do
- described_class.new(project, user).close_issue(issue, closed_via: closing_commit)
- end
+
+ close_issue
email = ActionMailer::Base.deliveries.last
body_text = email.body.parts.map(&:body).join(" ")
@@ -216,10 +253,20 @@ RSpec.describe Issues::CloseService do
context "valid params" do
subject(:close_issue) do
perform_enqueued_jobs do
- described_class.new(project, user).close_issue(issue)
+ described_class.new(project: project, current_user: user).close_issue(issue)
end
end
+ it 'verifies the number of queries' do
+ recorded = ActiveRecord::QueryRecorder.new { close_issue }
+ expected_queries = 32
+
+ expect(recorded.count).to be <= expected_queries
+ expect(recorded.cached_count).to eq(0)
+ end
+
+ it_behaves_like 'removes labels marked for removal from issue when closed'
+
it 'closes the issue' do
close_issue
@@ -230,7 +277,7 @@ RSpec.describe Issues::CloseService do
it 'records closed user' do
close_issue
- expect(issue.closed_by_id).to be(user.id)
+ expect(issue.reload.closed_by_id).to be(user.id)
end
it 'sends email to user2 about assign of new issue', :sidekiq_might_not_need_inline do
@@ -249,11 +296,23 @@ RSpec.describe Issues::CloseService do
end
it 'marks todos as done' do
+ todo = create(:todo, :assigned, user: user, project: project, target: issue, author: user2)
+
close_issue
expect(todo.reload).to be_done
end
+ context 'when closing the issue fails' do
+ it 'does not assign a closed_by value for the issue' do
+ allow(issue).to receive(:close).and_return(false)
+
+ close_issue
+
+ expect(issue.closed_by_id).to be_nil
+ end
+ end
+
context 'when there is an associated Alert Management Alert' do
context 'when alert can be resolved' do
let!(:alert) { create(:alert_management_alert, issue: issue, project: project) }
@@ -303,26 +362,32 @@ RSpec.describe Issues::CloseService do
end
context 'when issue is not confidential' do
+ it_behaves_like 'removes labels marked for removal from issue when closed'
+
it 'executes issue hooks' do
expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks)
expect(project).to receive(:execute_services).with(an_instance_of(Hash), :issue_hooks)
- described_class.new(project, user).close_issue(issue)
+ close_issue
end
end
context 'when issue is confidential' do
- it 'executes confidential issue hooks' do
- issue = create(:issue, :confidential, project: project)
+ let(:issue) { create(:issue, :confidential, project: project) }
+
+ it_behaves_like 'removes labels marked for removal from issue when closed'
+ it 'executes confidential issue hooks' do
expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :confidential_issue_hooks)
expect(project).to receive(:execute_services).with(an_instance_of(Hash), :confidential_issue_hooks)
- described_class.new(project, user).close_issue(issue)
+ close_issue
end
end
context 'internal issues disabled' do
+ let!(:todo) { create(:todo, :assigned, user: user, project: project, target: issue, author: user2) }
+
before do
project.issues_enabled = false
project.save!
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index 83c6373c335..9c84242d8ae 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe Issues::CreateService do
let_it_be(:assignee) { create(:user) }
let_it_be(:milestone) { create(:milestone, project: project) }
- let(:issue) { described_class.new(project, user, opts).execute }
+ let(:issue) { described_class.new(project: project, current_user: user, params: opts).execute }
context 'when params are valid' do
let_it_be(:labels) { create_pair(:label, project: project) }
@@ -44,7 +44,7 @@ RSpec.describe Issues::CreateService do
end
context 'when skip_system_notes is true' do
- let(:issue) { described_class.new(project, user, opts).execute(skip_system_notes: true) }
+ let(:issue) { described_class.new(project: project, current_user: user, params: opts).execute(skip_system_notes: true) }
it 'does not call Issuable::CommonSystemNotesService' do
expect(Issuable::CommonSystemNotesService).not_to receive(:new)
@@ -96,7 +96,7 @@ RSpec.describe Issues::CreateService do
end
it 'filters out params that cannot be set without the :admin_issue permission' do
- issue = described_class.new(project, guest, opts).execute
+ issue = described_class.new(project: project, current_user: guest, params: opts).execute
expect(issue).to be_persisted
expect(issue.title).to eq('Awesome issue')
@@ -108,7 +108,7 @@ RSpec.describe Issues::CreateService do
end
it 'creates confidential issues' do
- issue = described_class.new(project, guest, confidential: true).execute
+ issue = described_class.new(project: project, current_user: guest, params: { confidential: true }).execute
expect(issue.confidential).to be_truthy
end
@@ -117,7 +117,7 @@ RSpec.describe Issues::CreateService do
it 'moves the issue to the end, in an asynchronous worker' do
expect(IssuePlacementWorker).to receive(:perform_async).with(be_nil, Integer)
- described_class.new(project, user, opts).execute
+ described_class.new(project: project, current_user: user, params: opts).execute
end
context 'when label belongs to project group' do
@@ -204,7 +204,7 @@ RSpec.describe Issues::CreateService do
it 'invalidates open issues counter for assignees when issue is assigned' do
project.add_maintainer(assignee)
- described_class.new(project, user, opts).execute
+ described_class.new(project: project, current_user: user, params: opts).execute
expect(assignee.assigned_open_issues_count).to eq 1
end
@@ -230,7 +230,7 @@ RSpec.describe Issues::CreateService do
expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks)
expect(project).to receive(:execute_services).with(an_instance_of(Hash), :issue_hooks)
- described_class.new(project, user, opts).execute
+ described_class.new(project: project, current_user: user, params: opts).execute
end
it 'executes confidential issue hooks when issue is confidential' do
@@ -239,7 +239,7 @@ RSpec.describe Issues::CreateService do
expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :confidential_issue_hooks)
expect(project).to receive(:execute_services).with(an_instance_of(Hash), :confidential_issue_hooks)
- described_class.new(project, user, opts).execute
+ described_class.new(project: project, current_user: user, params: opts).execute
end
context 'after_save callback to store_mentions' do
@@ -283,7 +283,7 @@ RSpec.describe Issues::CreateService do
it 'removes assignee when user id is invalid' do
opts = { title: 'Title', description: 'Description', assignee_ids: [-1] }
- issue = described_class.new(project, user, opts).execute
+ issue = described_class.new(project: project, current_user: user, params: opts).execute
expect(issue.assignees).to be_empty
end
@@ -291,7 +291,7 @@ RSpec.describe Issues::CreateService do
it 'removes assignee when user id is 0' do
opts = { title: 'Title', description: 'Description', assignee_ids: [0] }
- issue = described_class.new(project, user, opts).execute
+ issue = described_class.new(project: project, current_user: user, params: opts).execute
expect(issue.assignees).to be_empty
end
@@ -300,7 +300,7 @@ RSpec.describe Issues::CreateService do
project.add_maintainer(assignee)
opts = { title: 'Title', description: 'Description', assignee_ids: [assignee.id] }
- issue = described_class.new(project, user, opts).execute
+ issue = described_class.new(project: project, current_user: user, params: opts).execute
expect(issue.assignees).to eq([assignee])
end
@@ -318,7 +318,7 @@ RSpec.describe Issues::CreateService do
project.update!(visibility_level: level)
opts = { title: 'Title', description: 'Description', assignee_ids: [assignee.id] }
- issue = described_class.new(project, user, opts).execute
+ issue = described_class.new(project: project, current_user: user, params: opts).execute
expect(issue.assignees).to be_empty
end
@@ -328,7 +328,7 @@ RSpec.describe Issues::CreateService do
end
it_behaves_like 'issuable record that supports quick actions' do
- let(:issuable) { described_class.new(project, user, params).execute }
+ let(:issuable) { described_class.new(project: project, current_user: user, params: params).execute }
end
context 'Quick actions' do
@@ -368,14 +368,14 @@ RSpec.describe Issues::CreateService do
let(:opts) { { discussion_to_resolve: discussion.id, merge_request_to_resolve_discussions_of: merge_request.iid } }
it 'resolves the discussion' do
- described_class.new(project, user, opts).execute
+ described_class.new(project: project, current_user: user, params: opts).execute
discussion.first_note.reload
expect(discussion.resolved?).to be(true)
end
it 'added a system note to the discussion' do
- described_class.new(project, user, opts).execute
+ described_class.new(project: project, current_user: user, params: opts).execute
reloaded_discussion = MergeRequest.find(merge_request.id).discussions.first
@@ -383,17 +383,19 @@ RSpec.describe Issues::CreateService do
end
it 'assigns the title and description for the issue' do
- issue = described_class.new(project, user, opts).execute
+ issue = described_class.new(project: project, current_user: user, params: opts).execute
expect(issue.title).not_to be_nil
expect(issue.description).not_to be_nil
end
it 'can set nil explicitly to the title and description' do
- issue = described_class.new(project, user,
- merge_request_to_resolve_discussions_of: merge_request,
- description: nil,
- title: nil).execute
+ issue = described_class.new(project: project, current_user: user,
+ params: {
+ merge_request_to_resolve_discussions_of: merge_request,
+ description: nil,
+ title: nil
+ }).execute
expect(issue.description).to be_nil
expect(issue.title).to be_nil
@@ -404,14 +406,14 @@ RSpec.describe Issues::CreateService do
let(:opts) { { merge_request_to_resolve_discussions_of: merge_request.iid } }
it 'resolves the discussion' do
- described_class.new(project, user, opts).execute
+ described_class.new(project: project, current_user: user, params: opts).execute
discussion.first_note.reload
expect(discussion.resolved?).to be(true)
end
it 'added a system note to the discussion' do
- described_class.new(project, user, opts).execute
+ described_class.new(project: project, current_user: user, params: opts).execute
reloaded_discussion = MergeRequest.find(merge_request.id).discussions.first
@@ -419,17 +421,19 @@ RSpec.describe Issues::CreateService do
end
it 'assigns the title and description for the issue' do
- issue = described_class.new(project, user, opts).execute
+ issue = described_class.new(project: project, current_user: user, params: opts).execute
expect(issue.title).not_to be_nil
expect(issue.description).not_to be_nil
end
it 'can set nil explicitly to the title and description' do
- issue = described_class.new(project, user,
- merge_request_to_resolve_discussions_of: merge_request,
- description: nil,
- title: nil).execute
+ issue = described_class.new(project: project, current_user: user,
+ params: {
+ merge_request_to_resolve_discussions_of: merge_request,
+ description: nil,
+ title: nil
+ }).execute
expect(issue.description).to be_nil
expect(issue.title).to be_nil
@@ -454,7 +458,7 @@ RSpec.describe Issues::CreateService do
end
subject do
- described_class.new(project, user, params)
+ described_class.new(project: project, current_user: user, params: params)
end
before do
diff --git a/spec/services/issues/duplicate_service_spec.rb b/spec/services/issues/duplicate_service_spec.rb
index 0b5bc3f32ef..0eb0bbb1480 100644
--- a/spec/services/issues/duplicate_service_spec.rb
+++ b/spec/services/issues/duplicate_service_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe Issues::DuplicateService do
let(:canonical_issue) { create(:issue, project: canonical_project) }
let(:duplicate_issue) { create(:issue, project: duplicate_project) }
- subject { described_class.new(duplicate_project, user, {}) }
+ subject { described_class.new(project: duplicate_project, current_user: user) }
describe '#execute' do
context 'when the issues passed are the same' do
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
index 2f29a2e2022..76588860957 100644
--- a/spec/services/issues/move_service_spec.rb
+++ b/spec/services/issues/move_service_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe Issues::MoveService do
end
subject(:move_service) do
- described_class.new(old_project, user)
+ described_class.new(project: old_project, current_user: user)
end
shared_context 'user can move issue' do
diff --git a/spec/services/issues/referenced_merge_requests_service_spec.rb b/spec/services/issues/referenced_merge_requests_service_spec.rb
index bf7a4c97e48..dc55ba8ebea 100644
--- a/spec/services/issues/referenced_merge_requests_service_spec.rb
+++ b/spec/services/issues/referenced_merge_requests_service_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'spec_helper.rb'
+require 'spec_helper'
RSpec.describe Issues::ReferencedMergeRequestsService do
def create_referencing_mr(attributes = {})
@@ -26,7 +26,7 @@ RSpec.describe Issues::ReferencedMergeRequestsService do
let_it_be(:referencing_mr) { create_referencing_mr(source_project: project, source_branch: 'csv') }
let_it_be(:referencing_mr_other_project) { create_referencing_mr(source_project: other_project, source_branch: 'csv') }
- let(:service) { described_class.new(project, user) }
+ let(:service) { described_class.new(project: project, current_user: user) }
describe '#execute' do
it 'returns a list of sorted merge requests' do
diff --git a/spec/services/issues/related_branches_service_spec.rb b/spec/services/issues/related_branches_service_spec.rb
index c9c029bca4f..7a4bae7f852 100644
--- a/spec/services/issues/related_branches_service_spec.rb
+++ b/spec/services/issues/related_branches_service_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Issues::RelatedBranchesService do
let(:user) { developer }
- subject { described_class.new(issue.project, user) }
+ subject { described_class.new(project: issue.project, current_user: user) }
before do
issue.project.add_developer(developer)
@@ -95,7 +95,7 @@ RSpec.describe Issues::RelatedBranchesService do
merge_request.create_cross_references!(user)
referenced_merge_requests = Issues::ReferencedMergeRequestsService
- .new(issue.project, user)
+ .new(project: issue.project, current_user: user)
.referenced_merge_requests(issue)
expect(referenced_merge_requests).not_to be_empty
diff --git a/spec/services/issues/reopen_service_spec.rb b/spec/services/issues/reopen_service_spec.rb
index ffe74cca9cf..746a9105531 100644
--- a/spec/services/issues/reopen_service_spec.rb
+++ b/spec/services/issues/reopen_service_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe Issues::ReopenService do
project.add_guest(guest)
perform_enqueued_jobs do
- described_class.new(project, guest).execute(issue)
+ described_class.new(project: project, current_user: guest).execute(issue)
end
end
@@ -33,11 +33,11 @@ RSpec.describe Issues::ReopenService do
issue.assignees << user
expect_any_instance_of(User).to receive(:invalidate_issue_cache_counts)
- described_class.new(project, user).execute(issue)
+ described_class.new(project: project, current_user: user).execute(issue)
end
it 'refreshes the number of opened issues' do
- service = described_class.new(project, user)
+ service = described_class.new(project: project, current_user: user)
expect { service.execute(issue) }
.to change { project.open_issues_count }.from(0).to(1)
@@ -50,14 +50,14 @@ RSpec.describe Issues::ReopenService do
expect(service).to receive(:delete_cache).and_call_original
end
- described_class.new(project, user).execute(issue)
+ described_class.new(project: project, current_user: user).execute(issue)
end
context 'issue is incident type' do
let(:issue) { create(:incident, :closed, project: project) }
let(:current_user) { user }
- subject { described_class.new(project, user).execute(issue) }
+ subject { described_class.new(project: project, current_user: user).execute(issue) }
it_behaves_like 'an incident management tracked event', :incident_management_incident_reopened
end
@@ -67,7 +67,7 @@ RSpec.describe Issues::ReopenService do
expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks)
expect(project).to receive(:execute_services).with(an_instance_of(Hash), :issue_hooks)
- described_class.new(project, user).execute(issue)
+ described_class.new(project: project, current_user: user).execute(issue)
end
end
@@ -78,7 +78,7 @@ RSpec.describe Issues::ReopenService do
expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :confidential_issue_hooks)
expect(project).to receive(:execute_services).with(an_instance_of(Hash), :confidential_issue_hooks)
- described_class.new(project, user).execute(issue)
+ described_class.new(project: project, current_user: user).execute(issue)
end
end
end
diff --git a/spec/services/issues/reorder_service_spec.rb b/spec/services/issues/reorder_service_spec.rb
index 78b937a1caf..15668a3aa23 100644
--- a/spec/services/issues/reorder_service_spec.rb
+++ b/spec/services/issues/reorder_service_spec.rb
@@ -75,7 +75,7 @@ RSpec.describe Issues::ReorderService do
match_params = { move_between_ids: [issue2.id, issue3.id], board_group_id: group.id }
expect(Issues::UpdateService)
- .to receive(:new).with(project, user, match_params)
+ .to receive(:new).with(project: project, current_user: user, params: match_params)
.and_return(double(execute: build(:issue)))
subject.execute(issue1)
@@ -95,6 +95,6 @@ RSpec.describe Issues::ReorderService do
end
def service(params)
- described_class.new(project, user, params)
+ described_class.new(project: project, current_user: user, params: params)
end
end
diff --git a/spec/services/issues/resolve_discussions_spec.rb b/spec/services/issues/resolve_discussions_spec.rb
index 9fbc9cbcca6..1ac71b966bc 100644
--- a/spec/services/issues/resolve_discussions_spec.rb
+++ b/spec/services/issues/resolve_discussions_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'spec_helper.rb'
+require 'spec_helper'
RSpec.describe Issues::ResolveDiscussions do
let(:project) { create(:project, :repository) }
@@ -11,7 +11,7 @@ RSpec.describe Issues::ResolveDiscussions do
DummyService.class_eval do
include ::Issues::ResolveDiscussions
- def initialize(*args)
+ def initialize(project:, current_user: nil, params: {})
super
filter_resolve_discussion_params
end
@@ -26,7 +26,7 @@ RSpec.describe Issues::ResolveDiscussions do
let(:other_merge_request) { create(:merge_request, source_project: project, source_branch: "fix") }
describe "#merge_request_for_resolving_discussion" do
- let(:service) { DummyService.new(project, user, merge_request_to_resolve_discussions_of: merge_request.iid) }
+ let(:service) { DummyService.new(project: project, current_user: user, params: { merge_request_to_resolve_discussions_of: merge_request.iid }) }
it "finds the merge request" do
expect(service.merge_request_to_resolve_discussions_of).to eq(merge_request)
@@ -45,10 +45,12 @@ RSpec.describe Issues::ResolveDiscussions do
describe "#discussions_to_resolve" do
it "contains a single discussion when matching merge request and discussion are passed" do
service = DummyService.new(
- project,
- user,
- discussion_to_resolve: discussion.id,
- merge_request_to_resolve_discussions_of: merge_request.iid
+ project: project,
+ current_user: user,
+ params: {
+ discussion_to_resolve: discussion.id,
+ merge_request_to_resolve_discussions_of: merge_request.iid
+ }
)
# We need to compare discussion id's because the Discussion-objects are rebuilt
# which causes the object-id's not to be different.
@@ -63,9 +65,9 @@ RSpec.describe Issues::ResolveDiscussions do
project: merge_request.target_project,
line_number: 15)])
service = DummyService.new(
- project,
- user,
- merge_request_to_resolve_discussions_of: merge_request.iid
+ project: project,
+ current_user: user,
+ params: { merge_request_to_resolve_discussions_of: merge_request.iid }
)
# We need to compare discussion id's because the Discussion-objects are rebuilt
# which causes the object-id's not to be different.
@@ -81,9 +83,9 @@ RSpec.describe Issues::ResolveDiscussions do
line_number: 15
)])
service = DummyService.new(
- project,
- user,
- merge_request_to_resolve_discussions_of: merge_request.iid
+ project: project,
+ current_user: user,
+ params: { merge_request_to_resolve_discussions_of: merge_request.iid }
)
# We need to compare discussion id's because the Discussion-objects are rebuilt
# which causes the object-id's not to be different.
@@ -94,10 +96,12 @@ RSpec.describe Issues::ResolveDiscussions do
it "is empty when a discussion and another merge request are passed" do
service = DummyService.new(
- project,
- user,
- discussion_to_resolve: discussion.id,
- merge_request_to_resolve_discussions_of: other_merge_request.iid
+ project: project,
+ current_user: user,
+ params: {
+ discussion_to_resolve: discussion.id,
+ merge_request_to_resolve_discussions_of: other_merge_request.iid
+ }
)
expect(service.discussions_to_resolve).to be_empty
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index fd42a84e405..8c97dd95ced 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -41,7 +41,7 @@ RSpec.describe Issues::UpdateService, :mailer do
end
def update_issue(opts)
- described_class.new(project, user, opts).execute(issue)
+ described_class.new(project: project, current_user: user, params: opts).execute(issue)
end
context 'valid params' do
@@ -165,20 +165,38 @@ RSpec.describe Issues::UpdateService, :mailer do
expect(user2.assigned_open_issues_count).to eq 1
end
- it 'sorts issues as specified by parameters' do
- issue1 = create(:issue, project: project, assignees: [user3])
- issue2 = create(:issue, project: project, assignees: [user3])
+ context 'when changing relative position' do
+ let(:issue1) { create(:issue, project: project, assignees: [user3]) }
+ let(:issue2) { create(:issue, project: project, assignees: [user3]) }
- [issue, issue1, issue2].each do |issue|
- issue.move_to_end
- issue.save!
+ before do
+ [issue, issue1, issue2].each do |issue|
+ issue.move_to_end
+ issue.save!
+ end
end
- opts[:move_between_ids] = [issue1.id, issue2.id]
+ it 'sorts issues as specified by parameters' do
+ opts[:move_between_ids] = [issue1.id, issue2.id]
- update_issue(opts)
+ update_issue(opts)
- expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
+ expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
+ end
+
+ context 'when block_issue_positioning flag is enabled' do
+ before do
+ stub_feature_flags(block_issue_repositioning: true)
+ end
+
+ it 'raises error' do
+ old_position = issue.relative_position
+ opts[:move_between_ids] = [issue1.id, issue2.id]
+
+ expect { update_issue(opts) }.to raise_error(::Gitlab::RelativePositioning::IssuePositioningDisabled)
+ expect(issue.reload.relative_position).to eq(old_position)
+ end
+ end
end
it 'does not rebalance even if needed if the flag is disabled' do
@@ -269,7 +287,7 @@ RSpec.describe Issues::UpdateService, :mailer do
opts[:move_between_ids] = [issue_1.id, issue_2.id]
opts[:board_group_id] = group.id
- described_class.new(issue_3.project, user, opts).execute(issue_3)
+ described_class.new(project: issue_3.project, current_user: user, params: opts).execute(issue_3)
expect(issue_2.relative_position).to be_between(issue_1.relative_position, issue_2.relative_position)
end
end
@@ -282,7 +300,12 @@ RSpec.describe Issues::UpdateService, :mailer do
end
it 'filters out params that cannot be set without the :admin_issue permission' do
- described_class.new(project, guest, opts.merge(confidential: true)).execute(issue)
+ described_class.new(
+ project: project, current_user: guest, params: opts.merge(
+ confidential: true,
+ issue_type: 'test_case'
+ )
+ ).execute(issue)
expect(issue).to be_valid
expect(issue.title).to eq 'New title'
@@ -293,6 +316,7 @@ RSpec.describe Issues::UpdateService, :mailer do
expect(issue.due_date).to be_nil
expect(issue.discussion_locked).to be_falsey
expect(issue.confidential).to be_falsey
+ expect(issue.issue_type).to eql('issue')
end
end
@@ -650,7 +674,7 @@ RSpec.describe Issues::UpdateService, :mailer do
opts = { label_ids: [label.id] }
perform_enqueued_jobs do
- @issue = described_class.new(project, user, opts).execute(issue)
+ @issue = described_class.new(project: project, current_user: user, params: opts).execute(issue)
end
should_email(subscriber)
@@ -666,7 +690,7 @@ RSpec.describe Issues::UpdateService, :mailer do
opts = { label_ids: [label.id, label2.id] }
perform_enqueued_jobs do
- @issue = described_class.new(project, user, opts).execute(issue)
+ @issue = described_class.new(project: project, current_user: user, params: opts).execute(issue)
end
should_not_email(subscriber)
@@ -677,7 +701,7 @@ RSpec.describe Issues::UpdateService, :mailer do
opts = { label_ids: [label2.id] }
perform_enqueued_jobs do
- @issue = described_class.new(project, user, opts).execute(issue)
+ @issue = described_class.new(project: project, current_user: user, params: opts).execute(issue)
end
should_not_email(subscriber)
@@ -709,7 +733,7 @@ RSpec.describe Issues::UpdateService, :mailer do
line_number: 1
}
}
- service = described_class.new(project, user, params)
+ service = described_class.new(project: project, current_user: user, params: params)
expect(Spam::SpamActionService).not_to receive(:new)
@@ -785,7 +809,7 @@ RSpec.describe Issues::UpdateService, :mailer do
context 'updating labels' do
let(:label3) { create(:label, project: project) }
- let(:result) { described_class.new(project, user, params).execute(issue).reload }
+ let(:result) { described_class.new(project: project, current_user: user, params: params).execute(issue).reload }
context 'when add_label_ids and label_ids are passed' do
let(:params) { { label_ids: [label.id], add_label_ids: [label3.id] } }
@@ -983,14 +1007,14 @@ RSpec.describe Issues::UpdateService, :mailer do
it 'raises an error for invalid move ids within a project' do
opts = { move_between_ids: [9000, non_existing_record_id] }
- expect { described_class.new(issue.project, user, opts).execute(issue) }
+ expect { described_class.new(project: issue.project, current_user: user, params: opts).execute(issue) }
.to raise_error(ActiveRecord::RecordNotFound)
end
it 'raises an error for invalid move ids within a group' do
opts = { move_between_ids: [9000, non_existing_record_id], board_group_id: create(:group).id }
- expect { described_class.new(issue.project, user, opts).execute(issue) }
+ expect { described_class.new(project: issue.project, current_user: user, params: opts).execute(issue) }
.to raise_error(ActiveRecord::RecordNotFound)
end
end
@@ -1014,13 +1038,13 @@ RSpec.describe Issues::UpdateService, :mailer do
with_them do
it 'broadcasts to the issues channel based on ActionCable and feature flag values' do
- expect(Gitlab::ActionCable::Config).to receive(:in_app?).and_return(action_cable_in_app_enabled)
+ allow(Gitlab::ActionCable::Config).to receive(:in_app?).and_return(action_cable_in_app_enabled)
stub_feature_flags(broadcast_issue_updates: feature_flag_enabled)
if should_broadcast
- expect(IssuesChannel).to receive(:broadcast_to).with(issue, event: 'updated')
+ expect(GraphqlTriggers).to receive(:issuable_assignees_updated).with(issue)
else
- expect(IssuesChannel).not_to receive(:broadcast_to)
+ expect(GraphqlTriggers).not_to receive(:issuable_assignees_updated).with(issue)
end
update_issue(update_params)
@@ -1030,7 +1054,7 @@ RSpec.describe Issues::UpdateService, :mailer do
it_behaves_like 'issuable record that supports quick actions' do
let(:existing_issue) { create(:issue, project: project) }
- let(:issuable) { described_class.new(project, user, params).execute(existing_issue) }
+ let(:issuable) { described_class.new(project: project, current_user: user, params: params).execute(existing_issue) }
end
end
end
diff --git a/spec/services/issues/zoom_link_service_spec.rb b/spec/services/issues/zoom_link_service_spec.rb
index 8e8adc516cf..19db892fcae 100644
--- a/spec/services/issues/zoom_link_service_spec.rb
+++ b/spec/services/issues/zoom_link_service_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Issues::ZoomLinkService do
let_it_be(:issue) { create(:issue) }
let(:project) { issue.project }
- let(:service) { described_class.new(issue, user) }
+ let(:service) { described_class.new(project: project, current_user: user, params: { issue: issue }) }
let(:zoom_link) { 'https://zoom.us/j/123456789' }
before do
diff --git a/spec/services/labels/available_labels_service_spec.rb b/spec/services/labels/available_labels_service_spec.rb
index 9ee0b80edcd..355dbd0c712 100644
--- a/spec/services/labels/available_labels_service_spec.rb
+++ b/spec/services/labels/available_labels_service_spec.rb
@@ -36,6 +36,15 @@ RSpec.describe Labels::AvailableLabelsService do
expect(result).to include(project_label, group_label)
expect(result).not_to include(other_project_label, other_group_label)
end
+
+ it 'do not cause additional query for finding labels' do
+ label_titles = [project_label.title]
+ control_count = ActiveRecord::QueryRecorder.new { described_class.new(user, project, labels: label_titles).find_or_create_by_titles }
+
+ new_label = create(:label, project: project)
+ label_titles = [project_label.title, new_label.title]
+ expect { described_class.new(user, project, labels: label_titles).find_or_create_by_titles }.not_to exceed_query_limit(control_count)
+ end
end
end
diff --git a/spec/services/labels/find_or_create_service_spec.rb b/spec/services/labels/find_or_create_service_spec.rb
index aa9eb0e6a0d..3ea2727dc60 100644
--- a/spec/services/labels/find_or_create_service_spec.rb
+++ b/spec/services/labels/find_or_create_service_spec.rb
@@ -25,6 +25,35 @@ RSpec.describe Labels::FindOrCreateService do
project.add_developer(user)
end
+ context 'when existing_labels_by_title is provided' do
+ let(:preloaded_label) { build(:label, title: 'Security') }
+
+ before do
+ params.merge!(
+ existing_labels_by_title: {
+ 'Security' => preloaded_label
+ })
+ end
+
+ context 'when label exists' do
+ it 'returns preloaded label' do
+ expect(service.execute).to eq preloaded_label
+ end
+ end
+
+ context 'when label does not exists' do
+ before do
+ params[:title] = 'Audit'
+ end
+
+ it 'does not generates additional label search' do
+ service.execute
+
+ expect(LabelsFinder).not_to receive(:new)
+ end
+ end
+ end
+
context 'when label does not exist at group level' do
it 'creates a new label at project level' do
expect { service.execute }.to change(project.labels, :count).by(1)
diff --git a/spec/services/lfs/push_service_spec.rb b/spec/services/lfs/push_service_spec.rb
index f67284ff48d..58fb2f3fb9b 100644
--- a/spec/services/lfs/push_service_spec.rb
+++ b/spec/services/lfs/push_service_spec.rb
@@ -63,6 +63,7 @@ RSpec.describe Lfs::PushService do
it 'returns a failure when submitting a batch fails' do
expect(lfs_client).to receive(:batch!) { raise 'failed' }
+ expect(Gitlab::ErrorTracking).to receive(:log_exception).and_call_original
expect(service.execute).to eq(status: :error, message: 'failed')
end
@@ -70,6 +71,7 @@ RSpec.describe Lfs::PushService do
stub_lfs_batch(lfs_object)
expect(lfs_client).to receive(:upload!) { raise 'failed' }
+ expect(Gitlab::ErrorTracking).to receive(:log_exception).and_call_original
expect(service.execute).to eq(status: :error, message: 'failed')
end
diff --git a/spec/services/merge_requests/add_context_service_spec.rb b/spec/services/merge_requests/add_context_service_spec.rb
index 27b46a9023c..448be27efe8 100644
--- a/spec/services/merge_requests/add_context_service_spec.rb
+++ b/spec/services/merge_requests/add_context_service_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe MergeRequests::AddContextService do
let(:commits) { ["874797c3a73b60d2187ed6e2fcabd289ff75171e"] }
let(:raw_repository) { project.repository.raw }
- subject(:service) { described_class.new(project, admin, merge_request: merge_request, commits: commits) }
+ subject(:service) { described_class.new(project: project, current_user: admin, params: { merge_request: merge_request, commits: commits }) }
describe "#execute" do
context "when admin mode is enabled", :enable_admin_mode do
@@ -32,7 +32,7 @@ RSpec.describe MergeRequests::AddContextService do
let(:user) { create(:user) }
let(:merge_request1) { create(:merge_request, source_project: project, author: user) }
- subject(:service) { described_class.new(project, user, merge_request: merge_request, commits: commits) }
+ subject(:service) { described_class.new(project: project, current_user: user, params: { merge_request: merge_request, commits: commits }) }
it "doesn't add context commit" do
subject.execute
@@ -42,7 +42,7 @@ RSpec.describe MergeRequests::AddContextService do
end
context "when the commits array is empty" do
- subject(:service) { described_class.new(project, admin, merge_request: merge_request, commits: []) }
+ subject(:service) { described_class.new(project: project, current_user: admin, params: { merge_request: merge_request, commits: [] }) }
it "doesn't add context commit" do
subject.execute
diff --git a/spec/services/merge_requests/add_spent_time_service_spec.rb b/spec/services/merge_requests/add_spent_time_service_spec.rb
new file mode 100644
index 00000000000..db3380e9582
--- /dev/null
+++ b/spec/services/merge_requests/add_spent_time_service_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe MergeRequests::AddSpentTimeService do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :public, :repository) }
+ let_it_be_with_reload(:merge_request) { create(:merge_request, :simple, :unique_branches, source_project: project) }
+
+ let(:duration) { 1500 }
+ let(:params) { { spend_time: { duration: duration, user_id: user.id } } }
+ let(:service) { described_class.new(project: project, current_user: user, params: params) }
+
+ describe '#execute' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'creates a new timelog with the specified duration' do
+ expect { service.execute(merge_request) }.to change { Timelog.count }.from(0).to(1)
+
+ timelog = merge_request.timelogs.last
+
+ expect(timelog).not_to be_nil
+ expect(timelog.time_spent).to eq(1500)
+ end
+
+ it 'creates a system note with the time added' do
+ expect { service.execute(merge_request) }.to change { Note.count }.from(0).to(1)
+
+ system_note = merge_request.notes.last
+
+ expect(system_note).not_to be_nil
+ expect(system_note.note_html).to include('added 25m of time spent')
+ end
+
+ it 'saves usage data' do
+ expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
+ .to receive(:track_time_spent_changed_action).once.with(user: user)
+
+ service.execute(merge_request)
+ end
+
+ it 'is more efficient than using the full update-service' do
+ other_mr = create(:merge_request, :simple, :unique_branches, source_project: project)
+
+ update_service = ::MergeRequests::UpdateService.new(project: project, current_user: user, params: params)
+ other_mr.reload
+
+ expect { service.execute(merge_request) }
+ .to issue_fewer_queries_than { update_service.execute(other_mr) }
+ end
+
+ context 'when duration is nil' do
+ let(:duration) { nil }
+
+ it 'does not create a timelog with the specified duration' do
+ expect { service.execute(merge_request) }.not_to change { Timelog.count }
+ expect(merge_request).not_to be_valid
+ end
+ end
+ end
+end
diff --git a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
index 6edaa91b8b2..8d1abe5ea89 100644
--- a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
+++ b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe ::MergeRequests::AddTodoWhenBuildFailsService do
let(:ref) { merge_request.source_branch }
let(:service) do
- described_class.new(project, user, commit_message: 'Awesome message')
+ described_class.new(project: project, current_user: user, params: { commit_message: 'Awesome message' })
end
let(:todo_service) { spy('todo service') }
diff --git a/spec/services/merge_requests/after_create_service_spec.rb b/spec/services/merge_requests/after_create_service_spec.rb
index e1f28e32164..cbbd193a411 100644
--- a/spec/services/merge_requests/after_create_service_spec.rb
+++ b/spec/services/merge_requests/after_create_service_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe MergeRequests::AfterCreateService do
let_it_be(:merge_request) { create(:merge_request) }
subject(:after_create_service) do
- described_class.new(merge_request.target_project, merge_request.author)
+ described_class.new(project: merge_request.target_project, current_user: merge_request.author)
end
describe '#execute' do
@@ -191,7 +191,7 @@ RSpec.describe MergeRequests::AfterCreateService do
it 'calls MergeRequests::LinkLfsObjectsService#execute' do
service = instance_spy(MergeRequests::LinkLfsObjectsService)
- allow(MergeRequests::LinkLfsObjectsService).to receive(:new).with(merge_request.target_project).and_return(service)
+ allow(MergeRequests::LinkLfsObjectsService).to receive(:new).with(project: merge_request.target_project).and_return(service)
execute_service
diff --git a/spec/services/merge_requests/approval_service_spec.rb b/spec/services/merge_requests/approval_service_spec.rb
index df9a98c5540..d30b2721a36 100644
--- a/spec/services/merge_requests/approval_service_spec.rb
+++ b/spec/services/merge_requests/approval_service_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe MergeRequests::ApprovalService do
let(:project) { merge_request.project }
let!(:todo) { create(:todo, user: user, project: project, target: merge_request) }
- subject(:service) { described_class.new(project, user) }
+ subject(:service) { described_class.new(project: project, current_user: user) }
before do
project.add_developer(user)
diff --git a/spec/services/merge_requests/assign_issues_service_spec.rb b/spec/services/merge_requests/assign_issues_service_spec.rb
index 6398e8c533e..b857f26c052 100644
--- a/spec/services/merge_requests/assign_issues_service_spec.rb
+++ b/spec/services/merge_requests/assign_issues_service_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe MergeRequests::AssignIssuesService do
let(:project) { create(:project, :public, :repository) }
let(:issue) { create(:issue, project: project) }
let(:merge_request) { create(:merge_request, :simple, source_project: project, author: user, description: "fixes #{issue.to_reference}") }
- let(:service) { described_class.new(project, user, merge_request: merge_request) }
+ let(:service) { described_class.new(project: project, current_user: user, params: { merge_request: merge_request }) }
before do
project.add_developer(user)
@@ -37,10 +37,12 @@ RSpec.describe MergeRequests::AssignIssuesService do
it 'accepts precomputed data for closes_issues' do
issue2 = create(:issue, project: project)
- service2 = described_class.new(project,
- user,
- merge_request: merge_request,
- closes_issues: [issue, issue2])
+ service2 = described_class.new(project: project,
+ current_user: user,
+ params: {
+ merge_request: merge_request,
+ closes_issues: [issue, issue2]
+ })
expect(service2.assignable_issues.count).to eq 2
end
@@ -52,10 +54,12 @@ RSpec.describe MergeRequests::AssignIssuesService do
it 'ignores external issues' do
external_issue = ExternalIssue.new('JIRA-123', project)
service = described_class.new(
- project,
- user,
- merge_request: merge_request,
- closes_issues: [external_issue]
+ project: project,
+ current_user: user,
+ params: {
+ merge_request: merge_request,
+ closes_issues: [external_issue]
+ }
)
expect(service.assignable_issues.count).to eq 0
diff --git a/spec/services/merge_requests/base_service_spec.rb b/spec/services/merge_requests/base_service_spec.rb
index d8ba2bc43fb..7911392ef19 100644
--- a/spec/services/merge_requests/base_service_spec.rb
+++ b/spec/services/merge_requests/base_service_spec.rb
@@ -17,7 +17,7 @@ RSpec.describe MergeRequests::BaseService do
}
end
- subject { MergeRequests::CreateService.new(project, project.owner, params) }
+ subject { MergeRequests::CreateService.new(project: project, current_user: project.owner, params: params) }
describe '#execute_hooks' do
shared_examples 'enqueues Jira sync worker' do
diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb
index 8adf6d69f73..5a6a9df3f44 100644
--- a/spec/services/merge_requests/build_service_spec.rb
+++ b/spec/services/merge_requests/build_service_spec.rb
@@ -49,7 +49,7 @@ RSpec.describe MergeRequests::BuildService do
end
let(:service) do
- described_class.new(project, user, params)
+ described_class.new(project: project, current_user: user, params: params)
end
before do
@@ -100,7 +100,7 @@ RSpec.describe MergeRequests::BuildService do
context 'with force_remove_source_branch parameter when the user is authorized' do
let(:mr_params) { params.merge(force_remove_source_branch: '1') }
let(:source_project) { fork_project(project, user) }
- let(:merge_request) { described_class.new(project, user, mr_params).execute }
+ let(:merge_request) { described_class.new(project: project, current_user: user, params: mr_params).execute }
before do
project.add_reporter(user)
diff --git a/spec/services/merge_requests/cleanup_refs_service_spec.rb b/spec/services/merge_requests/cleanup_refs_service_spec.rb
index a1822a4d5ba..e8690ae5bf2 100644
--- a/spec/services/merge_requests/cleanup_refs_service_spec.rb
+++ b/spec/services/merge_requests/cleanup_refs_service_spec.rb
@@ -54,7 +54,7 @@ RSpec.describe MergeRequests::CleanupRefsService do
context 'when merge request has merge ref' do
before do
MergeRequests::MergeToRefService
- .new(merge_request.project, merge_request.author)
+ .new(project: merge_request.project, current_user: merge_request.author)
.execute(merge_request)
end
diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb
index 48f56b3ec68..f6336a85a25 100644
--- a/spec/services/merge_requests/close_service_spec.rb
+++ b/spec/services/merge_requests/close_service_spec.rb
@@ -21,7 +21,7 @@ RSpec.describe MergeRequests::CloseService do
it_behaves_like 'merge request reviewers cache counters invalidator'
context 'valid params' do
- let(:service) { described_class.new(project, user, {}) }
+ let(:service) { described_class.new(project: project, current_user: user) }
before do
allow(service).to receive(:execute_hooks)
@@ -73,7 +73,7 @@ RSpec.describe MergeRequests::CloseService do
expect(metrics_service).to receive(:close)
- described_class.new(project, user, {}).execute(merge_request)
+ described_class.new(project: project, current_user: user).execute(merge_request)
end
it 'calls the merge request activity counter' do
@@ -81,11 +81,11 @@ RSpec.describe MergeRequests::CloseService do
.to receive(:track_close_mr_action)
.with(user: user)
- described_class.new(project, user, {}).execute(merge_request)
+ described_class.new(project: project, current_user: user).execute(merge_request)
end
it 'refreshes the number of open merge requests for a valid MR', :use_clean_rails_memory_store_caching do
- service = described_class.new(project, user, {})
+ service = described_class.new(project: project, current_user: user)
expect { service.execute(merge_request) }
.to change { project.open_merge_requests_count }.from(1).to(0)
@@ -96,19 +96,19 @@ RSpec.describe MergeRequests::CloseService do
expect(service).to receive(:execute_for_merge_request).with(merge_request)
end
- described_class.new(project, user).execute(merge_request)
+ described_class.new(project: project, current_user: user).execute(merge_request)
end
it 'schedules CleanupRefsService' do
expect(MergeRequests::CleanupRefsService).to receive(:schedule).with(merge_request)
- described_class.new(project, user).execute(merge_request)
+ described_class.new(project: project, current_user: user).execute(merge_request)
end
context 'current user is not authorized to close merge request' do
before do
perform_enqueued_jobs do
- @merge_request = described_class.new(project, guest).execute(merge_request)
+ @merge_request = described_class.new(project: project, current_user: guest).execute(merge_request)
end
end
diff --git a/spec/services/merge_requests/create_from_issue_service_spec.rb b/spec/services/merge_requests/create_from_issue_service_spec.rb
index 6528edfc8b7..749b30bff5f 100644
--- a/spec/services/merge_requests/create_from_issue_service_spec.rb
+++ b/spec/services/merge_requests/create_from_issue_service_spec.rb
@@ -11,8 +11,8 @@ RSpec.describe MergeRequests::CreateFromIssueService do
let(:milestone_id) { create(:milestone, project: project).id }
let(:issue) { create(:issue, project: project, milestone_id: milestone_id) }
let(:custom_source_branch) { 'custom-source-branch' }
- let(:service) { described_class.new(project, user, service_params) }
- let(:service_with_custom_source_branch) { described_class.new(project, user, branch_name: custom_source_branch, **service_params) }
+ let(:service) { described_class.new(project: project, current_user: user, mr_params: service_params) }
+ let(:service_with_custom_source_branch) { described_class.new(project: project, current_user: user, mr_params: { branch_name: custom_source_branch, **service_params }) }
before do
project.add_developer(user)
@@ -21,14 +21,14 @@ RSpec.describe MergeRequests::CreateFromIssueService do
describe '#execute' do
shared_examples_for 'a service that creates a merge request from an issue' do
it 'returns an error when user can not create merge request on target project' do
- result = described_class.new(project, create(:user), service_params).execute
+ result = described_class.new(project: project, current_user: create(:user), mr_params: service_params).execute
expect(result[:status]).to eq(:error)
expect(result[:message]).to eq('Not allowed to create merge request')
end
it 'returns an error with invalid issue iid' do
- result = described_class.new(project, user, issue_iid: -1).execute
+ result = described_class.new(project: project, current_user: user, mr_params: { issue_iid: -1 }).execute
expect(result[:status]).to eq(:error)
expect(result[:message]).to eq('Invalid issue iid')
@@ -123,7 +123,7 @@ RSpec.describe MergeRequests::CreateFromIssueService do
end
context 'when ref branch is set', :sidekiq_might_not_need_inline do
- subject { described_class.new(project, user, ref: 'feature', **service_params).execute }
+ subject { described_class.new(project: project, current_user: user, mr_params: { ref: 'feature', **service_params }).execute }
it 'sets the merge request source branch to the new issue branch' do
expect(subject[:merge_request].source_branch).to eq(issue.to_branch_name)
@@ -134,7 +134,7 @@ RSpec.describe MergeRequests::CreateFromIssueService do
end
context 'when the ref is a tag' do
- subject { described_class.new(project, user, ref: 'v1.0.0', **service_params).execute }
+ subject { described_class.new(project: project, current_user: user, mr_params: { ref: 'v1.0.0', **service_params }).execute }
it 'sets the merge request source branch to the new issue branch' do
expect(subject[:merge_request].source_branch).to eq(issue.to_branch_name)
@@ -150,7 +150,7 @@ RSpec.describe MergeRequests::CreateFromIssueService do
end
context 'when ref branch does not exist' do
- subject { described_class.new(project, user, ref: 'no-such-branch', **service_params).execute }
+ subject { described_class.new(project: project, current_user: user, mr_params: { ref: 'no-such-branch', **service_params }).execute }
it 'creates a merge request' do
expect { subject }.to change(target_project.merge_requests, :count).by(1)
diff --git a/spec/services/merge_requests/create_pipeline_service_spec.rb b/spec/services/merge_requests/create_pipeline_service_spec.rb
index 3e2e940dc24..a0ac168f3d7 100644
--- a/spec/services/merge_requests/create_pipeline_service_spec.rb
+++ b/spec/services/merge_requests/create_pipeline_service_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe MergeRequests::CreatePipelineService do
let_it_be(:project, reload: true) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
- let(:service) { described_class.new(project, actor, params) }
+ let(:service) { described_class.new(project: project, current_user: actor, params: params) }
let(:actor) { user }
let(:params) { {} }
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index f2bc55103f0..b2351ab53bd 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -21,7 +21,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
}
end
- let(:service) { described_class.new(project, user, opts) }
+ let(:service) { described_class.new(project: project, current_user: user, params: opts) }
let(:merge_request) { service.execute }
before do
@@ -347,12 +347,12 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
}
end
- let(:issuable) { described_class.new(project, user, params).execute }
+ let(:issuable) { described_class.new(project: project, current_user: user, params: params).execute }
end
context 'Quick actions' do
context 'with assignee and milestone in params and command' do
- let(:merge_request) { described_class.new(project, user, opts).execute }
+ let(:merge_request) { described_class.new(project: project, current_user: user, params: opts).execute }
let(:milestone) { create(:milestone, project: project) }
let(:opts) do
@@ -390,7 +390,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
it 'removes assignee_id when user id is invalid' do
opts = { title: 'Title', description: 'Description', assignee_ids: [-1] }
- merge_request = described_class.new(project, user, opts).execute
+ merge_request = described_class.new(project: project, current_user: user, params: opts).execute
expect(merge_request.assignee_ids).to be_empty
end
@@ -398,7 +398,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
it 'removes assignee_id when user id is 0' do
opts = { title: 'Title', description: 'Description', assignee_ids: [0] }
- merge_request = described_class.new(project, user, opts).execute
+ merge_request = described_class.new(project: project, current_user: user, params: opts).execute
expect(merge_request.assignee_ids).to be_empty
end
@@ -407,7 +407,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
project.add_maintainer(user2)
opts = { title: 'Title', description: 'Description', assignee_ids: [user2.id] }
- merge_request = described_class.new(project, user, opts).execute
+ merge_request = described_class.new(project: project, current_user: user, params: opts).execute
expect(merge_request.assignees).to eq([user2])
end
@@ -426,7 +426,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
it 'invalidates open merge request counter for assignees when merge request is assigned' do
project.add_maintainer(user2)
- described_class.new(project, user, opts).execute
+ described_class.new(project: project, current_user: user, params: opts).execute
expect(user2.assigned_open_merge_requests_count).to eq 1
end
@@ -445,7 +445,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
project.update!(visibility_level: level)
opts = { title: 'Title', description: 'Description', assignee_ids: [user2.id] }
- merge_request = described_class.new(project, user, opts).execute
+ merge_request = described_class.new(project: project, current_user: user, params: opts).execute
expect(merge_request.assignee_id).to be_nil
end
@@ -473,7 +473,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
end
it 'raises an error' do
- expect { described_class.new(project, user, opts).execute }
+ expect { described_class.new(project: project, current_user: user, params: opts).execute }
.to raise_error Gitlab::Access::AccessDeniedError
end
end
@@ -485,7 +485,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
end
it 'raises an error' do
- expect { described_class.new(project, user, opts).execute }
+ expect { described_class.new(project: project, current_user: user, params: opts).execute }
.to raise_error Gitlab::Access::AccessDeniedError
end
end
@@ -497,7 +497,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
end
it 'creates the merge request', :sidekiq_might_not_need_inline do
- merge_request = described_class.new(project, user, opts).execute
+ merge_request = described_class.new(project: project, current_user: user, params: opts).execute
expect(merge_request).to be_persisted
end
@@ -505,7 +505,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
it 'does not create the merge request when the target project is archived' do
target_project.update!(archived: true)
- expect { described_class.new(project, user, opts).execute }
+ expect { described_class.new(project: project, current_user: user, params: opts).execute }
.to raise_error Gitlab::Access::AccessDeniedError
end
end
@@ -529,7 +529,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
end
it 'ignores source_project_id' do
- merge_request = described_class.new(project, user, opts).execute
+ merge_request = described_class.new(project: project, current_user: user, params: opts).execute
expect(merge_request.source_project_id).to eq(project.id)
end
diff --git a/spec/services/merge_requests/ff_merge_service_spec.rb b/spec/services/merge_requests/ff_merge_service_spec.rb
index aec5a3b3fa3..24a1a8b3113 100644
--- a/spec/services/merge_requests/ff_merge_service_spec.rb
+++ b/spec/services/merge_requests/ff_merge_service_spec.rb
@@ -23,7 +23,7 @@ RSpec.describe MergeRequests::FfMergeService do
describe '#execute' do
context 'valid params' do
- let(:service) { described_class.new(project, user, valid_merge_params) }
+ let(:service) { described_class.new(project: project, current_user: user, params: valid_merge_params) }
def execute_ff_merge
perform_enqueued_jobs do
@@ -92,7 +92,7 @@ RSpec.describe MergeRequests::FfMergeService do
end
context 'error handling' do
- let(:service) { described_class.new(project, user, valid_merge_params.merge(commit_message: 'Awesome message')) }
+ let(:service) { described_class.new(project: project, current_user: user, params: valid_merge_params.merge(commit_message: 'Awesome message')) }
before do
allow(Gitlab::AppLogger).to receive(:error)
diff --git a/spec/services/merge_requests/get_urls_service_spec.rb b/spec/services/merge_requests/get_urls_service_spec.rb
index 053752626dc..5f81e1728fa 100644
--- a/spec/services/merge_requests/get_urls_service_spec.rb
+++ b/spec/services/merge_requests/get_urls_service_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe MergeRequests::GetUrlsService do
include ProjectForksHelper
let(:project) { create(:project, :public, :repository) }
- let(:service) { described_class.new(project) }
+ let(:service) { described_class.new(project: project) }
let(:source_branch) { "merge-test" }
let(:new_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/-/merge_requests/new?merge_request%5Bsource_branch%5D=#{source_branch}" }
let(:show_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/-/merge_requests/#{merge_request.iid}" }
@@ -106,7 +106,7 @@ RSpec.describe MergeRequests::GetUrlsService do
let!(:merge_request) { create(:merge_request, source_project: forked_project, target_project: project, source_branch: source_branch) }
let(:changes) { existing_branch_changes }
# Source project is now the forked one
- let(:service) { described_class.new(forked_project) }
+ let(:service) { described_class.new(project: forked_project) }
before do
allow(forked_project).to receive(:empty_repo?).and_return(false)
diff --git a/spec/services/merge_requests/handle_assignees_change_service_spec.rb b/spec/services/merge_requests/handle_assignees_change_service_spec.rb
index cc595aab04b..0bf18f16abb 100644
--- a/spec/services/merge_requests/handle_assignees_change_service_spec.rb
+++ b/spec/services/merge_requests/handle_assignees_change_service_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe MergeRequests::HandleAssigneesChangeService do
let_it_be(:old_assignees) { create_list(:user, 3) }
let(:options) { {} }
- let(:service) { described_class.new(project, user) }
+ let(:service) { described_class.new(project: project, current_user: user) }
before_all do
project.add_maintainer(user)
@@ -38,18 +38,6 @@ RSpec.describe MergeRequests::HandleAssigneesChangeService do
async_execute
end
-
- context 'when async_handle_merge_request_assignees_change feature is disabled' do
- before do
- stub_feature_flags(async_handle_merge_request_assignees_change: false)
- end
-
- it 'calls #execute' do
- expect(service).to receive(:execute).with(merge_request, old_assignees, options)
-
- async_execute
- end
- end
end
describe '#execute' do
diff --git a/spec/services/merge_requests/link_lfs_objects_service_spec.rb b/spec/services/merge_requests/link_lfs_objects_service_spec.rb
index c1765e3a2ab..2fb6bbaf02f 100644
--- a/spec/services/merge_requests/link_lfs_objects_service_spec.rb
+++ b/spec/services/merge_requests/link_lfs_objects_service_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe MergeRequests::LinkLfsObjectsService, :sidekiq_inline do
)
end
- subject { described_class.new(target_project) }
+ subject { described_class.new(project: target_project) }
shared_examples_for 'linking LFS objects' do
context 'when source project is the same as target project' do
diff --git a/spec/services/merge_requests/mark_reviewer_reviewed_service_spec.rb b/spec/services/merge_requests/mark_reviewer_reviewed_service_spec.rb
index 1075f6f9034..4d7bd3d8800 100644
--- a/spec/services/merge_requests/mark_reviewer_reviewed_service_spec.rb
+++ b/spec/services/merge_requests/mark_reviewer_reviewed_service_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe MergeRequests::MarkReviewerReviewedService do
let(:merge_request) { create(:merge_request, reviewers: [current_user]) }
let(:reviewer) { merge_request.merge_request_reviewers.find_by(user_id: current_user.id) }
let(:project) { merge_request.project }
- let(:service) { described_class.new(project, current_user) }
+ let(:service) { described_class.new(project: project, current_user: current_user) }
let(:result) { service.execute(merge_request) }
before do
@@ -16,7 +16,7 @@ RSpec.describe MergeRequests::MarkReviewerReviewedService do
describe '#execute' do
describe 'invalid permissions' do
- let(:service) { described_class.new(project, create(:user)) }
+ let(:service) { described_class.new(project: project, current_user: create(:user)) }
it 'returns an error' do
expect(result[:status]).to eq :error
@@ -24,7 +24,7 @@ RSpec.describe MergeRequests::MarkReviewerReviewedService do
end
describe 'reviewer does not exist' do
- let(:service) { described_class.new(project, create(:user)) }
+ let(:service) { described_class.new(project: project, current_user: create(:user)) }
it 'returns an error' do
expect(result[:status]).to eq :error
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index c73cbad9d2f..ac39fb59c62 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe MergeRequests::MergeService do
+ include ExclusiveLeaseHelpers
+
let_it_be(:user) { create(:user) }
let_it_be(:user2) { create(:user) }
@@ -15,11 +17,14 @@ RSpec.describe MergeRequests::MergeService do
end
describe '#execute' do
- let(:service) { described_class.new(project, user, merge_params) }
+ let(:service) { described_class.new(project: project, current_user: user, params: merge_params) }
let(:merge_params) do
{ commit_message: 'Awesome message', sha: merge_request.diff_head_sha }
end
+ let(:lease_key) { "merge_requests_merge_service:#{merge_request.id}" }
+ let!(:lease) { stub_exclusive_lease(lease_key) }
+
context 'valid params' do
before do
allow(service).to receive(:execute_hooks)
@@ -90,6 +95,20 @@ RSpec.describe MergeRequests::MergeService do
end
end
+ context 'running the service multiple time' do
+ it 'is idempotent' do
+ 2.times { service.execute(merge_request) }
+
+ expect(merge_request.merge_error).to be_falsey
+ expect(merge_request).to be_valid
+ expect(merge_request).to be_merged
+
+ commit_messages = project.repository.commits('master', limit: 2).map(&:message)
+ expect(commit_messages.uniq.size).to eq(2)
+ expect(merge_request.in_progress_merge_commit_sha).to be_nil
+ end
+ end
+
context 'when an invalid sha is passed' do
let(:merge_request) do
create(:merge_request, :simple,
@@ -209,7 +228,7 @@ RSpec.describe MergeRequests::MergeService do
context 'source branch removal' do
context 'when the source branch is protected' do
let(:service) do
- described_class.new(project, user, merge_params.merge('should_remove_source_branch' => true))
+ described_class.new(project: project, current_user: user, params: merge_params.merge('should_remove_source_branch' => true))
end
before do
@@ -225,7 +244,7 @@ RSpec.describe MergeRequests::MergeService do
context 'when the source branch is the default branch' do
let(:service) do
- described_class.new(project, user, merge_params.merge('should_remove_source_branch' => true))
+ described_class.new(project: project, current_user: user, params: merge_params.merge('should_remove_source_branch' => true))
end
before do
@@ -251,7 +270,7 @@ RSpec.describe MergeRequests::MergeService do
end
context 'when the merger set the source branch not to be removed' do
- let(:service) { described_class.new(project, user, merge_params.merge('should_remove_source_branch' => false)) }
+ let(:service) { described_class.new(project: project, current_user: user, params: merge_params.merge('should_remove_source_branch' => false)) }
it 'does not delete the source branch' do
expect(::MergeRequests::DeleteSourceBranchWorker).not_to receive(:perform_async)
@@ -263,7 +282,7 @@ RSpec.describe MergeRequests::MergeService do
context 'when MR merger set the source branch to be removed' do
let(:service) do
- described_class.new(project, user, merge_params.merge('should_remove_source_branch' => true))
+ described_class.new(project: project, current_user: user, params: merge_params.merge('should_remove_source_branch' => true))
end
it 'removes the source branch using the current user' do
@@ -306,10 +325,12 @@ RSpec.describe MergeRequests::MergeService do
end
it 'logs and saves error if user is not authorized' do
+ stub_exclusive_lease
+
unauthorized_user = create(:user)
project.add_reporter(unauthorized_user)
- service = described_class.new(project, unauthorized_user)
+ service = described_class.new(project: project, current_user: unauthorized_user)
service.execute(merge_request)
@@ -423,6 +444,7 @@ RSpec.describe MergeRequests::MergeService do
merge_request.project.update!(merge_method: merge_method)
error_message = 'Only fast-forward merge is allowed for your project. Please update your source branch'
allow(service).to receive(:execute_hooks)
+ expect(lease).to receive(:cancel)
service.execute(merge_request)
@@ -473,5 +495,17 @@ RSpec.describe MergeRequests::MergeService do
end
end
end
+
+ context 'when the other sidekiq worker has already been running' do
+ before do
+ stub_exclusive_lease_taken(lease_key)
+ end
+
+ it 'does not execute service' do
+ expect(service).not_to receive(:commit)
+
+ service.execute(merge_request)
+ end
+ end
end
end
diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb
index 938165a807c..bb764ff5672 100644
--- a/spec/services/merge_requests/merge_to_ref_service_spec.rb
+++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb
@@ -74,7 +74,7 @@ RSpec.describe MergeRequests::MergeToRefService do
describe '#execute' do
let(:service) do
- described_class.new(project, user, **params)
+ described_class.new(project: project, current_user: user, params: params)
end
let(:params) { { commit_message: 'Awesome message', should_remove_source_branch: true, sha: merge_request.diff_head_sha } }
@@ -94,7 +94,7 @@ RSpec.describe MergeRequests::MergeToRefService do
it 'returns an error when Gitlab::Git::CommandError is raised during merge' do
allow(project.repository).to receive(:merge_to_ref) do
- raise Gitlab::Git::CommandError.new('Failed to create merge commit')
+ raise Gitlab::Git::CommandError, 'Failed to create merge commit'
end
result = service.execute(merge_request)
@@ -111,11 +111,11 @@ RSpec.describe MergeRequests::MergeToRefService do
end
let(:merge_ref_service) do
- described_class.new(project, user, {})
+ described_class.new(project: project, current_user: user)
end
let(:merge_service) do
- MergeRequests::MergeService.new(project, user, { sha: merge_request.diff_head_sha })
+ MergeRequests::MergeService.new(project: project, current_user: user, params: { sha: merge_request.diff_head_sha })
end
context 'when merge commit' do
diff --git a/spec/services/merge_requests/mergeability_check_service_spec.rb b/spec/services/merge_requests/mergeability_check_service_spec.rb
index e0baf5af8b4..65599b7e046 100644
--- a/spec/services/merge_requests/mergeability_check_service_spec.rb
+++ b/spec/services/merge_requests/mergeability_check_service_spec.rb
@@ -87,7 +87,7 @@ RSpec.describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shar
described_class.new(merge_request).async_execute
end
- context 'when read only DB' do
+ context 'when read-only DB' do
before do
allow(Gitlab::Database).to receive(:read_only?) { true }
end
@@ -232,7 +232,7 @@ RSpec.describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shar
context 'when MR cannot be merged and has outdated merge ref' do
before do
- MergeRequests::MergeToRefService.new(project, merge_request.author).execute(merge_request)
+ MergeRequests::MergeToRefService.new(project: project, current_user: merge_request.author).execute(merge_request)
merge_request.mark_as_unmergeable!
end
@@ -258,7 +258,7 @@ RSpec.describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shar
end
end
- context 'when read only DB' do
+ context 'when read-only DB' do
it 'returns ServiceResponse.error' do
allow(Gitlab::Database).to receive(:read_only?) { true }
@@ -332,7 +332,7 @@ RSpec.describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shar
context 'when MR is mergeable but merge-ref is already updated' do
before do
- MergeRequests::MergeToRefService.new(project, merge_request.author).execute(merge_request)
+ MergeRequests::MergeToRefService.new(project: project, current_user: merge_request.author).execute(merge_request)
merge_request.mark_as_mergeable!
end
@@ -361,7 +361,7 @@ RSpec.describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shar
context 'merge with conflicts' do
it 'calls MergeToRefService with true allow_conflicts param' do
expect(MergeRequests::MergeToRefService).to receive(:new)
- .with(project, merge_request.author, { allow_conflicts: true }).and_call_original
+ .with(project: project, current_user: merge_request.author, params: { allow_conflicts: true }).and_call_original
subject
end
@@ -373,7 +373,7 @@ RSpec.describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shar
it 'calls MergeToRefService with false allow_conflicts param' do
expect(MergeRequests::MergeToRefService).to receive(:new)
- .with(project, merge_request.author, { allow_conflicts: false }).and_call_original
+ .with(project: project, current_user: merge_request.author, params: { allow_conflicts: false }).and_call_original
subject
end
diff --git a/spec/services/merge_requests/post_merge_service_spec.rb b/spec/services/merge_requests/post_merge_service_spec.rb
index 247b053e729..14804aa33d4 100644
--- a/spec/services/merge_requests/post_merge_service_spec.rb
+++ b/spec/services/merge_requests/post_merge_service_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe MergeRequests::PostMergeService do
let_it_be(:merge_request, reload: true) { create(:merge_request, assignees: [user]) }
let_it_be(:project) { merge_request.project }
- subject { described_class.new(project, user).execute(merge_request) }
+ subject { described_class.new(project: project, current_user: user).execute(merge_request) }
before do
project.add_maintainer(user)
@@ -22,7 +22,6 @@ RSpec.describe MergeRequests::PostMergeService do
it 'refreshes the number of open merge requests for a valid MR', :use_clean_rails_memory_store_caching do
# Cache the counter before the MR changed state.
project.open_merge_requests_count
- merge_request.update!(state: 'merged')
expect { subject }.to change { project.open_merge_requests_count }.from(1).to(0)
end
diff --git a/spec/services/merge_requests/push_options_handler_service_spec.rb b/spec/services/merge_requests/push_options_handler_service_spec.rb
index b5086ea3a82..87c3fc6a2d8 100644
--- a/spec/services/merge_requests/push_options_handler_service_spec.rb
+++ b/spec/services/merge_requests/push_options_handler_service_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe MergeRequests::PushOptionsHandlerService do
let_it_be(:user3) { create(:user, developer_projects: [project]) }
let_it_be(:forked_project) { fork_project(project, user1, repository: true) }
- let(:service) { described_class.new(project, user1, changes, push_options) }
+ let(:service) { described_class.new(project: project, current_user: user1, changes: changes, push_options: push_options) }
let(:source_branch) { 'fix' }
let(:target_branch) { 'feature' }
let(:title) { 'my title' }
diff --git a/spec/services/merge_requests/pushed_branches_service_spec.rb b/spec/services/merge_requests/pushed_branches_service_spec.rb
index cd6af4c275e..59424263ec5 100644
--- a/spec/services/merge_requests/pushed_branches_service_spec.rb
+++ b/spec/services/merge_requests/pushed_branches_service_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe MergeRequests::PushedBranchesService do
let(:project) { create(:project) }
- let!(:service) { described_class.new(project, nil, changes: pushed_branches) }
+ let!(:service) { described_class.new(project: project, current_user: nil, params: { changes: pushed_branches }) }
context 'when branches pushed' do
let(:pushed_branches) do
diff --git a/spec/services/merge_requests/rebase_service_spec.rb b/spec/services/merge_requests/rebase_service_spec.rb
index 653fcf12a76..a46f3cf6148 100644
--- a/spec/services/merge_requests/rebase_service_spec.rb
+++ b/spec/services/merge_requests/rebase_service_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe MergeRequests::RebaseService do
let(:repository) { project.repository.raw }
let(:skip_ci) { false }
- subject(:service) { described_class.new(project, user, {}) }
+ subject(:service) { described_class.new(project: project, current_user: user) }
before do
project.add_maintainer(user)
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index f9b76db877b..6e6b4a91e0d 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -64,7 +64,7 @@ RSpec.describe MergeRequests::RefreshService do
end
context 'push to origin repo source branch' do
- let(:refresh_service) { service.new(@project, @user) }
+ let(:refresh_service) { service.new(project: @project, current_user: @user) }
let(:notification_service) { spy('notification_service') }
before do
@@ -187,7 +187,7 @@ RSpec.describe MergeRequests::RefreshService do
context 'when pipeline exists for the source branch' do
let!(:pipeline) { create(:ci_empty_pipeline, ref: @merge_request.source_branch, project: @project, sha: @commits.first.sha)}
- subject { service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master') }
+ subject { service.new(project: @project, current_user: @user).execute(@oldrev, @newrev, 'refs/heads/master') }
it 'updates the head_pipeline_id for @merge_request', :sidekiq_might_not_need_inline do
expect { subject }.to change { @merge_request.reload.head_pipeline_id }.from(nil).to(pipeline.id)
@@ -198,12 +198,12 @@ RSpec.describe MergeRequests::RefreshService do
end
end
- shared_examples 'Pipelines for merge requests' do
+ context 'Pipelines for merge requests', :sidekiq_inline do
before do
stub_ci_pipeline_yaml_file(config)
end
- subject { service.new(project, @user).execute(@oldrev, @newrev, ref) }
+ subject { service.new(project: project, current_user: @user).execute(@oldrev, @newrev, ref) }
let(:ref) { 'refs/heads/master' }
let(:project) { @project }
@@ -291,11 +291,11 @@ RSpec.describe MergeRequests::RefreshService do
context "when MergeRequestUpdateWorker is retried by an exception" do
it 'does not re-create a duplicate detached merge request pipeline' do
expect do
- service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master')
+ service.new(project: @project, current_user: @user).execute(@oldrev, @newrev, 'refs/heads/master')
end.to change { @merge_request.pipelines_for_merge_request.count }.by(1)
expect do
- service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master')
+ service.new(project: @project, current_user: @user).execute(@oldrev, @newrev, 'refs/heads/master')
end.not_to change { @merge_request.pipelines_for_merge_request.count }
end
end
@@ -364,20 +364,8 @@ RSpec.describe MergeRequests::RefreshService do
end
end
- context 'when the code_review_async_pipeline_creation feature flag is on', :sidekiq_inline do
- it_behaves_like 'Pipelines for merge requests'
- end
-
- context 'when the code_review_async_pipeline_creation feature flag is off', :sidekiq_inline do
- before do
- stub_feature_flags(code_review_async_pipeline_creation: false)
- end
-
- it_behaves_like 'Pipelines for merge requests'
- end
-
context 'push to origin repo source branch' do
- let(:refresh_service) { service.new(@project, @user) }
+ let(:refresh_service) { service.new(project: @project, current_user: @user) }
let(:notification_service) { spy('notification_service') }
before do
@@ -409,7 +397,7 @@ RSpec.describe MergeRequests::RefreshService do
context 'push to origin repo target branch', :sidekiq_might_not_need_inline do
context 'when all MRs to the target branch had diffs' do
before do
- service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
+ service.new(project: @project, current_user: @user).execute(@oldrev, @newrev, 'refs/heads/feature')
reload_mrs
end
@@ -438,7 +426,7 @@ RSpec.describe MergeRequests::RefreshService do
# feature all along.
empty_fork_merge_request.update_columns(target_branch: 'feature')
- service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
+ service.new(project: @project, current_user: @user).execute(@oldrev, @newrev, 'refs/heads/feature')
reload_mrs
empty_fork_merge_request.reload
end
@@ -461,7 +449,7 @@ RSpec.describe MergeRequests::RefreshService do
# Merge master -> feature branch
@project.repository.merge(@user, @merge_request.diff_head_sha, @merge_request, 'Test message')
commit = @project.repository.commit('feature')
- service.new(@project, @user).execute(@oldrev, commit.id, 'refs/heads/feature')
+ service.new(project: @project, current_user: @user).execute(@oldrev, commit.id, 'refs/heads/feature')
reload_mrs
end
@@ -479,7 +467,7 @@ RSpec.describe MergeRequests::RefreshService do
end
context 'push to fork repo source branch', :sidekiq_might_not_need_inline do
- let(:refresh_service) { service.new(@fork_project, @user) }
+ let(:refresh_service) { service.new(project: @fork_project, current_user: @user) }
def refresh
allow(refresh_service).to receive(:execute_hooks)
@@ -546,7 +534,7 @@ RSpec.describe MergeRequests::RefreshService do
context 'push to fork repo target branch', :sidekiq_might_not_need_inline do
describe 'changes to merge requests' do
before do
- service.new(@fork_project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
+ service.new(project: @fork_project, current_user: @user).execute(@oldrev, @newrev, 'refs/heads/feature')
reload_mrs
end
@@ -563,7 +551,7 @@ RSpec.describe MergeRequests::RefreshService do
describe 'merge request diff' do
it 'does not reload the diff of the merge request made from fork' do
expect do
- service.new(@fork_project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
+ service.new(project: @fork_project, current_user: @user).execute(@oldrev, @newrev, 'refs/heads/feature')
end.not_to change { @fork_merge_request.reload.merge_request_diff }
end
end
@@ -594,28 +582,28 @@ RSpec.describe MergeRequests::RefreshService do
it 'reloads a new diff for a push to the forked project' do
expect do
- service.new(@fork_project, @user).execute(@oldrev, first_commit, 'refs/heads/master')
+ service.new(project: @fork_project, current_user: @user).execute(@oldrev, first_commit, 'refs/heads/master')
reload_mrs
end.to change { forked_master_mr.merge_request_diffs.count }.by(1)
end
it 'reloads a new diff for a force push to the source branch' do
expect do
- service.new(@fork_project, @user).execute(@oldrev, force_push_commit, 'refs/heads/master')
+ service.new(project: @fork_project, current_user: @user).execute(@oldrev, force_push_commit, 'refs/heads/master')
reload_mrs
end.to change { forked_master_mr.merge_request_diffs.count }.by(1)
end
it 'reloads a new diff for a force push to the target branch' do
expect do
- service.new(@project, @user).execute(@oldrev, force_push_commit, 'refs/heads/master')
+ service.new(project: @project, current_user: @user).execute(@oldrev, force_push_commit, 'refs/heads/master')
reload_mrs
end.to change { forked_master_mr.merge_request_diffs.count }.by(1)
end
it 'reloads a new diff for a push to the target project that contains a commit in the MR' do
expect do
- service.new(@project, @user).execute(@oldrev, first_commit, 'refs/heads/master')
+ service.new(project: @project, current_user: @user).execute(@oldrev, first_commit, 'refs/heads/master')
reload_mrs
end.to change { forked_master_mr.merge_request_diffs.count }.by(1)
end
@@ -626,7 +614,7 @@ RSpec.describe MergeRequests::RefreshService do
branch_name: 'master')
expect do
- service.new(@project, @user).execute(@newrev, new_commit, 'refs/heads/master')
+ service.new(project: @project, current_user: @user).execute(@newrev, new_commit, 'refs/heads/master')
reload_mrs
end.not_to change { forked_master_mr.merge_request_diffs.count }
end
@@ -635,7 +623,7 @@ RSpec.describe MergeRequests::RefreshService do
context 'push to origin repo target branch after fork project was removed' do
before do
@fork_project.destroy!
- service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
+ service.new(project: @project, current_user: @user).execute(@oldrev, @newrev, 'refs/heads/feature')
reload_mrs
end
@@ -651,7 +639,7 @@ RSpec.describe MergeRequests::RefreshService do
end
context 'push new branch that exists in a merge request' do
- let(:refresh_service) { service.new(@fork_project, @user) }
+ let(:refresh_service) { service.new(project: @fork_project, current_user: @user) }
it 'refreshes the merge request', :sidekiq_might_not_need_inline do
expect(refresh_service).to receive(:execute_hooks)
@@ -700,7 +688,7 @@ RSpec.describe MergeRequests::RefreshService do
source_branch: 'close-by-commit',
source_project: project)
- refresh_service = service.new(project, user)
+ refresh_service = service.new(project: project, current_user: user)
allow(refresh_service).to receive(:execute_hooks)
refresh_service.execute(@oldrev, @newrev, 'refs/heads/close-by-commit')
@@ -723,7 +711,7 @@ RSpec.describe MergeRequests::RefreshService do
source_branch: 'close-by-commit',
source_project: forked_project)
- refresh_service = service.new(forked_project, user)
+ refresh_service = service.new(project: forked_project, current_user: user)
allow(refresh_service).to receive(:execute_hooks)
refresh_service.execute(@oldrev, @newrev, 'refs/heads/close-by-commit')
@@ -734,7 +722,7 @@ RSpec.describe MergeRequests::RefreshService do
end
context 'marking the merge request as draft' do
- let(:refresh_service) { service.new(@project, @user) }
+ let(:refresh_service) { service.new(project: @project, current_user: @user) }
before do
allow(refresh_service).to receive(:execute_hooks)
@@ -814,7 +802,7 @@ RSpec.describe MergeRequests::RefreshService do
end
describe 'updating merge_commit' do
- let(:service) { described_class.new(project, user) }
+ let(:service) { described_class.new(project: project, current_user: user) }
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
@@ -902,7 +890,7 @@ RSpec.describe MergeRequests::RefreshService do
end
let(:auto_merge_strategy) { AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS }
- let(:refresh_service) { service.new(project, user) }
+ let(:refresh_service) { service.new(project: project, current_user: user) }
before do
target_project.merge_method = merge_method
diff --git a/spec/services/merge_requests/reload_merge_head_diff_service_spec.rb b/spec/services/merge_requests/reload_merge_head_diff_service_spec.rb
index 3152a4e3861..b333d4af6cf 100644
--- a/spec/services/merge_requests/reload_merge_head_diff_service_spec.rb
+++ b/spec/services/merge_requests/reload_merge_head_diff_service_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe MergeRequests::ReloadMergeHeadDiffService do
describe '#execute' do
before do
MergeRequests::MergeToRefService
- .new(merge_request.project, merge_request.author)
+ .new(project: merge_request.project, current_user: merge_request.author)
.execute(merge_request)
end
diff --git a/spec/services/merge_requests/remove_approval_service_spec.rb b/spec/services/merge_requests/remove_approval_service_spec.rb
index 4ef2da290e1..ef6a0ec69bd 100644
--- a/spec/services/merge_requests/remove_approval_service_spec.rb
+++ b/spec/services/merge_requests/remove_approval_service_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe MergeRequests::RemoveApprovalService do
let(:merge_request) { create(:merge_request, source_project: project) }
let!(:existing_approval) { create(:approval, merge_request: merge_request) }
- subject(:service) { described_class.new(project, user) }
+ subject(:service) { described_class.new(project: project, current_user: user) }
def execute!
service.execute(merge_request)
diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb
index 8541d597581..b9df31b6727 100644
--- a/spec/services/merge_requests/reopen_service_spec.rb
+++ b/spec/services/merge_requests/reopen_service_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe MergeRequests::ReopenService do
it_behaves_like 'merge request reviewers cache counters invalidator'
context 'valid params' do
- let(:service) { described_class.new(project, user, {}) }
+ let(:service) { described_class.new(project: project, current_user: user) }
before do
allow(service).to receive(:execute_hooks)
@@ -65,7 +65,7 @@ RSpec.describe MergeRequests::ReopenService do
it 'caches merge request closing issues' do
expect(merge_request).to receive(:cache_merge_request_closes_issues!)
- described_class.new(project, user, {}).execute(merge_request)
+ described_class.new(project: project, current_user: user).execute(merge_request)
end
it 'updates metrics' do
@@ -78,7 +78,7 @@ RSpec.describe MergeRequests::ReopenService do
expect(service).to receive(:reopen)
- described_class.new(project, user, {}).execute(merge_request)
+ described_class.new(project: project, current_user: user).execute(merge_request)
end
it 'calls the merge request activity counter' do
@@ -86,11 +86,11 @@ RSpec.describe MergeRequests::ReopenService do
.to receive(:track_reopen_mr_action)
.with(user: user)
- described_class.new(project, user, {}).execute(merge_request)
+ described_class.new(project: project, current_user: user).execute(merge_request)
end
it 'refreshes the number of open merge requests for a valid MR' do
- service = described_class.new(project, user, {})
+ service = described_class.new(project: project, current_user: user)
expect { service.execute(merge_request) }
.to change { project.open_merge_requests_count }.from(0).to(1)
@@ -99,7 +99,7 @@ RSpec.describe MergeRequests::ReopenService do
context 'current user is not authorized to reopen merge request' do
before do
perform_enqueued_jobs do
- @merge_request = described_class.new(project, guest).execute(merge_request)
+ @merge_request = described_class.new(project: project, current_user: guest).execute(merge_request)
end
end
diff --git a/spec/services/merge_requests/request_review_service_spec.rb b/spec/services/merge_requests/request_review_service_spec.rb
index 5cb4120852a..8bc31df605c 100644
--- a/spec/services/merge_requests/request_review_service_spec.rb
+++ b/spec/services/merge_requests/request_review_service_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe MergeRequests::RequestReviewService do
let(:merge_request) { create(:merge_request, reviewers: [user]) }
let(:reviewer) { merge_request.find_reviewer(user) }
let(:project) { merge_request.project }
- let(:service) { described_class.new(project, current_user) }
+ let(:service) { described_class.new(project: project, current_user: current_user) }
let(:result) { service.execute(merge_request, user) }
let(:todo_service) { spy('todo service') }
let(:notification_service) { spy('notification service') }
@@ -26,7 +26,7 @@ RSpec.describe MergeRequests::RequestReviewService do
describe '#execute' do
describe 'invalid permissions' do
- let(:service) { described_class.new(project, create(:user)) }
+ let(:service) { described_class.new(project: project, current_user: create(:user)) }
it 'returns an error' do
expect(result[:status]).to eq :error
diff --git a/spec/services/merge_requests/resolve_todos_service_spec.rb b/spec/services/merge_requests/resolve_todos_service_spec.rb
index 3e6f2ea3f5d..53bd259f0f4 100644
--- a/spec/services/merge_requests/resolve_todos_service_spec.rb
+++ b/spec/services/merge_requests/resolve_todos_service_spec.rb
@@ -23,18 +23,6 @@ RSpec.describe MergeRequests::ResolveTodosService do
async_execute
end
-
- context 'when resolve_merge_request_todos_async feature is disabled' do
- before do
- stub_feature_flags(resolve_merge_request_todos_async: false)
- end
-
- it 'calls #execute' do
- expect(service).to receive(:execute)
-
- async_execute
- end
- end
end
describe '#execute' do
diff --git a/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb b/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb
index 874cf66659a..74f3a1b06fc 100644
--- a/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb
+++ b/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe MergeRequests::ResolvedDiscussionNotificationService do
let(:user) { create(:user) }
let(:project) { merge_request.project }
- subject { described_class.new(project, user) }
+ subject { described_class.new(project: project, current_user: user) }
describe "#execute" do
context "when not all discussions are resolved" do
diff --git a/spec/services/merge_requests/retarget_chain_service_spec.rb b/spec/services/merge_requests/retarget_chain_service_spec.rb
index 3937fbe58c3..87bde4a1400 100644
--- a/spec/services/merge_requests/retarget_chain_service_spec.rb
+++ b/spec/services/merge_requests/retarget_chain_service_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe MergeRequests::RetargetChainService do
let_it_be(:merge_request, reload: true) { create(:merge_request, assignees: [user]) }
let_it_be(:project) { merge_request.project }
- subject { described_class.new(project, user).execute(merge_request) }
+ subject { described_class.new(project: project, current_user: user).execute(merge_request) }
before do
project.add_maintainer(user)
diff --git a/spec/services/merge_requests/squash_service_spec.rb b/spec/services/merge_requests/squash_service_spec.rb
index acbd0a42fcd..149748cdabc 100644
--- a/spec/services/merge_requests/squash_service_spec.rb
+++ b/spec/services/merge_requests/squash_service_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe MergeRequests::SquashService do
include GitHelpers
- let(:service) { described_class.new(project, user, { merge_request: merge_request }) }
+ let(:service) { described_class.new(project: project, current_user: user, params: { merge_request: merge_request }) }
let(:user) { project.owner }
let(:project) { create(:project, :repository) }
let(:repository) { project.repository.raw }
@@ -62,7 +62,7 @@ RSpec.describe MergeRequests::SquashService do
end
it 'will still perform the squash when a custom squash commit message has been provided' do
- service = described_class.new(project, user, { merge_request: merge_request, squash_commit_message: 'A custom commit message' })
+ service = described_class.new(project: project, current_user: user, params: { merge_request: merge_request, squash_commit_message: 'A custom commit message' })
expect(merge_request.target_project.repository).to receive(:squash).and_return('sha')
@@ -98,7 +98,7 @@ RSpec.describe MergeRequests::SquashService do
end
context 'if a message was provided' do
- let(:service) { described_class.new(project, user, { merge_request: merge_request, squash_commit_message: message }) }
+ let(:service) { described_class.new(project: project, current_user: user, params: { merge_request: merge_request, squash_commit_message: message }) }
let(:message) { 'My custom message' }
let(:squash_sha) { service.execute[:squash_sha] }
diff --git a/spec/services/merge_requests/update_assignees_service_spec.rb b/spec/services/merge_requests/update_assignees_service_spec.rb
index de03aab5418..076161c9029 100644
--- a/spec/services/merge_requests/update_assignees_service_spec.rb
+++ b/spec/services/merge_requests/update_assignees_service_spec.rb
@@ -26,7 +26,7 @@ RSpec.describe MergeRequests::UpdateAssigneesService do
project.add_developer(user3)
end
- let(:service) { described_class.new(project, user, opts) }
+ let(:service) { described_class.new(project: project, current_user: user, params: opts) }
let(:opts) { { assignee_ids: [user2.id] } }
describe 'execute' do
@@ -36,8 +36,24 @@ RSpec.describe MergeRequests::UpdateAssigneesService do
end
context 'when the parameters are valid' do
+ context 'when using sentinel values' do
+ let(:opts) { { assignee_ids: [0] } }
+
+ it 'removes all assignees' do
+ expect { update_merge_request }.to change(merge_request, :assignees).to([])
+ end
+ end
+
+ context 'the assignee_ids parameter is the empty list' do
+ let(:opts) { { assignee_ids: [] } }
+
+ it 'removes all assignees' do
+ expect { update_merge_request }.to change(merge_request, :assignees).to([])
+ end
+ end
+
it 'updates the MR, and queues the more expensive work for later' do
- expect_next(MergeRequests::HandleAssigneesChangeService, project, user) do |service|
+ expect_next(MergeRequests::HandleAssigneesChangeService, project: project, current_user: user) do |service|
expect(service)
.to receive(:async_execute)
.with(merge_request, [user3], execute_hooks: true)
@@ -56,7 +72,7 @@ RSpec.describe MergeRequests::UpdateAssigneesService do
end
it 'is more efficient than using the full update-service' do
- allow_next(MergeRequests::HandleAssigneesChangeService, project, user) do |service|
+ allow_next(MergeRequests::HandleAssigneesChangeService, project: project, current_user: user) do |service|
expect(service)
.to receive(:async_execute)
.with(merge_request, [user3], execute_hooks: true)
@@ -69,7 +85,7 @@ RSpec.describe MergeRequests::UpdateAssigneesService do
source_project: merge_request.project,
author: merge_request.author)
- update_service = ::MergeRequests::UpdateService.new(project, user, opts)
+ update_service = ::MergeRequests::UpdateService.new(project: project, current_user: user, params: opts)
expect { service.execute(merge_request) }
.to issue_fewer_queries_than { update_service.execute(other_mr) }
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 8c010855eb2..a85fbd77d70 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -43,7 +43,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
end
def update_merge_request(opts)
- @merge_request = MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
+ @merge_request = MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request)
@merge_request.reload
end
@@ -64,7 +64,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
}
end
- let(:service) { described_class.new(project, current_user, opts) }
+ let(:service) { described_class.new(project: project, current_user: current_user, params: opts) }
let(:current_user) { user }
before do
@@ -99,7 +99,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
.to receive(:track_description_edit_action).once.with(user: user)
- MergeRequests::UpdateService.new(project, user, opts).execute(merge_request2)
+ MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request2)
end
it 'tracks Draft/WIP marking' do
@@ -108,7 +108,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
opts[:title] = "WIP: #{opts[:title]}"
- MergeRequests::UpdateService.new(project, user, opts).execute(merge_request2)
+ MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request2)
end
it 'tracks Draft/WIP un-marking' do
@@ -117,7 +117,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
opts[:title] = "Non-draft/wip title string"
- MergeRequests::UpdateService.new(project, user, opts).execute(draft_merge_request)
+ MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(draft_merge_request)
end
context 'when MR is locked' do
@@ -128,7 +128,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
opts[:discussion_locked] = true
- MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
+ MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request)
end
end
@@ -139,7 +139,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
opts[:discussion_locked] = false
- MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
+ MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request)
end
end
end
@@ -154,7 +154,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
opts[:discussion_locked] = false
- MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
+ MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request)
end
end
@@ -165,7 +165,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
opts[:discussion_locked] = true
- MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
+ MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request)
end
end
end
@@ -184,7 +184,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
spent_at: Date.parse('2021-02-24')
}
- MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
+ MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request)
end
it 'tracks milestone change' do
@@ -193,7 +193,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
opts[:milestone] = milestone
- MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
+ MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request)
end
it 'track labels change' do
@@ -202,7 +202,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
opts[:label_ids] = [label2.id]
- MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
+ MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request)
end
context 'reviewers' do
@@ -213,7 +213,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
opts[:reviewers] = [user2]
- MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
+ MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request)
end
end
@@ -224,7 +224,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
opts[:reviewers] = merge_request.reviewers
- MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
+ MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request)
end
end
end
@@ -439,7 +439,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
let(:milestone) { create(:milestone, project: project) }
let(:req_opts) { { source_branch: 'feature', target_branch: 'master' } }
- subject { MergeRequests::UpdateService.new(project, user, opts).execute(merge_request) }
+ subject { MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request) }
context 'when mentionable attributes change' do
let(:opts) { { description: "Description with #{user.to_reference}" }.merge(req_opts) }
@@ -486,7 +486,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
}
end
- let(:service) { described_class.new(project, user, opts) }
+ let(:service) { described_class.new(project: project, current_user: user, params: opts) }
context 'without pipeline' do
before do
@@ -547,7 +547,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
context 'with a non-authorised user' do
let(:visitor) { create(:user) }
- let(:service) { described_class.new(project, visitor, opts) }
+ let(:service) { described_class.new(project: project, current_user: visitor, params: opts) }
before do
merge_request.update_attribute(:merge_error, 'Error')
@@ -805,7 +805,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
opts = { title: 'New title' }
perform_enqueued_jobs do
- @merge_request = described_class.new(project, user, opts).execute(merge_request)
+ @merge_request = described_class.new(project: project, current_user: user, params: opts).execute(merge_request)
end
should_email(subscriber)
@@ -818,7 +818,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
opts = { title: 'Draft: New title' }
perform_enqueued_jobs do
- @merge_request = described_class.new(project, user, opts).execute(merge_request)
+ @merge_request = described_class.new(project: project, current_user: user, params: opts).execute(merge_request)
end
should_not_email(subscriber)
@@ -840,7 +840,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
opts = { label_ids: [label.id] }
perform_enqueued_jobs do
- @merge_request = described_class.new(project, user, opts).execute(merge_request)
+ @merge_request = described_class.new(project: project, current_user: user, params: opts).execute(merge_request)
end
should_email(subscriber)
@@ -856,7 +856,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
opts = { label_ids: [label.id, label2.id] }
perform_enqueued_jobs do
- @merge_request = described_class.new(project, user, opts).execute(merge_request)
+ @merge_request = described_class.new(project: project, current_user: user, params: opts).execute(merge_request)
end
should_not_email(subscriber)
@@ -867,7 +867,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
opts = { label_ids: [label2.id] }
perform_enqueued_jobs do
- @merge_request = described_class.new(project, user, opts).execute(merge_request)
+ @merge_request = described_class.new(project: project, current_user: user, params: opts).execute(merge_request)
end
should_not_email(subscriber)
@@ -933,7 +933,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
it 'creates a `MergeRequestsClosingIssues` record for each issue' do
issue_closing_opts = { description: "Closes #{first_issue.to_reference} and #{second_issue.to_reference}" }
- service = described_class.new(project, user, issue_closing_opts)
+ service = described_class.new(project: project, current_user: user, params: issue_closing_opts)
allow(service).to receive(:execute_hooks)
service.execute(merge_request)
@@ -945,7 +945,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
create(:merge_requests_closing_issues, issue: first_issue, merge_request: merge_request)
create(:merge_requests_closing_issues, issue: second_issue, merge_request: merge_request)
- service = described_class.new(project, user, description: "not closing any issues")
+ service = described_class.new(project: project, current_user: user, params: { description: "not closing any issues" })
allow(service).to receive(:execute_hooks)
service.execute(merge_request.reload)
@@ -1002,7 +1002,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
it 'unassigns assignee when user id is 0' do
merge_request.update!(assignee_ids: [user.id])
- expect_next_instance_of(MergeRequests::HandleAssigneesChangeService, project, user) do |service|
+ expect_next_instance_of(MergeRequests::HandleAssigneesChangeService, project: project, current_user: user) do |service|
expect(service)
.to receive(:async_execute)
.with(merge_request, [user])
@@ -1014,7 +1014,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
end
it 'saves assignee when user id is valid' do
- expect_next_instance_of(MergeRequests::HandleAssigneesChangeService, project, user) do |service|
+ expect_next_instance_of(MergeRequests::HandleAssigneesChangeService, project: project, current_user: user) do |service|
expect(service)
.to receive(:async_execute)
.with(merge_request, [user3])
@@ -1052,6 +1052,35 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
end
end
+ context 'when adding time spent' do
+ let(:spend_time) { { duration: 1800, user_id: user3.id } }
+
+ context ':use_specialized_service' do
+ context 'when true' do
+ it 'passes the update action to ::MergeRequests::AddSpentTimeService' do
+ expect(::MergeRequests::AddSpentTimeService)
+ .to receive(:new).and_call_original
+
+ update_merge_request(spend_time: spend_time, use_specialized_service: true)
+ end
+ end
+
+ context 'when false or nil' do
+ before do
+ expect(::MergeRequests::AddSpentTimeService).not_to receive(:new)
+ end
+
+ it 'does not pass the update action to ::MergeRequests::UpdateAssigneesService when false' do
+ update_merge_request(spend_time: spend_time, use_specialized_service: false)
+ end
+
+ it 'does not pass the update action to ::MergeRequests::UpdateAssigneesService when nil' do
+ update_merge_request(spend_time: spend_time, use_specialized_service: nil)
+ end
+ end
+ end
+ end
+
include_examples 'issuable update service' do
let(:open_issuable) { merge_request }
let(:closed_issuable) { create(:closed_merge_request, source_project: project) }
@@ -1145,7 +1174,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
it_behaves_like 'issuable record that supports quick actions' do
let(:existing_merge_request) { create(:merge_request, source_project: project) }
- let(:issuable) { described_class.new(project, user, params).execute(existing_merge_request) }
+ let(:issuable) { described_class.new(project: project, current_user: user, params: params).execute(existing_merge_request) }
end
end
end
diff --git a/spec/services/namespaces/package_settings/update_service_spec.rb b/spec/services/namespaces/package_settings/update_service_spec.rb
index fa0c58e4c9b..030bc03038e 100644
--- a/spec/services/namespaces/package_settings/update_service_spec.rb
+++ b/spec/services/namespaces/package_settings/update_service_spec.rb
@@ -32,7 +32,9 @@ RSpec.describe ::Namespaces::PackageSettings::UpdateService do
end
shared_examples 'updating the namespace package setting' do
- it_behaves_like 'updating the namespace package setting attributes', from: { maven_duplicates_allowed: true, maven_duplicate_exception_regex: 'SNAPSHOT' }, to: { maven_duplicates_allowed: false, maven_duplicate_exception_regex: 'RELEASE' }
+ it_behaves_like 'updating the namespace package setting attributes',
+ from: { maven_duplicates_allowed: true, maven_duplicate_exception_regex: 'SNAPSHOT', generic_duplicates_allowed: true, generic_duplicate_exception_regex: 'foo' },
+ to: { maven_duplicates_allowed: false, maven_duplicate_exception_regex: 'RELEASE', generic_duplicates_allowed: false, generic_duplicate_exception_regex: 'bar' }
it_behaves_like 'returning a success'
@@ -60,7 +62,12 @@ RSpec.describe ::Namespaces::PackageSettings::UpdateService do
context 'with existing namespace package setting' do
let_it_be(:package_settings) { create(:namespace_package_setting, namespace: namespace) }
- let_it_be(:params) { { maven_duplicates_allowed: false, maven_duplicate_exception_regex: 'RELEASE' } }
+ let_it_be(:params) do
+ { maven_duplicates_allowed: false,
+ maven_duplicate_exception_regex: 'RELEASE',
+ generic_duplicates_allowed: false,
+ generic_duplicate_exception_regex: 'bar' }
+ end
where(:user_role, :shared_examples_name) do
:maintainer | 'updating the namespace package setting'
diff --git a/spec/services/notes/build_service_spec.rb b/spec/services/notes/build_service_spec.rb
index deeab66c4e9..b7b08390dcd 100644
--- a/spec/services/notes/build_service_spec.rb
+++ b/spec/services/notes/build_service_spec.rb
@@ -173,7 +173,7 @@ RSpec.describe Notes::BuildService do
let(:user) { create(:user) }
it 'returns `Discussion to reply to cannot be found` error' do
- expect(new_note.errors.first).to include("Discussion to reply to cannot be found")
+ expect(new_note.errors.added?(:base, "Discussion to reply to cannot be found")).to be true
end
end
end
diff --git a/spec/services/notes/copy_service_spec.rb b/spec/services/notes/copy_service_spec.rb
index fd44aa7cf40..d9b6bafd7ff 100644
--- a/spec/services/notes/copy_service_spec.rb
+++ b/spec/services/notes/copy_service_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Notes::CopyService do
let_it_be(:noteable) { create(:issue) }
it 'validates that we cannot copy notes to the same Noteable' do
- expect { described_class.new(noteable, noteable) }.to raise_error(ArgumentError)
+ expect { described_class.new(nil, noteable, noteable) }.to raise_error(ArgumentError)
end
end
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index d28cb118529..31263feb947 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -176,7 +176,7 @@ RSpec.describe Notes::CreateService do
end
it 'note is associated with a note diff file' do
- MergeRequests::MergeToRefService.new(merge_request.project, merge_request.author).execute(merge_request)
+ MergeRequests::MergeToRefService.new(project: merge_request.project, current_user: merge_request.author).execute(merge_request)
note = described_class.new(project_with_repo, user, new_opts).execute
diff --git a/spec/services/notes/quick_actions_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb
index c098500b78a..9692bb08379 100644
--- a/spec/services/notes/quick_actions_service_spec.rb
+++ b/spec/services/notes/quick_actions_service_spec.rb
@@ -103,6 +103,30 @@ RSpec.describe Notes::QuickActionsService do
expect(Timelog.last.note_id).to eq(note.id)
end
end
+
+ context 'adds a system note' do
+ context 'when not specifying a date' do
+ let(:note_text) { "/spend 1h" }
+
+ it 'does not include the date' do
+ _, update_params = service.execute(note)
+ service.apply_updates(update_params, note)
+
+ expect(Note.last.note).to eq('added 1h of time spent')
+ end
+ end
+
+ context 'when specifying a date' do
+ let(:note_text) { "/spend 1h 2020-01-01" }
+
+ it 'does include the date' do
+ _, update_params = service.execute(note)
+ service.apply_updates(update_params, note)
+
+ expect(Note.last.note).to eq('added 1h of time spent at 2020-01-01')
+ end
+ end
+ end
end
end
@@ -214,25 +238,25 @@ RSpec.describe Notes::QuickActionsService do
end
end
- describe '.noteable_update_service' do
+ describe '.noteable_update_service_class' do
include_context 'note on noteable'
it 'returns Issues::UpdateService for a note on an issue' do
note = create(:note_on_issue, project: project)
- expect(described_class.noteable_update_service(note)).to eq(Issues::UpdateService)
+ expect(described_class.noteable_update_service_class(note)).to eq(Issues::UpdateService)
end
it 'returns MergeRequests::UpdateService for a note on a merge request' do
note = create(:note_on_merge_request, project: project)
- expect(described_class.noteable_update_service(note)).to eq(MergeRequests::UpdateService)
+ expect(described_class.noteable_update_service_class(note)).to eq(MergeRequests::UpdateService)
end
it 'returns Commits::TagService for a note on a commit' do
note = create(:note_on_commit, project: project)
- expect(described_class.noteable_update_service(note)).to eq(Commits::TagService)
+ expect(described_class.noteable_update_service_class(note)).to eq(Commits::TagService)
end
end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 6eff768eac2..c3a0766cb17 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -412,7 +412,7 @@ RSpec.describe NotificationService, :mailer do
it_should_not_email!
end
- context 'do exist' do
+ context 'do exist and note not confidential' do
let!(:issue_email_participant) { issue.issue_email_participants.create!(email: 'service.desk@example.com') }
before do
@@ -422,6 +422,18 @@ RSpec.describe NotificationService, :mailer do
it_should_email!
end
+
+ context 'do exist and note is confidential' do
+ let(:note) { create(:note, noteable: issue, project: project, confidential: true) }
+ let!(:issue_email_participant) { issue.issue_email_participants.create!(email: 'service.desk@example.com') }
+
+ before do
+ issue.update!(external_author: 'service.desk@example.com')
+ project.update!(service_desk_enabled: true)
+ end
+
+ it_should_not_email!
+ end
end
describe '#new_note' do
diff --git a/spec/services/packages/debian/generate_distribution_key_service_spec.rb b/spec/services/packages/debian/generate_distribution_key_service_spec.rb
new file mode 100644
index 00000000000..b31830c2d3b
--- /dev/null
+++ b/spec/services/packages/debian/generate_distribution_key_service_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Debian::GenerateDistributionKeyService do
+ let_it_be(:user) { create(:user) }
+
+ let(:params) { {} }
+
+ subject { described_class.new(current_user: user, params: params) }
+
+ let(:response) { subject.execute }
+
+ context 'with a user' do
+ it 'returns an Hash', :aggregate_failures do
+ expect(GPGME::Ctx).to receive(:new).with(armor: true, offline: true).and_call_original
+ expect(User).to receive(:random_password).with(no_args).and_call_original
+
+ expect(response).to be_a Hash
+ expect(response.keys).to contain_exactly(:private_key, :public_key, :fingerprint, :passphrase)
+ expect(response[:private_key]).to start_with('-----BEGIN PGP PRIVATE KEY BLOCK-----')
+ expect(response[:public_key]).to start_with('-----BEGIN PGP PUBLIC KEY BLOCK-----')
+ expect(response[:fingerprint].length).to eq(40)
+ expect(response[:passphrase].length).to be > 10
+ end
+ end
+
+ context 'without a user' do
+ let(:user) { nil }
+
+ it 'raises an ArgumentError' do
+ expect { response }.to raise_error(ArgumentError, 'Please provide a user')
+ end
+ end
+end
diff --git a/spec/services/packages/debian/generate_distribution_service_spec.rb b/spec/services/packages/debian/generate_distribution_service_spec.rb
new file mode 100644
index 00000000000..0547d18c8bc
--- /dev/null
+++ b/spec/services/packages/debian/generate_distribution_service_spec.rb
@@ -0,0 +1,182 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Debian::GenerateDistributionService do
+ let_it_be(:group) { create(:group, :public) }
+ let_it_be(:project) { create(:project, :public, group: group) }
+ let_it_be(:project_distribution) { create("debian_project_distribution", container: project, codename: 'unstable', valid_time_duration_seconds: 48.hours.to_i) }
+
+ let_it_be(:incoming) { create(:debian_incoming, project: project) }
+
+ before_all do
+ ::Packages::Debian::ProcessChangesService.new(incoming.package_files.last, nil).execute
+ end
+
+ let(:service) { described_class.new(distribution) }
+
+ describe '#execute' do
+ subject { service.execute }
+
+ shared_examples 'Generate Distribution' do |container_type|
+ context "for #{container_type}" do
+ if container_type == :group
+ let_it_be(:container) { group }
+ let_it_be(:distribution, reload: true) { create('debian_group_distribution', container: group, codename: 'unstable', valid_time_duration_seconds: 48.hours.to_i) }
+ else
+ let_it_be(:container) { project }
+ let_it_be(:distribution, reload: true) { project_distribution }
+ end
+
+ context 'with components and architectures' do
+ let_it_be(:component_main ) { create("debian_#{container_type}_component", distribution: distribution, name: 'main') }
+ let_it_be(:component_contrib) { create("debian_#{container_type}_component", distribution: distribution, name: 'contrib') }
+
+ let_it_be(:architecture_all ) { create("debian_#{container_type}_architecture", distribution: distribution, name: 'all') }
+ let_it_be(:architecture_amd64) { create("debian_#{container_type}_architecture", distribution: distribution, name: 'amd64') }
+ let_it_be(:architecture_arm64) { create("debian_#{container_type}_architecture", distribution: distribution, name: 'arm64') }
+
+ let_it_be(:component_file1) { create("debian_#{container_type}_component_file", component: component_main, architecture: architecture_all, created_at: '2020-01-24T09:00:00.000Z') } # destroyed
+ let_it_be(:component_file2) { create("debian_#{container_type}_component_file", component: component_main, architecture: architecture_amd64, created_at: '2020-01-24T10:29:59.000Z') } # destroyed
+ let_it_be(:component_file3) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_all, created_at: '2020-01-24T10:30:00.000Z') } # kept
+ let_it_be(:component_file4) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_amd64, created_at: '2020-01-24T11:30:00.000Z') } # kept
+
+ def check_component_file(component_name, component_file_type, architecture_name, expected_content)
+ component_file = distribution
+ .component_files
+ .with_component_name(component_name)
+ .with_file_type(component_file_type)
+ .with_architecture_name(architecture_name)
+ .last
+
+ expect(component_file).not_to be_nil
+ expect(component_file.file.exists?).to eq(!expected_content.nil?)
+
+ unless expected_content.nil?
+ component_file.file.use_file do |file_path|
+ expect(File.read(file_path)).to eq(expected_content)
+ end
+ end
+ end
+
+ it 'updates distribution and component files', :aggregate_failures do
+ travel_to(Time.utc(2020, 01, 25, 15, 17, 18, 123456)) do
+ expect(Gitlab::ErrorTracking).not_to receive(:log_exception)
+
+ expect { subject }
+ .to not_change { Packages::Package.count }
+ .and not_change { Packages::PackageFile.count }
+ .and change { distribution.component_files.count }.from(4).to(2 + 6)
+
+ expected_main_amd64_content = <<~EOF
+ Package: libsample0
+ Source: sample
+ Version: 1.2.3~alpha2
+ Installed-Size: 7
+ Maintainer: John Doe <john.doe@example.com>
+ Architecture: amd64
+ Description: Some mostly empty lib
+ Used in GitLab tests.
+ .
+ Testing another paragraph.
+ Multi-Arch: same
+ Homepage: https://gitlab.com/
+ Section: libs
+ Priority: optional
+ Filename: pool/unstable/#{project.id}/s/sample/libsample0_1.2.3~alpha2_amd64.deb
+ Size: 409600
+ MD5sum: fb0842b21adc44207996296fe14439dd
+ SHA256: 1c383a525bfcba619c7305ccd106d61db501a6bbaf0003bf8d0c429fbdb7fcc1
+
+ Package: sample-dev
+ Source: sample (1.2.3~alpha2)
+ Version: 1.2.3~binary
+ Installed-Size: 7
+ Maintainer: John Doe <john.doe@example.com>
+ Architecture: amd64
+ Depends: libsample0 (= 1.2.3~binary)
+ Description: Some mostly empty developpement files
+ Used in GitLab tests.
+ .
+ Testing another paragraph.
+ Multi-Arch: same
+ Homepage: https://gitlab.com/
+ Section: libdevel
+ Priority: optional
+ Filename: pool/unstable/#{project.id}/s/sample/sample-dev_1.2.3~binary_amd64.deb
+ Size: 409600
+ MD5sum: d2afbd28e4d74430d22f9504e18bfdf5
+ SHA256: 9fbeee2191ce4dab5288fad5ecac1bd369f58fef9a992a880eadf0caf25f086d
+ EOF
+
+ check_component_file('main', :packages, 'all', nil)
+ check_component_file('main', :packages, 'amd64', expected_main_amd64_content)
+ check_component_file('main', :packages, 'arm64', nil)
+
+ check_component_file('contrib', :packages, 'all', nil)
+ check_component_file('contrib', :packages, 'amd64', nil)
+ check_component_file('contrib', :packages, 'arm64', nil)
+
+ size = expected_main_amd64_content.length
+ md5sum = Digest::MD5.hexdigest(expected_main_amd64_content)
+ sha256 = Digest::SHA256.hexdigest(expected_main_amd64_content)
+
+ expected_release_content = <<~EOF
+ Codename: unstable
+ Date: Sat, 25 Jan 2020 15:17:18 +0000
+ Valid-Until: Mon, 27 Jan 2020 15:17:18 +0000
+ Architectures: all amd64 arm64
+ Components: contrib main
+ MD5Sum:
+ d41d8cd98f00b204e9800998ecf8427e 0 contrib/binary-all/Packages
+ d41d8cd98f00b204e9800998ecf8427e 0 contrib/binary-amd64/Packages
+ d41d8cd98f00b204e9800998ecf8427e 0 contrib/binary-arm64/Packages
+ d41d8cd98f00b204e9800998ecf8427e 0 main/binary-all/Packages
+ #{md5sum} #{size} main/binary-amd64/Packages
+ d41d8cd98f00b204e9800998ecf8427e 0 main/binary-arm64/Packages
+ SHA256:
+ e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-all/Packages
+ e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-amd64/Packages
+ e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-arm64/Packages
+ e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-all/Packages
+ #{sha256} #{size} main/binary-amd64/Packages
+ e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-arm64/Packages
+ EOF
+
+ distribution.file.use_file do |file_path|
+ expect(File.read(file_path)).to eq(expected_release_content)
+ end
+ end
+ end
+ end
+
+ context 'without components and architectures' do
+ it 'updates distribution and component files', :aggregate_failures do
+ travel_to(Time.utc(2020, 01, 25, 15, 17, 18, 123456)) do
+ expect(Gitlab::ErrorTracking).not_to receive(:log_exception)
+
+ expect { subject }
+ .to not_change { Packages::Package.count }
+ .and not_change { Packages::PackageFile.count }
+ .and not_change { distribution.component_files.count }
+
+ expected_release_content = <<~EOF
+ Codename: unstable
+ Date: Sat, 25 Jan 2020 15:17:18 +0000
+ Valid-Until: Mon, 27 Jan 2020 15:17:18 +0000
+ MD5Sum:
+ SHA256:
+ EOF
+
+ distribution.file.use_file do |file_path|
+ expect(File.read(file_path)).to eq(expected_release_content)
+ end
+ end
+ end
+ end
+ end
+ end
+
+ it_behaves_like 'Generate Distribution', :project
+ it_behaves_like 'Generate Distribution', :group
+ end
+end
diff --git a/spec/services/packages/debian/process_changes_service_spec.rb b/spec/services/packages/debian/process_changes_service_spec.rb
index 98b531bde10..f23471659bc 100644
--- a/spec/services/packages/debian/process_changes_service_spec.rb
+++ b/spec/services/packages/debian/process_changes_service_spec.rb
@@ -17,6 +17,7 @@ RSpec.describe Packages::Debian::ProcessChangesService do
.to change { Packages::Package.count }.from(1).to(2)
.and not_change { Packages::PackageFile.count }
.and change { incoming.package_files.count }.from(7).to(0)
+ .and change { package_file.debian_file_metadatum&.reload&.file_type }.from('unknown').to('changes')
created_package = Packages::Package.last
expect(created_package.name).to eq 'sample'
diff --git a/spec/services/packages/generic/create_package_file_service_spec.rb b/spec/services/packages/generic/create_package_file_service_spec.rb
index 10c54369f26..1c9eb53cfc7 100644
--- a/spec/services/packages/generic/create_package_file_service_spec.rb
+++ b/spec/services/packages/generic/create_package_file_service_spec.rb
@@ -6,13 +6,16 @@ RSpec.describe Packages::Generic::CreatePackageFileService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:pipeline) { create(:ci_pipeline, user: user) }
+ let_it_be(:file_name) { 'myfile.tar.gz.1' }
+
let(:build) { double('build', pipeline: pipeline) }
describe '#execute' do
+ let_it_be(:package) { create(:generic_package, project: project) }
+
let(:sha256) { '440e5e148a25331bbd7991575f7d54933c0ebf6cc735a18ee5066ac1381bb590' }
let(:temp_file) { Tempfile.new("test") }
let(:file) { UploadedFile.new(temp_file.path, sha256: sha256) }
- let(:package) { create(:generic_package, project: project) }
let(:package_service) { double }
let(:params) do
@@ -20,7 +23,7 @@ RSpec.describe Packages::Generic::CreatePackageFileService do
package_name: 'mypackage',
package_version: '0.0.1',
file: file,
- file_name: 'myfile.tar.gz.1',
+ file_name: file_name,
build: build
}
end
@@ -34,7 +37,7 @@ RSpec.describe Packages::Generic::CreatePackageFileService do
}
end
- subject { described_class.new(project, user, params).execute }
+ subject(:execute_service) { described_class.new(project, user, params).execute }
before do
FileUtils.touch(temp_file)
@@ -47,14 +50,14 @@ RSpec.describe Packages::Generic::CreatePackageFileService do
end
it 'creates package file', :aggregate_failures do
- expect { subject }.to change { package.package_files.count }.by(1)
+ expect { execute_service }.to change { package.package_files.count }.by(1)
.and change { Packages::PackageFileBuildInfo.count }.by(1)
package_file = package.package_files.last
aggregate_failures do
expect(package_file.package.status).to eq('default')
expect(package_file.package).to eq(package)
- expect(package_file.file_name).to eq('myfile.tar.gz.1')
+ expect(package_file.file_name).to eq(file_name)
expect(package_file.size).to eq(file.size)
expect(package_file.file_sha256).to eq(sha256)
end
@@ -65,7 +68,7 @@ RSpec.describe Packages::Generic::CreatePackageFileService do
let(:package_params) { super().merge(status: 'hidden') }
it 'updates an existing packages status' do
- expect { subject }.to change { package.package_files.count }.by(1)
+ expect { execute_service }.to change { package.package_files.count }.by(1)
.and change { Packages::PackageFileBuildInfo.count }.by(1)
package_file = package.package_files.last
@@ -76,5 +79,32 @@ RSpec.describe Packages::Generic::CreatePackageFileService do
end
it_behaves_like 'assigns build to package file'
+
+ context 'with existing package' do
+ before do
+ create(:package_file, package: package, file_name: file_name)
+ end
+
+ it { expect { execute_service }.to change { project.package_files.count }.by(1) }
+
+ context 'when duplicates are not allowed' do
+ before do
+ package.project.namespace.package_settings.update!(generic_duplicates_allowed: false)
+ end
+
+ it 'does not allow duplicates' do
+ expect { execute_service }.to raise_error(::Packages::DuplicatePackageError)
+ .and change { project.package_files.count }.by(0)
+ end
+
+ context 'when the package name matches the exception regex' do
+ before do
+ package.project.namespace.package_settings.update!(generic_duplicate_exception_regex: '.*')
+ end
+
+ it { expect { execute_service }.to change { project.package_files.count }.by(1) }
+ end
+ end
+ end
end
end
diff --git a/spec/services/packages/maven/find_or_create_package_service_spec.rb b/spec/services/packages/maven/find_or_create_package_service_spec.rb
index 2543ab0c669..803371af4bf 100644
--- a/spec/services/packages/maven/find_or_create_package_service_spec.rb
+++ b/spec/services/packages/maven/find_or_create_package_service_spec.rb
@@ -130,7 +130,15 @@ RSpec.describe Packages::Maven::FindOrCreatePackageService do
context 'when the package name matches the exception regex' do
before do
- package_settings.update!(maven_duplicate_exception_regex: '.*')
+ package_settings.update!(maven_duplicate_exception_regex: existing_package.name)
+ end
+
+ it_behaves_like 'reuse existing package'
+ end
+
+ context 'when the package version matches the exception regex' do
+ before do
+ package_settings.update!(maven_duplicate_exception_regex: existing_package.version)
end
it_behaves_like 'reuse existing package'
diff --git a/spec/services/packages/nuget/search_service_spec.rb b/spec/services/packages/nuget/search_service_spec.rb
index db758dc6672..1838065c5be 100644
--- a/spec/services/packages/nuget/search_service_spec.rb
+++ b/spec/services/packages/nuget/search_service_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Packages::Nuget::SearchService do
let_it_be(:group) { create(:group) }
let_it_be(:subgroup) { create(:group, parent: group) }
let_it_be(:project) { create(:project, namespace: subgroup) }
- let_it_be(:package_a) { create(:nuget_package, project: project, name: 'DummyPackageA') }
+ let_it_be_with_refind(:package_a) { create(:nuget_package, project: project, name: 'DummyPackageA') }
let_it_be(:packages_b) { create_list(:nuget_package, 5, project: project, name: 'DummyPackageB') }
let_it_be(:packages_c) { create_list(:nuget_package, 5, project: project, name: 'DummyPackageC') }
let_it_be(:package_d) { create(:nuget_package, project: project, name: 'FooBarD') }
@@ -79,6 +79,16 @@ RSpec.describe Packages::Nuget::SearchService do
it { expect_search_results 4, package_a, packages_b, packages_c, package_d }
end
+ context 'with non-displayable packages' do
+ let(:search_term) { '' }
+
+ before do
+ package_a.update_column(:status, 1)
+ end
+
+ it { expect_search_results 3, packages_b, packages_c, package_d }
+ end
+
context 'with prefix search term' do
let(:search_term) { 'dummy' }
diff --git a/spec/services/packages/rubygems/dependency_resolver_service_spec.rb b/spec/services/packages/rubygems/dependency_resolver_service_spec.rb
index 206bffe53f8..78abfc96ed5 100644
--- a/spec/services/packages/rubygems/dependency_resolver_service_spec.rb
+++ b/spec/services/packages/rubygems/dependency_resolver_service_spec.rb
@@ -92,7 +92,7 @@ RSpec.describe Packages::Rubygems::DependencyResolverService do
]
}]
- expect(subject.payload).to eq(expected_result)
+ expect(subject.payload).to match_array(expected_result)
end
end
end
diff --git a/spec/services/packages/rubygems/process_gem_service_spec.rb b/spec/services/packages/rubygems/process_gem_service_spec.rb
index 83e868d9579..64deb39c6d8 100644
--- a/spec/services/packages/rubygems/process_gem_service_spec.rb
+++ b/spec/services/packages/rubygems/process_gem_service_spec.rb
@@ -16,12 +16,11 @@ RSpec.describe Packages::Rubygems::ProcessGemService do
describe '#execute' do
subject { service.execute }
- context 'no gem file', :aggregate_failures do
+ context 'no gem file' do
let(:package_file) { nil }
it 'returns an error' do
- expect(subject.error?).to be(true)
- expect(subject.message).to eq('Gem was not processed')
+ expect { subject }.to raise_error(::Packages::Rubygems::ProcessGemService::ExtractionError, 'Gem was not processed - package_file is not set')
end
end
diff --git a/spec/services/packages/terraform_module/create_package_service_spec.rb b/spec/services/packages/terraform_module/create_package_service_spec.rb
new file mode 100644
index 00000000000..f911bb5b82c
--- /dev/null
+++ b/spec/services/packages/terraform_module/create_package_service_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::TerraformModule::CreatePackageService do
+ let_it_be(:namespace) { create(:namespace) }
+ let_it_be(:project) { create(:project, namespace: namespace) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:sha256) { '440e5e148a25331bbd7991575f7d54933c0ebf6cc735a18ee5066ac1381bb590' }
+ let_it_be(:temp_file) { Tempfile.new('test') }
+ let_it_be(:file) { UploadedFile.new(temp_file.path, sha256: sha256) }
+
+ let(:overrides) { {} }
+
+ let(:params) do
+ {
+ module_name: 'foo',
+ module_system: 'bar',
+ module_version: '1.0.1',
+ file: file,
+ file_name: 'foo-bar-1.0.1.tgz'
+ }.merge(overrides)
+ end
+
+ subject { described_class.new(project, user, params).execute }
+
+ describe '#execute' do
+ context 'valid package' do
+ it 'creates a package' do
+ expect { subject }
+ .to change { ::Packages::Package.count }.by(1)
+ .and change { ::Packages::Package.terraform_module.count }.by(1)
+ end
+ end
+
+ context 'package already exists elsewhere' do
+ let(:project2) { create(:project, namespace: namespace) }
+ let!(:existing_package) { create(:terraform_module_package, project: project2, name: 'foo/bar', version: '1.0.0') }
+
+ it { expect(subject[:http_status]).to eq 403 }
+ it { expect(subject[:message]).to be 'Package already exists.' }
+ end
+
+ context 'version already exists' do
+ let!(:existing_version) { create(:terraform_module_package, project: project, name: 'foo/bar', version: '1.0.1') }
+
+ it { expect(subject[:http_status]).to eq 403 }
+ it { expect(subject[:message]).to be 'Package version already exists.' }
+ end
+
+ context 'with empty version' do
+ let(:overrides) { { module_version: '' } }
+
+ it { expect(subject[:http_status]).to eq 400 }
+ it { expect(subject[:message]).to eq 'Version is empty.' }
+ end
+ end
+end
diff --git a/spec/services/post_receive_service_spec.rb b/spec/services/post_receive_service_spec.rb
index 033194972c7..2a78dc454c7 100644
--- a/spec/services/post_receive_service_spec.rb
+++ b/spec/services/post_receive_service_spec.rb
@@ -264,7 +264,7 @@ RSpec.describe PostReceiveService do
context "project path matches" do
before do
- allow(project).to receive(:full_path).and_return("/company/sekrit-project")
+ allow(project).to receive(:full_path).and_return("company/sekrit-project")
end
it "does output the latest scoped broadcast message" do
diff --git a/spec/services/projects/alerting/notify_service_spec.rb b/spec/services/projects/alerting/notify_service_spec.rb
index c272ce13132..feae8f3967c 100644
--- a/spec/services/projects/alerting/notify_service_spec.rb
+++ b/spec/services/projects/alerting/notify_service_spec.rb
@@ -3,77 +3,49 @@
require 'spec_helper'
RSpec.describe Projects::Alerting::NotifyService do
- let_it_be_with_reload(:project) { create(:project, :repository) }
+ let_it_be_with_reload(:project) { create(:project) }
+
+ let(:payload) { ActionController::Parameters.new(payload_raw).permit! }
+ let(:payload_raw) { {} }
+
+ let(:service) { described_class.new(project, payload) }
before do
- allow(ProjectServiceWorker).to receive(:perform_async)
+ stub_licensed_features(oncall_schedules: false, generic_alert_fingerprinting: false)
end
describe '#execute' do
- let(:token) { 'invalid-token' }
- let(:starts_at) { Time.current.change(usec: 0) }
- let(:fingerprint) { 'testing' }
- let(:service) { described_class.new(project, payload) }
- let_it_be(:environment) { create(:environment, project: project) }
- let(:environment) { create(:environment, project: project) }
- let(:ended_at) { nil }
- let(:payload_raw) do
- {
- title: 'alert title',
- start_time: starts_at.rfc3339,
- end_time: ended_at&.rfc3339,
- severity: 'low',
- monitoring_tool: 'GitLab RSpec',
- service: 'GitLab Test Suite',
- description: 'Very detailed description',
- hosts: ['1.1.1.1', '2.2.2.2'],
- fingerprint: fingerprint,
- gitlab_environment_name: environment.name
- }.with_indifferent_access
- end
+ include_context 'incident management settings enabled'
- let(:payload) { ActionController::Parameters.new(payload_raw).permit! }
+ subject { service.execute(token, integration) }
- subject { service.execute(token, nil) }
+ context 'with HTTP integration' do
+ let_it_be_with_reload(:integration) { create(:alert_management_http_integration, project: project) }
- shared_examples 'notifications are handled correctly' do
context 'with valid token' do
let(:token) { integration.token }
- let(:incident_management_setting) { double(send_email?: email_enabled, create_issue?: issue_enabled, auto_close_incident?: auto_close_enabled) }
- let(:email_enabled) { false }
- let(:issue_enabled) { false }
- let(:auto_close_enabled) { false }
-
- before do
- allow(service)
- .to receive(:incident_management_setting)
- .and_return(incident_management_setting)
- end
context 'with valid payload' do
- shared_examples 'assigns the alert properties' do
- it 'ensure that created alert has all data properly assigned' do
- subject
- expect(last_alert_attributes).to match(
- project_id: project.id,
- title: payload_raw.fetch(:title),
- started_at: Time.zone.parse(payload_raw.fetch(:start_time)),
- severity: payload_raw.fetch(:severity),
- status: AlertManagement::Alert.status_value(:triggered),
- events: 1,
- domain: 'operations',
- hosts: payload_raw.fetch(:hosts),
- payload: payload_raw.with_indifferent_access,
- issue_id: nil,
- description: payload_raw.fetch(:description),
- monitoring_tool: payload_raw.fetch(:monitoring_tool),
- service: payload_raw.fetch(:service),
- fingerprint: Digest::SHA1.hexdigest(fingerprint),
- environment_id: environment.id,
- ended_at: nil,
- prometheus_alert_id: nil
- )
- end
+ let_it_be(:environment) { create(:environment, project: project) }
+ let_it_be(:fingerprint) { 'testing' }
+ let_it_be(:source) { 'GitLab RSpec' }
+ let_it_be(:starts_at) { Time.current.change(usec: 0) }
+
+ let(:ended_at) { nil }
+ let(:domain) { 'operations' }
+ let(:payload_raw) do
+ {
+ title: 'alert title',
+ start_time: starts_at.rfc3339,
+ end_time: ended_at&.rfc3339,
+ severity: 'low',
+ monitoring_tool: source,
+ service: 'GitLab Test Suite',
+ description: 'Very detailed description',
+ hosts: ['1.1.1.1', '2.2.2.2'],
+ fingerprint: fingerprint,
+ gitlab_environment_name: environment.name
+ }.with_indifferent_access
end
let(:last_alert_attributes) do
@@ -82,8 +54,8 @@ RSpec.describe Projects::Alerting::NotifyService do
.with_indifferent_access
end
- it_behaves_like 'creates an alert management alert'
- it_behaves_like 'assigns the alert properties'
+ it_behaves_like 'processes new firing alert'
+ it_behaves_like 'properly assigns the alert properties'
it 'passes the integration to alert processing' do
expect(Gitlab::AlertManagement::Payload)
@@ -94,101 +66,18 @@ RSpec.describe Projects::Alerting::NotifyService do
subject
end
- it 'creates a system note corresponding to alert creation' do
- expect { subject }.to change(Note, :count).by(1)
- expect(Note.last.note).to include(payload_raw.fetch(:monitoring_tool))
- end
-
- context 'existing alert with same fingerprint' do
- let(:fingerprint_sha) { Digest::SHA1.hexdigest(fingerprint) }
- let!(:alert) { create(:alert_management_alert, project: project, fingerprint: fingerprint_sha) }
-
- it_behaves_like 'adds an alert management alert event'
-
- context 'end time given' do
- let(:ended_at) { Time.current.change(nsec: 0) }
-
- it 'does not resolve the alert' do
- expect { subject }.not_to change { alert.reload.status }
- end
-
- it 'does not set the ended at' do
- subject
-
- expect(alert.reload.ended_at).to be_nil
- end
-
- it_behaves_like 'does not an create alert management alert'
- it_behaves_like 'creates single system note based on the source of the alert'
-
- context 'auto_close_enabled setting enabled' do
- let(:auto_close_enabled) { true }
-
- it 'resolves the alert and sets the end time', :aggregate_failures do
- subject
- alert.reload
-
- expect(alert.resolved?).to eq(true)
- expect(alert.ended_at).to eql(ended_at)
- end
-
- it_behaves_like 'creates status-change system note for an auto-resolved alert'
-
- context 'related issue exists' do
- let(:alert) { create(:alert_management_alert, :with_issue, project: project, fingerprint: fingerprint_sha) }
- let(:issue) { alert.issue }
-
- it { expect { subject }.to change { issue.reload.state }.from('opened').to('closed') }
- it { expect { subject }.to change(ResourceStateEvent, :count).by(1) }
- end
-
- context 'with issue enabled' do
- let(:issue_enabled) { true }
-
- it_behaves_like 'does not process incident issues'
- end
- end
- end
-
- context 'existing alert is resolved' do
- let!(:alert) { create(:alert_management_alert, :resolved, project: project, fingerprint: fingerprint_sha) }
-
- it_behaves_like 'creates an alert management alert'
- it_behaves_like 'assigns the alert properties'
- end
-
- context 'existing alert is ignored' do
- let!(:alert) { create(:alert_management_alert, :ignored, project: project, fingerprint: fingerprint_sha) }
-
- it_behaves_like 'adds an alert management alert event'
- end
-
- context 'two existing alerts, one resolved one open' do
- let!(:resolved_existing_alert) { create(:alert_management_alert, :resolved, project: project, fingerprint: fingerprint_sha) }
- let!(:alert) { create(:alert_management_alert, project: project, fingerprint: fingerprint_sha) }
-
- it_behaves_like 'adds an alert management alert event'
- end
- end
-
- context 'end time given' do
- let(:ended_at) { Time.current }
-
- it_behaves_like 'creates an alert management alert'
- it_behaves_like 'assigns the alert properties'
- end
-
- context 'with a minimal payload' do
- let(:payload_raw) do
+ context 'with partial payload' do
+ let_it_be(:source) { integration.name }
+ let_it_be(:payload_raw) do
{
title: 'alert title',
start_time: starts_at.rfc3339
}
end
- it_behaves_like 'creates an alert management alert'
+ include_examples 'processes never-before-seen alert'
- it 'created alert has all data properly assigned' do
+ it 'assigns the alert properties' do
subject
expect(last_alert_attributes).to match(
@@ -212,7 +101,19 @@ RSpec.describe Projects::Alerting::NotifyService do
)
end
- it_behaves_like 'creates single system note based on the source of the alert'
+ context 'with existing alert with matching payload' do
+ let_it_be(:fingerprint) { payload_raw.except(:start_time).stringify_keys }
+ let_it_be(:gitlab_fingerprint) { Gitlab::AlertManagement::Fingerprint.generate(fingerprint) }
+ let_it_be(:alert) { create(:alert_management_alert, project: project, fingerprint: gitlab_fingerprint) }
+
+ include_examples 'processes never-before-seen alert'
+ end
+ end
+
+ context 'with resolving payload' do
+ let(:ended_at) { Time.current.change(usec: 0) }
+
+ it_behaves_like 'processes recovery alert'
end
end
@@ -223,63 +124,30 @@ RSpec.describe Projects::Alerting::NotifyService do
allow(Gitlab::Utils::DeepSize).to receive(:new).and_return(deep_size_object)
end
- it_behaves_like 'does not process incident issues due to error', http_status: :bad_request
- it_behaves_like 'does not an create alert management alert'
+ it_behaves_like 'alerts service responds with an error and takes no actions', :bad_request
end
- it_behaves_like 'does not process incident issues'
-
- context 'issue enabled' do
- let(:issue_enabled) { true }
-
- it_behaves_like 'processes incident issues'
-
- context 'when alert already exists' do
- let(:fingerprint_sha) { Digest::SHA1.hexdigest(fingerprint) }
- let!(:alert) { create(:alert_management_alert, project: project, fingerprint: fingerprint_sha) }
-
- context 'when existing alert does not have an associated issue' do
- it_behaves_like 'processes incident issues'
- end
-
- context 'when existing alert has an associated issue' do
- let!(:alert) { create(:alert_management_alert, :with_issue, project: project, fingerprint: fingerprint_sha) }
-
- it_behaves_like 'does not process incident issues'
- end
+ context 'with inactive integration' do
+ before do
+ integration.update!(active: false)
end
- end
- context 'with emails turned on' do
- let(:email_enabled) { true }
-
- it_behaves_like 'Alert Notification Service sends notification email'
+ it_behaves_like 'alerts service responds with an error and takes no actions', :forbidden
end
end
context 'with invalid token' do
- it_behaves_like 'does not process incident issues due to error', http_status: :unauthorized
- it_behaves_like 'does not an create alert management alert'
- end
- end
-
- context 'with an HTTP Integration' do
- let_it_be_with_reload(:integration) { create(:alert_management_http_integration, project: project) }
+ let(:token) { 'invalid-token' }
- subject { service.execute(token, integration) }
-
- it_behaves_like 'notifications are handled correctly' do
- let(:source) { integration.name }
+ it_behaves_like 'alerts service responds with an error and takes no actions', :unauthorized
end
+ end
- context 'with deactivated HTTP Integration' do
- before do
- integration.update!(active: false)
- end
+ context 'without HTTP integration' do
+ let(:integration) { nil }
+ let(:token) { nil }
- it_behaves_like 'does not process incident issues due to error', http_status: :forbidden
- it_behaves_like 'does not an create alert management alert'
- end
+ it_behaves_like 'alerts service responds with an error and takes no actions', :forbidden
end
end
end
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index e0d6b9afcff..cd659bf5e60 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -273,16 +273,6 @@ RSpec.describe Projects::CreateService, '#execute' do
opts[:default_branch] = 'master'
expect(create_project(user, opts)).to eq(nil)
end
-
- it 'sets invalid service as inactive' do
- create(:service, type: 'JiraService', project: nil, template: true, active: true)
-
- project = create_project(user, opts)
- service = project.services.first
-
- expect(project).to be_persisted
- expect(service.active).to be false
- end
end
context 'wiki_enabled creates repository directory' do
@@ -574,18 +564,18 @@ RSpec.describe Projects::CreateService, '#execute' do
let!(:template_integration) { create(:prometheus_service, :template, api_url: 'https://prometheus.template.com/') }
it 'creates a service from the template' do
- expect(project.services.count).to eq(1)
- expect(project.services.first.api_url).to eq(template_integration.api_url)
- expect(project.services.first.inherit_from_id).to be_nil
+ expect(project.integrations.count).to eq(1)
+ expect(project.integrations.first.api_url).to eq(template_integration.api_url)
+ expect(project.integrations.first.inherit_from_id).to be_nil
end
context 'with an active instance-level integration' do
let!(:instance_integration) { create(:prometheus_service, :instance, api_url: 'https://prometheus.instance.com/') }
it 'creates a service from the instance-level integration' do
- expect(project.services.count).to eq(1)
- expect(project.services.first.api_url).to eq(instance_integration.api_url)
- expect(project.services.first.inherit_from_id).to eq(instance_integration.id)
+ expect(project.integrations.count).to eq(1)
+ expect(project.integrations.first.api_url).to eq(instance_integration.api_url)
+ expect(project.integrations.first.inherit_from_id).to eq(instance_integration.id)
end
context 'with an active group-level integration' do
@@ -604,9 +594,9 @@ RSpec.describe Projects::CreateService, '#execute' do
end
it 'creates a service from the group-level integration' do
- expect(project.services.count).to eq(1)
- expect(project.services.first.api_url).to eq(group_integration.api_url)
- expect(project.services.first.inherit_from_id).to eq(group_integration.id)
+ expect(project.integrations.count).to eq(1)
+ expect(project.integrations.first.api_url).to eq(group_integration.api_url)
+ expect(project.integrations.first.inherit_from_id).to eq(group_integration.id)
end
context 'with an active subgroup' do
@@ -625,25 +615,14 @@ RSpec.describe Projects::CreateService, '#execute' do
end
it 'creates a service from the subgroup-level integration' do
- expect(project.services.count).to eq(1)
- expect(project.services.first.api_url).to eq(subgroup_integration.api_url)
- expect(project.services.first.inherit_from_id).to eq(subgroup_integration.id)
+ expect(project.integrations.count).to eq(1)
+ expect(project.integrations.first.api_url).to eq(subgroup_integration.api_url)
+ expect(project.integrations.first.inherit_from_id).to eq(subgroup_integration.id)
end
end
end
end
end
-
- context 'when there is an invalid integration' do
- before do
- create(:service, :template, type: 'DroneCiService', active: true)
- end
-
- it 'creates an inactive service' do
- expect(project).to be_persisted
- expect(project.services.first.active).to be false
- end
- end
end
context 'when skip_disk_validation is used' do
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index b2a68bbd0aa..ff582279d71 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -418,6 +418,54 @@ RSpec.describe Projects::DestroyService, :aggregate_failures do
end
end
+ context 'when project has webhooks' do
+ let!(:web_hook1) { create(:project_hook, project: project) }
+ let!(:web_hook2) { create(:project_hook, project: project) }
+ let!(:another_project_web_hook) { create(:project_hook) }
+ let!(:web_hook_log) { create(:web_hook_log, web_hook: web_hook1) }
+
+ it 'deletes webhooks and logs related to project' do
+ expect_next_instance_of(WebHooks::DestroyService, user) do |instance|
+ expect(instance).to receive(:sync_destroy).with(web_hook1).and_call_original
+ end
+ expect_next_instance_of(WebHooks::DestroyService, user) do |instance|
+ expect(instance).to receive(:sync_destroy).with(web_hook2).and_call_original
+ end
+
+ expect do
+ destroy_project(project, user)
+ end.to change(WebHook, :count).by(-2)
+ .and change(WebHookLog, :count).by(-1)
+ end
+
+ context 'when an error is raised deleting webhooks' do
+ before do
+ allow_next_instance_of(WebHooks::DestroyService) do |instance|
+ allow(instance).to receive(:sync_destroy).and_return(message: 'foo', status: :error)
+ end
+ end
+
+ it_behaves_like 'handles errors thrown during async destroy', "Failed to remove webhooks"
+ end
+
+ context 'when "destroy_webhooks_before_the_project" flag is disabled' do
+ before do
+ stub_feature_flags(destroy_webhooks_before_the_project: false)
+ end
+
+ it 'does not call WebHooks::DestroyService' do
+ expect(WebHooks::DestroyService).not_to receive(:new)
+
+ expect do
+ destroy_project(project, user)
+ end.to change(WebHook, :count).by(-2)
+ .and change(WebHookLog, :count).by(-1)
+
+ expect(another_project_web_hook.reload).to be
+ end
+ end
+ end
+
context 'error while destroying', :sidekiq_inline do
let!(:pipeline) { create(:ci_pipeline, project: project) }
let!(:builds) { create_list(:ci_build, 2, :artifacts, pipeline: pipeline) }
diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb
deleted file mode 100644
index 0aa4a1cd312..00000000000
--- a/spec/services/projects/housekeeping_service_spec.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-# This is a compatibility class to avoid calling a non-existent
-# class from sidekiq during deployment.
-#
-# We're deploying the name of the referenced class in 13.9. Nevertheless,
-# we cannot remove the class entirely because there can be jobs
-# referencing it. We still need this specs to ensure that the old
-# class still has the old behavior.
-#
-# We can get rid of this class in 13.10
-# https://gitlab.com/gitlab-org/gitlab/-/issues/297580
-#
-RSpec.describe Projects::HousekeepingService do
- it_behaves_like 'housekeeps repository' do
- let_it_be(:resource) { create(:project, :repository) }
- end
-end
diff --git a/spec/services/projects/prometheus/alerts/notify_service_spec.rb b/spec/services/projects/prometheus/alerts/notify_service_spec.rb
index e196220eabe..bfc8225b654 100644
--- a/spec/services/projects/prometheus/alerts/notify_service_spec.rb
+++ b/spec/services/projects/prometheus/alerts/notify_service_spec.rb
@@ -6,25 +6,26 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do
include PrometheusHelpers
using RSpec::Parameterized::TableSyntax
- let_it_be(:project, reload: true) { create(:project) }
+ let_it_be_with_reload(:project) { create(:project) }
+ let_it_be_with_refind(:setting) do
+ create(:project_incident_management_setting, project: project, send_email: true, create_issue: true)
+ end
let(:service) { described_class.new(project, payload) }
let(:token_input) { 'token' }
- let!(:setting) do
- create(:project_incident_management_setting, project: project, send_email: true, create_issue: true)
- end
-
- let(:subject) { service.execute(token_input) }
+ subject { service.execute(token_input) }
context 'with valid payload' do
let_it_be(:alert_firing) { create(:prometheus_alert, project: project) }
let_it_be(:alert_resolved) { create(:prometheus_alert, project: project) }
- let_it_be(:cluster) { create(:cluster, :provided_by_user, projects: [project]) }
+ let_it_be(:cluster, reload: true) { create(:cluster, :provided_by_user, projects: [project]) }
+
let(:payload_raw) { prometheus_alert_payload(firing: [alert_firing], resolved: [alert_resolved]) }
let(:payload) { ActionController::Parameters.new(payload_raw).permit! }
let(:payload_alert_firing) { payload_raw['alerts'].first }
let(:token) { 'token' }
+ let(:source) { 'Prometheus' }
context 'with environment specific clusters' do
let(:prd_cluster) do
@@ -53,15 +54,15 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do
context 'without token' do
let(:token_input) { nil }
- it_behaves_like 'Alert Notification Service sends notification email'
+ include_examples 'processes one firing and one resolved prometheus alerts'
end
context 'with token' do
- it_behaves_like 'Alert Notification Service sends no notifications', http_status: :unauthorized
+ it_behaves_like 'alerts service responds with an error and takes no actions', :unauthorized
end
end
- context 'with project specific cluster' do
+ context 'with project specific cluster using prometheus application' do
where(:cluster_enabled, :status, :configured_token, :token_input, :result) do
true | :installed | token | token | :success
true | :installed | nil | nil | :success
@@ -87,9 +88,43 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do
case result = params[:result]
when :success
- it_behaves_like 'Alert Notification Service sends notification email'
+ include_examples 'processes one firing and one resolved prometheus alerts'
+ when :failure
+ it_behaves_like 'alerts service responds with an error and takes no actions', :unauthorized
+ else
+ raise "invalid result: #{result.inspect}"
+ end
+ end
+ end
+
+ context 'with project specific cluster using prometheus integration' do
+ where(:cluster_enabled, :integration_enabled, :configured_token, :token_input, :result) do
+ true | true | token | token | :success
+ true | true | nil | nil | :success
+ true | true | token | 'x' | :failure
+ true | true | token | nil | :failure
+ true | false | token | token | :failure
+ false | true | token | token | :failure
+ false | nil | nil | token | :failure
+ end
+
+ with_them do
+ before do
+ cluster.update!(enabled: cluster_enabled)
+
+ unless integration_enabled.nil?
+ create(:clusters_integrations_prometheus,
+ cluster: cluster,
+ enabled: integration_enabled,
+ alert_manager_token: configured_token)
+ end
+ end
+
+ case result = params[:result]
+ when :success
+ include_examples 'processes one firing and one resolved prometheus alerts'
when :failure
- it_behaves_like 'Alert Notification Service sends no notifications', http_status: :unauthorized
+ it_behaves_like 'alerts service responds with an error and takes no actions', :unauthorized
else
raise "invalid result: #{result.inspect}"
end
@@ -97,9 +132,9 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do
end
context 'without project specific cluster' do
- let!(:cluster) { create(:cluster, enabled: true) }
+ let_it_be(:cluster) { create(:cluster, enabled: true) }
- it_behaves_like 'Alert Notification Service sends no notifications', http_status: :unauthorized
+ it_behaves_like 'alerts service responds with an error and takes no actions', :unauthorized
end
context 'with manual prometheus installation' do
@@ -126,9 +161,9 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do
case result = params[:result]
when :success
- it_behaves_like 'Alert Notification Service sends notification email'
+ it_behaves_like 'processes one firing and one resolved prometheus alerts'
when :failure
- it_behaves_like 'Alert Notification Service sends no notifications', http_status: :unauthorized
+ it_behaves_like 'alerts service responds with an error and takes no actions', :unauthorized
else
raise "invalid result: #{result.inspect}"
end
@@ -150,50 +185,53 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do
let(:token_input) { public_send(token) if token }
let(:integration) { create(:alert_management_http_integration, active, project: project) if active }
- let(:subject) { service.execute(token_input, integration) }
+ subject { service.execute(token_input, integration) }
case result = params[:result]
when :success
- it_behaves_like 'Alert Notification Service sends notification email'
+ it_behaves_like 'processes one firing and one resolved prometheus alerts'
when :failure
- it_behaves_like 'Alert Notification Service sends no notifications', http_status: :unauthorized
+ it_behaves_like 'alerts service responds with an error and takes no actions', :unauthorized
else
raise "invalid result: #{result.inspect}"
end
end
end
- context 'alert emails' do
+ context 'incident settings' do
before do
create(:prometheus_service, project: project)
create(:project_alerting_setting, project: project, token: token)
end
- context 'when incident_management_setting does not exist' do
- let!(:setting) { nil }
+ it_behaves_like 'processes one firing and one resolved prometheus alerts'
- it 'does not send notification email', :sidekiq_might_not_need_inline do
- expect_any_instance_of(NotificationService)
- .not_to receive(:async)
-
- expect(subject).to be_success
+ context 'when incident_management_setting does not exist' do
+ before do
+ setting.destroy!
end
- end
- context 'when incident_management_setting.send_email is true' do
- it_behaves_like 'Alert Notification Service sends notification email'
+ it { is_expected.to be_success }
+ include_examples 'does not send alert notification emails'
+ include_examples 'does not process incident issues'
end
context 'incident_management_setting.send_email is false' do
- let!(:setting) do
- create(:project_incident_management_setting, send_email: false, project: project)
+ before do
+ setting.update!(send_email: false)
end
- it 'does not send notification' do
- expect(NotificationService).not_to receive(:new)
+ it { is_expected.to be_success }
+ include_examples 'does not send alert notification emails'
+ end
- expect(subject).to be_success
+ context 'incident_management_setting.create_issue is false' do
+ before do
+ setting.update!(create_issue: false)
end
+
+ it { is_expected.to be_success }
+ include_examples 'does not process incident issues'
end
end
@@ -233,7 +271,7 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do
.and_return(false)
end
- it_behaves_like 'Alert Notification Service sends no notifications', http_status: :unprocessable_entity
+ it_behaves_like 'alerts service responds with an error and takes no actions', :unprocessable_entity
end
context 'when the payload is too big' do
@@ -244,14 +282,7 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do
allow(Gitlab::Utils::DeepSize).to receive(:new).and_return(deep_size_object)
end
- it_behaves_like 'Alert Notification Service sends no notifications', http_status: :bad_request
-
- it 'does not process Prometheus alerts' do
- expect(AlertManagement::ProcessPrometheusAlertService)
- .not_to receive(:new)
-
- subject
- end
+ it_behaves_like 'alerts service responds with an error and takes no actions', :bad_request
end
end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 5f41ec1d610..8498b752610 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -130,7 +130,7 @@ RSpec.describe Projects::TransferService do
execute_transfer
expect(project.slack_service.webhook).to eq(group_integration.webhook)
- expect(Service.count).to eq(3)
+ expect(Integration.count).to eq(3)
end
end
diff --git a/spec/services/projects/unlink_fork_service_spec.rb b/spec/services/projects/unlink_fork_service_spec.rb
index 90def365fca..d939a79b7e9 100644
--- a/spec/services/projects/unlink_fork_service_spec.rb
+++ b/spec/services/projects/unlink_fork_service_spec.rb
@@ -16,11 +16,11 @@ RSpec.describe Projects::UnlinkForkService, :use_clean_rails_memory_store_cachin
let(:merge_request2) { create(:merge_request, source_project: forked_project, target_project: fork_project(project)) }
let(:merge_request_in_fork) { create(:merge_request, source_project: forked_project, target_project: forked_project) }
- let(:mr_close_service) { MergeRequests::CloseService.new(forked_project, user) }
+ let(:mr_close_service) { MergeRequests::CloseService.new(project: forked_project, current_user: user) }
before do
allow(MergeRequests::CloseService).to receive(:new)
- .with(forked_project, user)
+ .with(project: forked_project, current_user: user)
.and_return(mr_close_service)
end
@@ -79,11 +79,11 @@ RSpec.describe Projects::UnlinkForkService, :use_clean_rails_memory_store_cachin
let!(:merge_request2) { create(:merge_request, source_project: project, target_project: fork_project(project)) }
let!(:merge_request_in_fork) { create(:merge_request, source_project: forked_project, target_project: forked_project) }
- let(:mr_close_service) { MergeRequests::CloseService.new(project, user) }
+ let(:mr_close_service) { MergeRequests::CloseService.new(project: project, current_user: user) }
before do
allow(MergeRequests::CloseService).to receive(:new)
- .with(project, user)
+ .with(project: project, current_user: user)
.and_return(mr_close_service)
end
@@ -142,11 +142,11 @@ RSpec.describe Projects::UnlinkForkService, :use_clean_rails_memory_store_cachin
let!(:mr_from_child) { create(:merge_request, source_project: fork_of_fork, target_project: forked_project) }
let!(:merge_request_in_fork) { create(:merge_request, source_project: forked_project, target_project: forked_project) }
- let(:mr_close_service) { MergeRequests::CloseService.new(forked_project, user) }
+ let(:mr_close_service) { MergeRequests::CloseService.new(project: forked_project, current_user: user) }
before do
allow(MergeRequests::CloseService).to receive(:new)
- .with(forked_project, user)
+ .with(project: forked_project, current_user: user)
.and_return(mr_close_service)
end
diff --git a/spec/services/projects/update_repository_storage_service_spec.rb b/spec/services/projects/update_repository_storage_service_spec.rb
index 828667fdfc2..5b15b7d5f34 100644
--- a/spec/services/projects/update_repository_storage_service_spec.rb
+++ b/spec/services/projects/update_repository_storage_service_spec.rb
@@ -36,7 +36,7 @@ RSpec.describe Projects::UpdateRepositoryStorageService do
end
context 'when the move succeeds' do
- it 'moves the repository to the new storage and unmarks the repository as read only' do
+ it 'moves the repository to the new storage and unmarks the repository as read-only' do
old_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
project.repository.path_to_repo
end
diff --git a/spec/services/projects/update_statistics_service_spec.rb b/spec/services/projects/update_statistics_service_spec.rb
index 92e97186be3..6987185b549 100644
--- a/spec/services/projects/update_statistics_service_spec.rb
+++ b/spec/services/projects/update_statistics_service_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Projects::UpdateStatisticsService do
+ using RSpec::Parameterized::TableSyntax
+
let(:service) { described_class.new(project, nil, statistics: statistics)}
let(:statistics) { %w(repository_size) }
@@ -18,12 +20,46 @@ RSpec.describe Projects::UpdateStatisticsService do
end
context 'with an existing project' do
- let(:project) { create(:project) }
+ let_it_be(:project) { create(:project) }
+
+ where(:statistics, :method_caches) do
+ [] | %i(size commit_count)
+ ['repository_size'] | [:size]
+ [:repository_size] | [:size]
+ [:lfs_objects_size] | nil
+ [:commit_count] | [:commit_count] # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands
+ [:repository_size, :commit_count] | %i(size commit_count)
+ [:repository_size, :commit_count, :lfs_objects_size] | %i(size commit_count)
+ end
+
+ with_them do
+ it 'refreshes the project statistics' do
+ expect(project.statistics).to receive(:refresh!).with(only: statistics.map(&:to_sym)).and_call_original
+
+ service.execute
+ end
+
+ it 'invalidates the method caches after a refresh' do
+ expect(project.wiki.repository).not_to receive(:expire_method_caches)
+
+ if method_caches.present?
+ expect(project.repository).to receive(:expire_method_caches).with(method_caches).and_call_original
+ else
+ expect(project.repository).not_to receive(:expire_method_caches)
+ end
+
+ service.execute
+ end
+ end
+ end
+
+ context 'with an existing project with a Wiki' do
+ let(:project) { create(:project, :repository, :wiki_enabled) }
+ let(:statistics) { [:wiki_size] }
- it 'refreshes the project statistics' do
- expect_any_instance_of(ProjectStatistics).to receive(:refresh!)
- .with(only: statistics.map(&:to_sym))
- .and_call_original
+ it 'invalidates and refreshes Wiki size' do
+ expect(project.statistics).to receive(:refresh!).with(only: statistics).and_call_original
+ expect(project.wiki.repository).to receive(:expire_method_caches).with(%i(size)).and_call_original
service.execute
end
diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index 9df238c6dac..f3ad69bae13 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -360,25 +360,29 @@ RSpec.describe QuickActions::InterpretService do
shared_examples 'spend command' do
it 'populates spend_time: 3600 if content contains /spend 1h' do
- _, updates, _ = service.execute(content, issuable)
+ freeze_time do
+ _, updates, _ = service.execute(content, issuable)
- expect(updates).to eq(spend_time: {
- duration: 3600,
- user_id: developer.id,
- spent_at: DateTime.current.to_date
- })
+ expect(updates).to eq(spend_time: {
+ duration: 3600,
+ user_id: developer.id,
+ spent_at: DateTime.current
+ })
+ end
end
end
shared_examples 'spend command with negative time' do
it 'populates spend_time: -7200 if content contains -120m' do
- _, updates, _ = service.execute(content, issuable)
+ freeze_time do
+ _, updates, _ = service.execute(content, issuable)
- expect(updates).to eq(spend_time: {
- duration: -7200,
- user_id: developer.id,
- spent_at: DateTime.current.to_date
- })
+ expect(updates).to eq(spend_time: {
+ duration: -7200,
+ user_id: developer.id,
+ spent_at: DateTime.current
+ })
+ end
end
it 'returns the spend_time message including the formatted duration and verb' do
diff --git a/spec/services/security/ci_configuration/sast_create_service_spec.rb b/spec/services/security/ci_configuration/sast_create_service_spec.rb
index ff7ab614e08..44f8f07a5be 100644
--- a/spec/services/security/ci_configuration/sast_create_service_spec.rb
+++ b/spec/services/security/ci_configuration/sast_create_service_spec.rb
@@ -3,67 +3,24 @@
require 'spec_helper'
RSpec.describe Security::CiConfiguration::SastCreateService, :snowplow do
- describe '#execute' do
- let_it_be(:project) { create(:project, :repository) }
- let_it_be(:user) { create(:user) }
- let(:params) { {} }
+ subject(:result) { described_class.new(project, user, params).execute }
- subject(:result) { described_class.new(project, user, params).execute }
+ let(:branch_name) { 'set-sast-config-1' }
- context 'user does not belong to project' do
- it 'returns an error status' do
- expect(result[:status]).to eq(:error)
- expect(result[:success_path]).to be_nil
- end
-
- it 'does not track a snowplow event' do
- subject
-
- expect_no_snowplow_event
- end
- end
-
- context 'user belongs to project' do
- before do
- project.add_developer(user)
- end
-
- it 'does track the snowplow event' do
- subject
-
- expect_snowplow_event(
- category: 'Security::CiConfiguration::SastCreateService',
- action: 'create',
- label: 'false'
- )
- end
-
- it 'raises exception if the user does not have permission to create a new branch' do
- allow(project).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, "You are not allowed to create protected branches on this project.")
-
- expect { subject }.to raise_error(Gitlab::Git::PreReceiveError)
- end
-
- context 'with no parameters' do
- it 'returns the path to create a new merge request' do
- expect(result[:status]).to eq(:success)
- expect(result[:success_path]).to match(/#{Gitlab::Routing.url_helpers.project_new_merge_request_url(project, {})}(.*)description(.*)source_branch/)
- end
- end
-
- context 'with parameters' do
- let(:params) do
- { 'stage' => 'security',
- 'SEARCH_MAX_DEPTH' => 1,
- 'SECURE_ANALYZERS_PREFIX' => 'new_registry',
- 'SAST_EXCLUDED_PATHS' => 'spec,docs' }
- end
+ let(:non_empty_params) do
+ { 'stage' => 'security',
+ 'SEARCH_MAX_DEPTH' => 1,
+ 'SECURE_ANALYZERS_PREFIX' => 'new_registry',
+ 'SAST_EXCLUDED_PATHS' => 'spec,docs' }
+ end
- it 'returns the path to create a new merge request' do
- expect(result[:status]).to eq(:success)
- expect(result[:success_path]).to match(/#{Gitlab::Routing.url_helpers.project_new_merge_request_url(project, {})}(.*)description(.*)source_branch/)
- end
- end
- end
+ let(:snowplow_event) do
+ {
+ category: 'Security::CiConfiguration::SastCreateService',
+ action: 'create',
+ label: 'false'
+ }
end
+
+ include_examples 'services security ci configuration create service'
end
diff --git a/spec/services/security/ci_configuration/secret_detection_create_service_spec.rb b/spec/services/security/ci_configuration/secret_detection_create_service_spec.rb
new file mode 100644
index 00000000000..c1df3ebdca5
--- /dev/null
+++ b/spec/services/security/ci_configuration/secret_detection_create_service_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Security::CiConfiguration::SecretDetectionCreateService, :snowplow do
+ subject(:result) { described_class.new(project, user).execute }
+
+ let(:branch_name) { 'set-secret-detection-config-1' }
+
+ let(:snowplow_event) do
+ {
+ category: 'Security::CiConfiguration::SecretDetectionCreateService',
+ action: 'create',
+ label: ''
+ }
+ end
+
+ include_examples 'services security ci configuration create service', true
+end
diff --git a/spec/services/snippets/update_repository_storage_service_spec.rb b/spec/services/snippets/update_repository_storage_service_spec.rb
index 6ba09a9dca9..50b28a5a125 100644
--- a/spec/services/snippets/update_repository_storage_service_spec.rb
+++ b/spec/services/snippets/update_repository_storage_service_spec.rb
@@ -31,7 +31,7 @@ RSpec.describe Snippets::UpdateRepositoryStorageService do
end
context 'when the move succeeds' do
- it 'moves the repository to the new storage and unmarks the repository as read only' do
+ it 'moves the repository to the new storage and unmarks the repository as read-only' do
old_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
snippet.repository.path_to_repo
end
diff --git a/spec/services/spam/spam_action_service_spec.rb b/spec/services/spam/spam_action_service_spec.rb
index e8ac826df1c..9ca52b92267 100644
--- a/spec/services/spam/spam_action_service_spec.rb
+++ b/spec/services/spam/spam_action_service_spec.rb
@@ -9,11 +9,11 @@ RSpec.describe Spam::SpamActionService do
let(:issue) { create(:issue, project: project, author: user) }
let(:fake_ip) { '1.2.3.4' }
let(:fake_user_agent) { 'fake-user-agent' }
- let(:fake_referrer) { 'fake-http-referrer' }
+ let(:fake_referer) { 'fake-http-referer' }
let(:env) do
{ 'action_dispatch.remote_ip' => fake_ip,
'HTTP_USER_AGENT' => fake_user_agent,
- 'HTTP_REFERRER' => fake_referrer }
+ 'HTTP_REFERER' => fake_referer }
end
let_it_be(:project) { create(:project, :public) }
@@ -80,7 +80,7 @@ RSpec.describe Spam::SpamActionService do
{
ip_address: fake_ip,
user_agent: fake_user_agent,
- referrer: fake_referrer
+ referer: fake_referer
}
end
@@ -222,6 +222,38 @@ RSpec.describe Spam::SpamActionService do
end
end
+ context 'spam verdict service advises to block the user' do
+ before do
+ allow(fake_verdict_service).to receive(:execute).and_return(BLOCK_USER)
+ end
+
+ context 'when allow_possible_spam feature flag is false' do
+ before do
+ stub_feature_flags(allow_possible_spam: false)
+ end
+
+ it_behaves_like 'only checks for spam if a request is provided'
+
+ it 'marks as spam' do
+ response = subject
+
+ expect(response.message).to match(expected_service_check_response_message)
+ expect(issue).to be_spam
+ end
+ end
+
+ context 'when allow_possible_spam feature flag is true' do
+ it_behaves_like 'only checks for spam if a request is provided'
+
+ it 'does not mark as spam' do
+ response = subject
+
+ expect(response.message).to match(expected_service_check_response_message)
+ expect(issue).not_to be_spam
+ end
+ end
+ end
+
context 'when spam verdict service conditionally allows' do
before do
allow(fake_verdict_service).to receive(:execute).and_return(CONDITIONAL_ALLOW)
@@ -281,6 +313,22 @@ RSpec.describe Spam::SpamActionService do
end
end
+ context 'when spam verdict service returns noop' do
+ before do
+ allow(fake_verdict_service).to receive(:execute).and_return(NOOP)
+ end
+
+ it 'does not create a spam log' do
+ expect { subject }.not_to change(SpamLog, :count)
+ end
+
+ it 'clears spam flags' do
+ expect(issue).to receive(:clear_spam_flags!)
+
+ subject
+ end
+ end
+
context 'with spam verdict service options' do
before do
allow(fake_verdict_service).to receive(:execute).and_return(ALLOW)
diff --git a/spec/services/spam/spam_verdict_service_spec.rb b/spec/services/spam/spam_verdict_service_spec.rb
index 14b788e3a86..215df81de63 100644
--- a/spec/services/spam/spam_verdict_service_spec.rb
+++ b/spec/services/spam/spam_verdict_service_spec.rb
@@ -7,28 +7,33 @@ RSpec.describe Spam::SpamVerdictService do
let(:fake_ip) { '1.2.3.4' }
let(:fake_user_agent) { 'fake-user-agent' }
- let(:fake_referrer) { 'fake-http-referrer' }
+ let(:fake_referer) { 'fake-http-referer' }
let(:env) do
{ 'action_dispatch.remote_ip' => fake_ip,
'HTTP_USER_AGENT' => fake_user_agent,
- 'HTTP_REFERRER' => fake_referrer }
+ 'HTTP_REFERER' => fake_referer }
end
let(:request) { double(:request, env: env) }
let(:check_for_spam) { true }
let_it_be(:user) { create(:user) }
- let(:issue) { build(:issue, author: user) }
+ let_it_be(:issue) { create(:issue, author: user) }
let(:service) do
described_class.new(user: user, target: issue, request: request, options: {})
end
+ let(:attribs) do
+ extra_attributes = { "monitorMode" => "false" }
+ extra_attributes
+ end
+
describe '#execute' do
subject { service.execute }
before do
allow(service).to receive(:akismet_verdict).and_return(nil)
- allow(service).to receive(:external_verdict).and_return(nil)
+ allow(service).to receive(:spamcheck_verdict).and_return([nil, attribs])
end
context 'if all services return nil' do
@@ -63,7 +68,7 @@ RSpec.describe Spam::SpamVerdictService do
context 'and they are supported' do
before do
allow(service).to receive(:akismet_verdict).and_return(DISALLOW)
- allow(service).to receive(:external_verdict).and_return(BLOCK_USER)
+ allow(service).to receive(:spamcheck_verdict).and_return([BLOCK_USER, attribs])
end
it 'renders the more restrictive verdict' do
@@ -74,7 +79,7 @@ RSpec.describe Spam::SpamVerdictService do
context 'and one is supported' do
before do
allow(service).to receive(:akismet_verdict).and_return('nonsense')
- allow(service).to receive(:external_verdict).and_return(BLOCK_USER)
+ allow(service).to receive(:spamcheck_verdict).and_return([BLOCK_USER, attribs])
end
it 'renders the more restrictive verdict' do
@@ -85,13 +90,56 @@ RSpec.describe Spam::SpamVerdictService do
context 'and none are supported' do
before do
allow(service).to receive(:akismet_verdict).and_return('nonsense')
- allow(service).to receive(:external_verdict).and_return('rubbish')
+ allow(service).to receive(:spamcheck_verdict).and_return(['rubbish', attribs])
end
it 'renders the more restrictive verdict' do
expect(subject).to eq ALLOW
end
end
+
+ context 'and attribs - monitorMode is true' do
+ let(:attribs) do
+ extra_attributes = { "monitorMode" => "true" }
+ extra_attributes
+ end
+
+ before do
+ allow(service).to receive(:akismet_verdict).and_return(DISALLOW)
+ allow(service).to receive(:spamcheck_verdict).and_return([BLOCK_USER, attribs])
+ end
+
+ it 'renders the more restrictive verdict' do
+ expect(subject).to eq(DISALLOW)
+ end
+ end
+ end
+
+ context 'records metrics' do
+ let(:histogram) { instance_double(Prometheus::Client::Histogram) }
+
+ using RSpec::Parameterized::TableSyntax
+
+ where(:verdict, :error, :label) do
+ Spam::SpamConstants::ALLOW | false | 'ALLOW'
+ Spam::SpamConstants::ALLOW | true | 'ERROR'
+ Spam::SpamConstants::CONDITIONAL_ALLOW | false | 'CONDITIONAL_ALLOW'
+ Spam::SpamConstants::BLOCK_USER | false | 'BLOCK'
+ Spam::SpamConstants::DISALLOW | false | 'DISALLOW'
+ Spam::SpamConstants::NOOP | false | 'NOOP'
+ end
+
+ with_them do
+ before do
+ allow(Gitlab::Metrics).to receive(:histogram).with(:gitlab_spamcheck_request_duration_seconds, anything).and_return(histogram)
+ allow(service).to receive(:spamcheck_verdict).and_return([verdict, attribs, error])
+ end
+
+ it 'records duration with labels' do
+ expect(histogram).to receive(:observe).with(a_hash_including(result: label), anything)
+ subject
+ end
+ end
end
end
@@ -150,48 +198,113 @@ RSpec.describe Spam::SpamVerdictService do
end
end
- describe '#external_verdict' do
- subject { service.send(:external_verdict) }
+ describe '#spamcheck_verdict' do
+ subject { service.send(:spamcheck_verdict) }
context 'if a Spam Check endpoint enabled and set to a URL' do
let(:spam_check_body) { {} }
- let(:spam_check_http_status) { nil }
+ let(:endpoint_url) { "grpc://www.spamcheckurl.com/spam_check" }
+
+ let(:spam_client) do
+ Gitlab::Spamcheck::Client.new
+ end
before do
stub_application_setting(spam_check_endpoint_enabled: true)
- stub_application_setting(spam_check_endpoint_url: "http://www.spamcheckurl.com/spam_check")
- stub_request(:post, /.*spamcheckurl.com.*/).to_return( body: spam_check_body.to_json, status: spam_check_http_status )
+ stub_application_setting(spam_check_endpoint_url: endpoint_url)
end
context 'if the endpoint is accessible' do
- let(:spam_check_http_status) { 200 }
- let(:error) { nil }
+ let(:error) { '' }
let(:verdict) { nil }
- let(:spam_check_body) do
- { verdict: verdict, error: error }
+
+ let(:attribs) do
+ extra_attributes = { "monitorMode" => "false" }
+ extra_attributes
+ end
+
+ before do
+ allow(service).to receive(:spamcheck_client).and_return(spam_client)
+ allow(spam_client).to receive(:issue_spam?).and_return([verdict, attribs, error])
+ end
+
+ context 'if the result is a NOOP verdict' do
+ let(:verdict) { NOOP }
+
+ it 'returns the verdict' do
+ expect(subject).to eq([NOOP, attribs])
+ end
+ end
+
+ context 'if attribs - monitorMode is true' do
+ let(:attribs) do
+ extra_attributes = { "monitorMode" => "true" }
+ extra_attributes
+ end
+
+ let(:verdict) { ALLOW }
+
+ it 'returns the verdict' do
+ expect(subject).to eq([ALLOW, attribs])
+ end
end
context 'the result is a valid verdict' do
- let(:verdict) { 'allow' }
+ let(:verdict) { ALLOW }
it 'returns the verdict' do
- expect(subject).to eq ALLOW
+ expect(subject).to eq([ALLOW, attribs])
end
end
- context 'the verdict is an unexpected string' do
- let(:verdict) { 'this is fine' }
+ context 'when recaptcha is enabled' do
+ before do
+ allow(Gitlab::Recaptcha).to receive(:enabled?).and_return(true)
+ end
- it 'returns the string' do
- expect(subject).to eq verdict
+ using RSpec::Parameterized::TableSyntax
+
+ # rubocop: disable Lint/BinaryOperatorWithIdenticalOperands
+ where(:verdict_value, :expected) do
+ ::Spam::SpamConstants::ALLOW | ::Spam::SpamConstants::ALLOW
+ ::Spam::SpamConstants::CONDITIONAL_ALLOW | ::Spam::SpamConstants::CONDITIONAL_ALLOW
+ ::Spam::SpamConstants::DISALLOW | ::Spam::SpamConstants::CONDITIONAL_ALLOW
+ ::Spam::SpamConstants::BLOCK_USER | ::Spam::SpamConstants::CONDITIONAL_ALLOW
+ end
+ # rubocop: enable Lint/BinaryOperatorWithIdenticalOperands
+
+ with_them do
+ let(:verdict) { verdict_value }
+
+ it "returns expected spam constant" do
+ expect(subject).to eq([expected, attribs])
+ end
end
end
- context 'the JSON is malformed' do
- let(:spam_check_body) { 'this is fine' }
+ context 'when recaptcha is disabled' do
+ before do
+ allow(Gitlab::Recaptcha).to receive(:enabled?).and_return(false)
+ end
+
+ [::Spam::SpamConstants::ALLOW,
+ ::Spam::SpamConstants::CONDITIONAL_ALLOW,
+ ::Spam::SpamConstants::DISALLOW,
+ ::Spam::SpamConstants::BLOCK_USER].each do |verdict_value|
+ let(:verdict) { verdict_value }
+ let(:expected) { [verdict_value, attribs] }
- it 'returns allow' do
- expect(subject).to eq ALLOW
+ it "returns expected spam constant" do
+ expect(subject).to eq(expected)
+ end
+ end
+ end
+
+ context 'the verdict is an unexpected value' do
+ let(:verdict) { :this_is_fine }
+
+ it 'returns the string' do
+ expect(subject).to eq([verdict, attribs])
end
end
@@ -199,7 +312,7 @@ RSpec.describe Spam::SpamVerdictService do
let(:verdict) { '' }
it 'returns nil' do
- expect(subject).to eq verdict
+ expect(subject).to eq([verdict, attribs])
end
end
@@ -207,7 +320,7 @@ RSpec.describe Spam::SpamVerdictService do
let(:verdict) { nil }
it 'returns nil' do
- expect(subject).to be_nil
+ expect(subject).to eq([nil, attribs])
end
end
@@ -215,15 +328,19 @@ RSpec.describe Spam::SpamVerdictService do
let(:error) { "Sorry Dave, I can't do that" }
it 'returns nil' do
- expect(subject).to be_nil
+ expect(subject).to eq([nil, attribs])
end
end
- context 'the HTTP status is not 200' do
- let(:spam_check_http_status) { 500 }
+ context 'the requested is aborted' do
+ let(:attribs) { nil }
+
+ before do
+ allow(spam_client).to receive(:issue_spam?).and_raise(GRPC::Aborted)
+ end
it 'returns nil' do
- expect(subject).to be_nil
+ expect(subject).to eq([ALLOW, attribs, true])
end
end
@@ -232,18 +349,20 @@ RSpec.describe Spam::SpamVerdictService do
let(:error) { 'oh noes!' }
it 'renders the verdict' do
- expect(subject).to eq DISALLOW
+ expect(subject).to eq [DISALLOW, attribs]
end
end
end
context 'if the endpoint times out' do
+ let(:attribs) { nil }
+
before do
- stub_request(:post, /.*spamcheckurl.com.*/).to_timeout
+ allow(spam_client).to receive(:issue_spam?).and_raise(GRPC::DeadlineExceeded)
end
it 'returns nil' do
- expect(subject).to be_nil
+ expect(subject).to eq([ALLOW, attribs, true])
end
end
end
diff --git a/spec/services/submit_usage_ping_service_spec.rb b/spec/services/submit_usage_ping_service_spec.rb
index 53cc33afcff..a9f1b2c2b2d 100644
--- a/spec/services/submit_usage_ping_service_spec.rb
+++ b/spec/services/submit_usage_ping_service_spec.rb
@@ -217,7 +217,7 @@ RSpec.describe SubmitUsagePingService do
end
def stub_response(body:, status: 201)
- stub_full_request(SubmitUsagePingService::STAGING_URL, method: :post)
+ stub_full_request(subject.send(:url), method: :post)
.to_return(
headers: { 'Content-Type' => 'application/json' },
body: body.to_json,
diff --git a/spec/services/suggestions/apply_service_spec.rb b/spec/services/suggestions/apply_service_spec.rb
index 77d0e892725..9cf794cde7e 100644
--- a/spec/services/suggestions/apply_service_spec.rb
+++ b/spec/services/suggestions/apply_service_spec.rb
@@ -67,11 +67,13 @@ RSpec.describe Suggestions::ApplyService do
apply(suggestions)
commit = project.repository.commit
+ author = suggestions.first.note.author
expect(user.commit_email).not_to eq(user.email)
- expect(commit.author_email).to eq(user.commit_email)
+ expect(commit.author_email).to eq(author.commit_email)
expect(commit.committer_email).to eq(user.commit_email)
- expect(commit.author_name).to eq(user.name)
+ expect(commit.author_name).to eq(author.name)
+ expect(commit.committer_name).to eq(user.name)
end
it 'tracks apply suggestion event' do
@@ -319,6 +321,73 @@ RSpec.describe Suggestions::ApplyService do
end
end
+ context 'single suggestion' do
+ let(:author) { suggestions.first.note.author }
+ let(:commit) { project.repository.commit }
+
+ context 'author of suggestion applies suggestion' do
+ before do
+ suggestion.note.update!(author_id: user.id)
+
+ apply(suggestions)
+ end
+
+ it 'created commit by same author and committer' do
+ expect(user.commit_email).to eq(author.commit_email)
+ expect(author).to eq(user)
+ expect(commit.author_email).to eq(author.commit_email)
+ expect(commit.committer_email).to eq(user.commit_email)
+ expect(commit.author_name).to eq(author.name)
+ expect(commit.committer_name).to eq(user.name)
+ end
+ end
+
+ context 'another user applies suggestion' do
+ before do
+ apply(suggestions)
+ end
+
+ it 'created commit has authors info and commiters info' do
+ expect(user.commit_email).not_to eq(user.email)
+ expect(author).not_to eq(user)
+ expect(commit.author_email).to eq(author.commit_email)
+ expect(commit.committer_email).to eq(user.commit_email)
+ expect(commit.author_name).to eq(author.name)
+ expect(commit.committer_name).to eq(user.name)
+ end
+ end
+ end
+
+ context 'multiple suggestions' do
+ let(:author_emails) { suggestions.map {|s| s.note.author.commit_email } }
+ let(:first_author) { suggestion.note.author }
+ let(:commit) { project.repository.commit }
+
+ context 'when all the same author' do
+ before do
+ apply(suggestions)
+ end
+
+ it 'uses first authors information' do
+ expect(author_emails).to include(first_author.commit_email).exactly(3)
+ expect(commit.author_email).to eq(first_author.commit_email)
+ end
+ end
+
+ context 'when all different authors' do
+ before do
+ suggestion2.note.update!(author_id: create(:user).id)
+ suggestion3.note.update!(author_id: create(:user).id)
+ apply(suggestions)
+ end
+
+ it 'uses committers information' do
+ expect(commit.author_email).to eq(user.commit_email)
+ expect(commit.committer_email).to eq(user.commit_email)
+ end
+ end
+ end
+
context 'multiple suggestions applied sequentially' do
def apply_suggestion(suggestion)
suggestion.reload
@@ -329,7 +398,7 @@ RSpec.describe Suggestions::ApplyService do
suggestion.reload
expect(result[:status]).to eq(:success)
- refresh = MergeRequests::RefreshService.new(project, user)
+ refresh = MergeRequests::RefreshService.new(project: project, current_user: user)
refresh.execute(merge_request.diff_head_sha,
suggestion.commit_id,
merge_request.source_branch_ref)
diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb
index d8435c72896..5d60b6e0487 100644
--- a/spec/services/system_hooks_service_spec.rb
+++ b/spec/services/system_hooks_service_spec.rb
@@ -3,133 +3,68 @@
require 'spec_helper'
RSpec.describe SystemHooksService do
- let(:user) { create(:user) }
- let(:project) { create(:project) }
- let(:project_member) { create(:project_member) }
- let(:key) { create(:key, user: user) }
- let(:deploy_key) { create(:key) }
- let(:group) { create(:group) }
- let(:group_member) { create(:group_member) }
-
- context 'event data' do
- it { expect(event_data(user, :create)).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id, :username) }
- it { expect(event_data(user, :destroy)).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id, :username) }
- it { expect(event_data(project, :create)).to include(:event_name, :name, :created_at, :updated_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) }
- it { expect(event_data(project, :update)).to include(:event_name, :name, :created_at, :updated_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) }
- it { expect(event_data(project, :destroy)).to include(:event_name, :name, :created_at, :updated_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) }
- it { expect(event_data(project_member, :create)).to include(:event_name, :created_at, :updated_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_username, :user_email, :user_id, :access_level, :project_visibility) }
- it { expect(event_data(project_member, :destroy)).to include(:event_name, :created_at, :updated_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_username, :user_email, :user_id, :access_level, :project_visibility) }
- it { expect(event_data(project_member, :update)).to include(:event_name, :created_at, :updated_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_username, :user_email, :user_id, :access_level, :project_visibility) }
- it { expect(event_data(key, :create)).to include(:username, :key, :id) }
- it { expect(event_data(key, :destroy)).to include(:username, :key, :id) }
- it { expect(event_data(deploy_key, :create)).to include(:key, :id) }
- it { expect(event_data(deploy_key, :destroy)).to include(:key, :id) }
-
- it do
- project.old_path_with_namespace = 'renamed_from_path'
- expect(event_data(project, :rename)).to include(
- :event_name, :name, :created_at, :updated_at, :path, :project_id,
- :owner_name, :owner_email, :project_visibility,
- :old_path_with_namespace
- )
+ describe '#execute_hooks_for' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:group_member) { create(:group_member, source: group, user: user) }
+ let_it_be(:project_member) { create(:project_member, source: project, user: user) }
+ let_it_be(:key) { create(:key, user: user) }
+ let_it_be(:deploy_key) { create(:key) }
+
+ let(:event) { :create }
+
+ using RSpec::Parameterized::TableSyntax
+
+ where(:model_name, :builder_class) do
+ :group_member | Gitlab::HookData::GroupMemberBuilder
+ :group | Gitlab::HookData::GroupBuilder
+ :project_member | Gitlab::HookData::ProjectMemberBuilder
+ :user | Gitlab::HookData::UserBuilder
+ :project | Gitlab::HookData::ProjectBuilder
+ :key | Gitlab::HookData::KeyBuilder
+ :deploy_key | Gitlab::HookData::KeyBuilder
end
- it do
- project.old_path_with_namespace = 'transferred_from_path'
- expect(event_data(project, :transfer)).to include(
- :event_name, :name, :created_at, :updated_at, :path, :project_id,
- :owner_name, :owner_email, :project_visibility,
- :old_path_with_namespace
- )
- end
-
- it do
- expect(event_data(group, :create)).to include(
- :event_name, :name, :created_at, :updated_at, :path, :group_id
- )
- end
+ with_them do
+ it 'builds the data with the relevant builder class and then calls #execute_hooks with the obtained data' do
+ data = double
+ model = public_send(model_name)
- it do
- expect(event_data(group, :destroy)).to include(
- :event_name, :name, :created_at, :updated_at, :path, :group_id
- )
- end
+ expect_next_instance_of(builder_class, model) do |builder|
+ expect(builder).to receive(:build).with(event).and_return(data)
+ end
- it do
- expect(event_data(group_member, :create)).to include(
- :event_name, :created_at, :updated_at, :group_name, :group_path,
- :group_id, :user_id, :user_username, :user_name, :user_email, :group_access
- )
- end
+ service = described_class.new
- it do
- expect(event_data(group_member, :destroy)).to include(
- :event_name, :created_at, :updated_at, :group_name, :group_path,
- :group_id, :user_id, :user_username, :user_name, :user_email, :group_access
- )
- end
-
- it do
- expect(event_data(group_member, :update)).to include(
- :event_name, :created_at, :updated_at, :group_name, :group_path,
- :group_id, :user_id, :user_username, :user_name, :user_email, :group_access
- )
- end
-
- it 'includes the correct project visibility level' do
- data = event_data(project, :create)
-
- expect(data[:project_visibility]).to eq('private')
- end
+ expect_next_instance_of(SystemHooksService) do |system_hook_service|
+ expect(system_hook_service).to receive(:execute_hooks).with(data)
+ end
- it 'handles nil datetime columns' do
- user.update!(created_at: nil, updated_at: nil)
- data = event_data(user, :destroy)
-
- expect(data[:created_at]).to be(nil)
- expect(data[:updated_at]).to be(nil)
+ service.execute_hooks_for(model, event)
+ end
end
+ end
- context 'group_rename' do
- it 'contains old and new path' do
- allow(group).to receive(:path_before_last_save).and_return('old-path')
+ describe '#execute_hooks' do
+ let(:data) { { key: :value } }
- data = event_data(group, :rename)
+ subject { described_class.new.execute_hooks(data) }
- expect(data).to include(:event_name, :name, :created_at, :updated_at, :full_path, :path, :group_id, :old_path, :old_full_path)
- expect(data[:path]).to eq(group.path)
- expect(data[:full_path]).to eq(group.path)
- expect(data[:old_path]).to eq(group.path_before_last_save)
- expect(data[:old_full_path]).to eq(group.path_before_last_save)
- end
+ it 'executes system hooks with the given data' do
+ hook = create(:system_hook)
- it 'contains old and new full_path for subgroup' do
- subgroup = create(:group, parent: group)
- allow(subgroup).to receive(:path_before_last_save).and_return('old-path')
+ allow(SystemHook).to receive_message_chain(:hooks_for, :find_each).and_yield(hook)
- data = event_data(subgroup, :rename)
+ expect(hook).to receive(:async_execute).with(data, 'system_hooks')
- expect(data[:full_path]).to eq(subgroup.full_path)
- expect(data[:old_path]).to eq('old-path')
- end
+ subject
end
- end
- context 'event names' do
- it { expect(event_name(project, :create)).to eq "project_create" }
- it { expect(event_name(project, :destroy)).to eq "project_destroy" }
- it { expect(event_name(project, :rename)).to eq "project_rename" }
- it { expect(event_name(project, :transfer)).to eq "project_transfer" }
- it { expect(event_name(project, :update)).to eq "project_update" }
- it { expect(event_name(key, :create)).to eq 'key_create' }
- it { expect(event_name(key, :destroy)).to eq 'key_destroy' }
- end
+ it 'executes FileHook with the given data' do
+ expect(Gitlab::FileHook).to receive(:execute_all_async).with(data)
- def event_data(*args)
- SystemHooksService.new.send :build_event_data, *args
- end
-
- def event_name(*args)
- SystemHooksService.new.send :build_event_name, *args
+ subject
+ end
end
end
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index 59f936509df..6a8e6dc8970 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -345,17 +345,10 @@ RSpec.describe TodoService do
describe '#destroy_target' do
it 'refreshes the todos count cache for users with todos on the target' do
- create(:todo, state: :pending, target: issue, user: john_doe, author: john_doe, project: issue.project)
+ create(:todo, state: :pending, target: issue, user: author, author: author, project: issue.project)
+ create(:todo, state: :done, target: issue, user: assignee, author: assignee, project: issue.project)
- expect_next(Users::UpdateTodoCountCacheService, [john_doe]).to receive(:execute)
-
- service.destroy_target(issue) { issue.destroy! }
- end
-
- it 'does not refresh the todos count cache for users with only done todos on the target' do
- create(:todo, :done, target: issue, user: john_doe, author: john_doe, project: issue.project)
-
- expect(Users::UpdateTodoCountCacheService).not_to receive(:new)
+ expect_next(Users::UpdateTodoCountCacheService, [author.id, assignee.id]).to receive(:execute)
service.destroy_target(issue) { issue.destroy! }
end
@@ -1101,7 +1094,7 @@ RSpec.describe TodoService do
it 'updates cached counts when a todo is created' do
issue = create(:issue, project: project, assignees: [john_doe], author: author)
- expect_next(Users::UpdateTodoCountCacheService, [john_doe]).to receive(:execute)
+ expect_next(Users::UpdateTodoCountCacheService, [john_doe.id]).to receive(:execute)
service.new_issue(issue, author)
end
diff --git a/spec/services/users/ban_service_spec.rb b/spec/services/users/ban_service_spec.rb
new file mode 100644
index 00000000000..0e6ac615da5
--- /dev/null
+++ b/spec/services/users/ban_service_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Users::BanService do
+ let(:current_user) { create(:admin) }
+
+ subject(:service) { described_class.new(current_user) }
+
+ describe '#execute' do
+ subject(:operation) { service.execute(user) }
+
+ context 'when successful' do
+ let(:user) { create(:user) }
+
+ it { is_expected.to eq(status: :success) }
+
+ it "bans the user" do
+ expect { operation }.to change { user.state }.to('banned')
+ end
+
+ it "blocks the user" do
+ expect { operation }.to change { user.blocked? }.from(false).to(true)
+ end
+
+ it 'logs ban in application logs' do
+ allow(Gitlab::AppLogger).to receive(:info)
+
+ operation
+
+ expect(Gitlab::AppLogger).to have_received(:info).with(message: "User banned", user: "#{user.username}", email: "#{user.email}", banned_by: "#{current_user.username}", ip_address: "#{current_user.current_sign_in_ip}")
+ end
+ end
+
+ context 'when failed' do
+ let(:user) { create(:user, :blocked) }
+
+ it 'returns error result' do
+ aggregate_failures 'error result' do
+ expect(operation[:status]).to eq(:error)
+ expect(operation[:message]).to match(/State cannot transition/)
+ end
+ end
+
+ it "does not change the user's state" do
+ expect { operation }.not_to change { user.state }
+ end
+ end
+ end
+end
diff --git a/spec/services/users/build_service_spec.rb b/spec/services/users/build_service_spec.rb
index b2a7d349ce6..e8786c677d1 100644
--- a/spec/services/users/build_service_spec.rb
+++ b/spec/services/users/build_service_spec.rb
@@ -6,105 +6,76 @@ RSpec.describe Users::BuildService do
using RSpec::Parameterized::TableSyntax
describe '#execute' do
- let(:params) { build_stubbed(:user).slice(:first_name, :last_name, :username, :email, :password) }
-
- context 'with an admin user' do
- let(:params) { build_stubbed(:user).slice(:name, :username, :email, :password) }
+ let_it_be(:current_user) { nil }
- let(:admin_user) { create(:admin) }
- let(:service) { described_class.new(admin_user, ActionController::Parameters.new(params).permit!) }
+ let(:params) { build_stubbed(:user).slice(:first_name, :last_name, :username, :email, :password) }
+ let(:service) { described_class.new(current_user, params) }
- it 'returns a valid user' do
- expect(service.execute).to be_valid
- end
+ shared_examples_for 'common build items' do
+ it { is_expected.to be_valid }
it 'sets the created_by_id' do
- expect(service.execute.created_by_id).to eq(admin_user.id)
+ expect(user.created_by_id).to eq(current_user&.id)
end
- context 'calls the UpdateCanonicalEmailService' do
- specify do
- expect(Users::UpdateCanonicalEmailService).to receive(:new).and_call_original
+ it 'calls UpdateCanonicalEmailService' do
+ expect(Users::UpdateCanonicalEmailService).to receive(:new).and_call_original
- service.execute
- end
+ user
end
- context 'allowed params' do
- let(:params) do
- {
- access_level: 1,
- admin: 1,
- avatar: anything,
- bio: 1,
- can_create_group: 1,
- color_scheme_id: 1,
- email: 1,
- external: 1,
- force_random_password: 1,
- hide_no_password: 1,
- hide_no_ssh_key: 1,
- linkedin: 1,
- name: 1,
- password: 1,
- password_automatically_set: 1,
- password_expires_at: 1,
- projects_limit: 1,
- remember_me: 1,
- skip_confirmation: 1,
- skype: 1,
- theme_id: 1,
- twitter: 1,
- username: 1,
- website_url: 1,
- private_profile: 1,
- organization: 1,
- location: 1,
- public_email: 1
- }
- end
+ context 'when user_type is provided' do
+ context 'when project_bot' do
+ before do
+ params.merge!({ user_type: :project_bot })
+ end
- it 'sets all allowed attributes' do
- admin_user # call first so the admin gets created before setting `expect`
+ it { expect(user.project_bot?).to be true }
+ end
- expect(User).to receive(:new).with(hash_including(params)).and_call_original
+ context 'when not a project_bot' do
+ before do
+ params.merge!({ user_type: :alert_bot })
+ end
- service.execute
+ it { expect(user).to be_human }
end
end
+ end
+ shared_examples_for 'current user not admin' do
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 | false
+ 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' | '' | false
+ true | false | 'fl@example.com' | '' | true # admin difference
true | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
- true | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | true
+ 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?' | false
+ true | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true # admin difference
false | nil | 'fl@example.com' | nil | false
- false | true | 'fl@example.com' | nil | true
+ 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' | '' | true
+ 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?' | true
+ 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?' | true
+ false | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false # admin difference
false | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
end
@@ -116,40 +87,11 @@ RSpec.describe Users::BuildService do
params.merge!({ external: external, email: email }.compact)
end
- subject(:user) { service.execute }
-
- it 'correctly sets user.external' do
+ it 'sets the value of Gitlab::CurrentSettings.user_default_external' do
expect(user.external).to eq(result)
end
end
end
- end
-
- context 'with non admin user' do
- let(:user) { create(:user) }
- let(:service) { described_class.new(user, params) }
-
- it 'raises AccessDeniedError exception' do
- expect { service.execute }.to raise_error Gitlab::Access::AccessDeniedError
- end
-
- context 'when authorization is skipped' do
- subject(:built_user) { service.execute(skip_authorization: true) }
-
- it { is_expected.to be_valid }
-
- it 'sets the created_by_id' do
- expect(built_user.created_by_id).to eq(user.id)
- end
- end
- end
-
- context 'with nil user' do
- let(:service) { described_class.new(nil, params) }
-
- it 'returns a valid user' do
- expect(service.execute).to be_valid
- end
context 'when "send_user_confirmation_email" application setting is true' do
before do
@@ -157,7 +99,7 @@ RSpec.describe Users::BuildService do
end
it 'does not confirm the user' do
- expect(service.execute).not_to be_confirmed
+ expect(user).not_to be_confirmed
end
end
@@ -167,27 +109,103 @@ RSpec.describe Users::BuildService do
end
it 'confirms the user' do
- expect(service.execute).to be_confirmed
+ expect(user).to be_confirmed
end
end
- context 'when user_type is provided' do
- subject(:user) { service.execute }
+ context 'with allowed params' do
+ let(:params) do
+ {
+ email: 1,
+ name: 1,
+ password: 1,
+ password_automatically_set: 1,
+ username: 1,
+ user_type: 'project_bot'
+ }
+ end
- context 'when project_bot' do
- before do
- params.merge!({ user_type: :project_bot })
- end
+ it 'sets all allowed attributes' do
+ expect(User).to receive(:new).with(hash_including(params)).and_call_original
- it { expect(user.project_bot?).to be true}
+ user
end
+ end
+ end
- context 'when not a project_bot' do
- before do
- params.merge!({ user_type: :alert_bot })
- end
+ context 'with nil current_user' do
+ subject(:user) { service.execute }
+
+ it_behaves_like 'common build items'
+ it_behaves_like 'current user not admin'
+ end
+
+ context 'with non admin current_user' do
+ let_it_be(:current_user) { create(:user) }
+
+ let(:service) { described_class.new(current_user, params) }
+
+ subject(:user) { service.execute(skip_authorization: true) }
+
+ it 'raises AccessDeniedError exception when authorization is not skipped' do
+ expect { service.execute }.to raise_error Gitlab::Access::AccessDeniedError
+ end
- it { expect(user.user_type).to be nil }
+ it_behaves_like 'common build items'
+ it_behaves_like 'current user not admin'
+ end
+
+ context 'with an admin current_user' do
+ let_it_be(:current_user) { create(:admin) }
+
+ let(:params) { build_stubbed(:user).slice(:name, :username, :email, :password) }
+ let(:service) { described_class.new(current_user, ActionController::Parameters.new(params).permit!) }
+
+ subject(:user) { service.execute }
+
+ it_behaves_like 'common build items'
+
+ context 'with allowed params' do
+ let(:params) do
+ {
+ access_level: 1,
+ admin: 1,
+ avatar: anything,
+ bio: 1,
+ can_create_group: 1,
+ color_scheme_id: 1,
+ email: 1,
+ external: 1,
+ force_random_password: 1,
+ hide_no_password: 1,
+ hide_no_ssh_key: 1,
+ linkedin: 1,
+ name: 1,
+ password: 1,
+ password_automatically_set: 1,
+ password_expires_at: 1,
+ projects_limit: 1,
+ remember_me: 1,
+ skip_confirmation: 1,
+ skype: 1,
+ theme_id: 1,
+ twitter: 1,
+ username: 1,
+ website_url: 1,
+ private_profile: 1,
+ organization: 1,
+ location: 1,
+ public_email: 1,
+ user_type: 'project_bot',
+ note: 1,
+ view_diffs_file_by_file: 1
+ }
+ end
+
+ it 'sets all allowed attributes' do
+ expect(User).to receive(:new).with(hash_including(params)).and_call_original
+
+ service.execute
end
end
@@ -195,34 +213,34 @@ RSpec.describe Users::BuildService 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
+ true | false | 'fl@example.com' | nil | false # admin difference
true | nil | 'fl@example.com' | '' | true
true | true | 'fl@example.com' | '' | true
- true | false | 'fl@example.com' | '' | true
+ true | false | 'fl@example.com' | '' | false # admin difference
true | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
- true | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
+ true | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | true # 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
+ true | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false # admin difference
false | nil | 'fl@example.com' | nil | false
- false | true | 'fl@example.com' | nil | false
+ false | true | 'fl@example.com' | nil | true # admin difference
false | false | 'fl@example.com' | nil | false
false | nil | 'fl@example.com' | '' | false
- false | true | 'fl@example.com' | '' | false
+ false | true | 'fl@example.com' | '' | true # admin difference
false | false | 'fl@example.com' | '' | false
false | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
- false | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
+ false | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | true # 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
+ false | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true # admin difference
false | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
end
@@ -234,8 +252,6 @@ RSpec.describe Users::BuildService do
params.merge!({ external: external, email: email }.compact)
end
- subject(:user) { service.execute }
-
it 'sets the value of Gitlab::CurrentSettings.user_default_external' do
expect(user.external).to eq(result)
end
diff --git a/spec/services/users/registrations_build_service_spec.rb b/spec/services/users/registrations_build_service_spec.rb
new file mode 100644
index 00000000000..bc3718dbdb2
--- /dev/null
+++ b/spec/services/users/registrations_build_service_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Users::RegistrationsBuildService do
+ describe '#execute' do
+ let(:base_params) { build_stubbed(:user).slice(:first_name, :last_name, :username, :email, :password) }
+ let(:skip_param) { {} }
+ let(:params) { base_params.merge(skip_param) }
+
+ subject(:service) { described_class.new(nil, params) }
+
+ before do
+ stub_application_setting(signup_enabled?: true)
+ end
+
+ context 'when automatic user confirmation is not enabled' do
+ before do
+ stub_application_setting(send_user_confirmation_email: true)
+ end
+
+ context 'when skip_confirmation is true' do
+ let(:skip_param) { { skip_confirmation: true } }
+
+ it 'confirms the user' do
+ expect(service.execute).to be_confirmed
+ end
+ end
+
+ context 'when skip_confirmation is not set' do
+ it 'does not confirm the user' do
+ expect(service.execute).not_to be_confirmed
+ end
+ end
+
+ context 'when skip_confirmation is false' do
+ let(:skip_param) { { skip_confirmation: false } }
+
+ it 'does not confirm the user' do
+ expect(service.execute).not_to be_confirmed
+ end
+ end
+ end
+
+ context 'when automatic user confirmation is enabled' do
+ before do
+ stub_application_setting(send_user_confirmation_email: false)
+ end
+
+ context 'when skip_confirmation is true' do
+ let(:skip_param) { { skip_confirmation: true } }
+
+ it 'confirms the user' do
+ expect(service.execute).to be_confirmed
+ end
+ end
+
+ context 'when skip_confirmation is not set the application setting takes precedence' do
+ it 'confirms the user' do
+ expect(service.execute).to be_confirmed
+ end
+ end
+
+ context 'when skip_confirmation is false the application setting takes precedence' do
+ let(:skip_param) { { skip_confirmation: false } }
+
+ it 'confirms the user' do
+ expect(service.execute).to be_confirmed
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/users/update_assigned_open_issue_count_service_spec.rb b/spec/services/users/update_assigned_open_issue_count_service_spec.rb
new file mode 100644
index 00000000000..55fc60a7893
--- /dev/null
+++ b/spec/services/users/update_assigned_open_issue_count_service_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Users::UpdateAssignedOpenIssueCountService do
+ let_it_be(:user) { create(:user) }
+
+ describe '#initialize' do
+ context 'incorrect arguments provided' do
+ it 'raises an error if there are no target user' do
+ expect { described_class.new(target_user: nil) }.to raise_error(ArgumentError, /Please provide a target user/)
+ expect { described_class.new(target_user: "nonsense") }.to raise_error(ArgumentError, /Please provide a target user/)
+ end
+ end
+
+ context 'when correct arguments provided' do
+ it 'is successful' do
+ expect { described_class.new(target_user: user) }.not_to raise_error
+ end
+ end
+ end
+
+ describe "#execute", :clean_gitlab_redis_cache do
+ let(:fake_update_service) { double }
+ let(:fake_issue_count_service) { double }
+ let(:provided_value) { nil }
+
+ subject { described_class.new(target_user: user).execute }
+
+ context 'successful' do
+ it 'returns a success response' do
+ expect(subject).to be_success
+ end
+
+ it 'writes the cache with the new value' do
+ expect(Rails.cache).to receive(:write).with(['users', user.id, 'assigned_open_issues_count'], 0, expires_in: User::COUNT_CACHE_VALIDITY_PERIOD)
+
+ subject
+ end
+
+ it 'calls the issues finder to get the latest value' do
+ expect(IssuesFinder).to receive(:new).with(user, assignee_id: user.id, state: 'opened', non_archived: true).and_return(fake_issue_count_service)
+ expect(fake_issue_count_service).to receive(:execute)
+
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/services/users/update_todo_count_cache_service_spec.rb b/spec/services/users/update_todo_count_cache_service_spec.rb
index 3e3618b1291..3d96af928df 100644
--- a/spec/services/users/update_todo_count_cache_service_spec.rb
+++ b/spec/services/users/update_todo_count_cache_service_spec.rb
@@ -14,13 +14,21 @@ RSpec.describe Users::UpdateTodoCountCacheService do
let_it_be(:todo5) { create(:todo, user: user2, state: :pending) }
let_it_be(:todo6) { create(:todo, user: user2, state: :pending) }
+ def execute_all
+ described_class.new([user1.id, user2.id]).execute
+ end
+
+ def execute_single
+ described_class.new([user1.id]).execute
+ end
+
it 'updates the todos_counts for users', :use_clean_rails_memory_store_caching do
Rails.cache.write(['users', user1.id, 'todos_done_count'], 0)
Rails.cache.write(['users', user1.id, 'todos_pending_count'], 0)
Rails.cache.write(['users', user2.id, 'todos_done_count'], 0)
Rails.cache.write(['users', user2.id, 'todos_pending_count'], 0)
- expect { described_class.new([user1, user2]).execute }
+ expect { execute_all }
.to change(user1, :todos_done_count).from(0).to(2)
.and change(user1, :todos_pending_count).from(0).to(1)
.and change(user2, :todos_done_count).from(0).to(1)
@@ -28,7 +36,7 @@ RSpec.describe Users::UpdateTodoCountCacheService do
Todo.delete_all
- expect { described_class.new([user1, user2]).execute }
+ expect { execute_all }
.to change(user1, :todos_done_count).from(2).to(0)
.and change(user1, :todos_pending_count).from(1).to(0)
.and change(user2, :todos_done_count).from(1).to(0)
@@ -36,26 +44,24 @@ RSpec.describe Users::UpdateTodoCountCacheService do
end
it 'avoids N+1 queries' do
- control_count = ActiveRecord::QueryRecorder.new { described_class.new([user1]).execute }.count
+ control_count = ActiveRecord::QueryRecorder.new { execute_single }.count
- expect { described_class.new([user1, user2]).execute }.not_to exceed_query_limit(control_count)
+ expect { execute_all }.not_to exceed_query_limit(control_count)
end
it 'executes one query per batch of users' do
stub_const("#{described_class}::QUERY_BATCH_SIZE", 1)
- expect(ActiveRecord::QueryRecorder.new { described_class.new([user1]).execute }.count).to eq(1)
- expect(ActiveRecord::QueryRecorder.new { described_class.new([user1, user2]).execute }.count).to eq(2)
+ expect(ActiveRecord::QueryRecorder.new { execute_single }.count).to eq(1)
+ expect(ActiveRecord::QueryRecorder.new { execute_all }.count).to eq(2)
end
- it 'sets the cache expire time to the users count_cache_validity_period' do
- allow(user1).to receive(:count_cache_validity_period).and_return(1.minute)
- allow(user2).to receive(:count_cache_validity_period).and_return(1.hour)
-
- expect(Rails.cache).to receive(:write).with(['users', user1.id, anything], anything, expires_in: 1.minute).twice
- expect(Rails.cache).to receive(:write).with(['users', user2.id, anything], anything, expires_in: 1.hour).twice
+ it 'sets the correct cache expire time' do
+ expect(Rails.cache).to receive(:write)
+ .with(['users', user1.id, anything], anything, expires_in: User::COUNT_CACHE_VALIDITY_PERIOD)
+ .twice
- described_class.new([user1, user2]).execute
+ execute_single
end
end
end
diff --git a/spec/services/users/upsert_credit_card_validation_service_spec.rb b/spec/services/users/upsert_credit_card_validation_service_spec.rb
new file mode 100644
index 00000000000..148638fe5e7
--- /dev/null
+++ b/spec/services/users/upsert_credit_card_validation_service_spec.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Users::UpsertCreditCardValidationService do
+ let_it_be(:user) { create(:user) }
+
+ let(:user_id) { user.id }
+ let(:credit_card_validated_time) { Time.utc(2020, 1, 1) }
+ let(:params) { { user_id: user_id, credit_card_validated_at: credit_card_validated_time } }
+
+ describe '#execute' do
+ subject(:service) { described_class.new(params) }
+
+ context 'successfully set credit card validation record for the user' do
+ context 'when user does not have credit card validation record' do
+ it 'creates the credit card validation and returns a success' do
+ expect(user.credit_card_validated_at).to be nil
+
+ result = service.execute
+
+ expect(result.status).to eq(:success)
+ expect(user.reload.credit_card_validated_at).to eq(credit_card_validated_time)
+ end
+ end
+
+ context 'when user has credit card validation record' do
+ let(:old_time) { Time.utc(1999, 2, 2) }
+
+ before do
+ create(:credit_card_validation, user: user, credit_card_validated_at: old_time)
+ end
+
+ it 'updates the credit card validation and returns a success' do
+ expect(user.credit_card_validated_at).to eq(old_time)
+
+ result = service.execute
+
+ expect(result.status).to eq(:success)
+ expect(user.reload.credit_card_validated_at).to eq(credit_card_validated_time)
+ end
+ end
+ end
+
+ shared_examples 'returns an error without tracking the exception' do
+ it do
+ expect(Gitlab::ErrorTracking).not_to receive(:track_exception)
+
+ result = service.execute
+
+ expect(result.status).to eq(:error)
+ end
+ end
+
+ context 'when user id does not exist' do
+ let(:user_id) { non_existing_record_id }
+
+ it_behaves_like 'returns an error without tracking the exception'
+ end
+
+ context 'when missing credit_card_validated_at' do
+ let(:params) { { user_id: user_id } }
+
+ it_behaves_like 'returns an error without tracking the exception'
+ end
+
+ context 'when missing user id' do
+ let(:params) { { credit_card_validated_at: credit_card_validated_time } }
+
+ it_behaves_like 'returns an error without tracking the exception'
+ end
+
+ context 'when unexpected exception happen' do
+ it 'tracks the exception and returns an error' do
+ expect(::Users::CreditCardValidation).to receive(:upsert).and_raise(e = StandardError.new('My exception!'))
+ expect(Gitlab::ErrorTracking).to receive(:track_exception).with(e, class: described_class.to_s, params: params)
+
+ result = service.execute
+
+ expect(result.status).to eq(:error)
+ end
+ end
+ end
+end
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
index 2fe72ab31c2..b3fd4e33640 100644
--- a/spec/services/web_hook_service_spec.rb
+++ b/spec/services/web_hook_service_spec.rb
@@ -5,8 +5,9 @@ require 'spec_helper'
RSpec.describe WebHookService do
include StubRequests
- let(:project) { create(:project) }
- let(:project_hook) { create(:project_hook) }
+ let_it_be(:project) { create(:project) }
+ let_it_be_with_reload(:project_hook) { create(:project_hook, project: project) }
+
let(:headers) do
{
'Content-Type' => 'application/json',
@@ -21,6 +22,10 @@ RSpec.describe WebHookService do
let(:service_instance) { described_class.new(project_hook, data, :push_hooks) }
+ around do |example|
+ travel_to(Time.current) { example.run }
+ end
+
describe '#initialize' do
before do
stub_application_setting(setting_name => setting)
@@ -56,12 +61,8 @@ RSpec.describe WebHookService do
end
describe '#execute' do
- before do
- project.hooks << [project_hook]
- end
-
context 'when token is defined' do
- let(:project_hook) { create(:project_hook, :token) }
+ let_it_be(:project_hook) { create(:project_hook, :token) }
it 'POSTs to the webhook URL' do
stub_full_request(project_hook.url, method: :post)
@@ -85,8 +86,8 @@ RSpec.describe WebHookService do
end
context 'when auth credentials are present' do
- let(:url) {'https://example.org'}
- let(:project_hook) { create(:project_hook, url: 'https://demo:demo@example.org/') }
+ let_it_be(:url) {'https://example.org'}
+ let_it_be(:project_hook) { create(:project_hook, url: 'https://demo:demo@example.org/') }
it 'uses the credentials' do
stub_full_request(url, method: :post)
@@ -100,8 +101,8 @@ RSpec.describe WebHookService do
end
context 'when auth credentials are partial present' do
- let(:url) {'https://example.org'}
- let(:project_hook) { create(:project_hook, url: 'https://demo@example.org/') }
+ let_it_be(:url) {'https://example.org'}
+ let_it_be(:project_hook) { create(:project_hook, url: 'https://demo@example.org/') }
it 'uses the credentials anyways' do
stub_full_request(url, method: :post)
@@ -120,10 +121,21 @@ RSpec.describe WebHookService do
expect { service_instance.execute }.to raise_error(StandardError)
end
+ it 'does not execute disabled hooks' do
+ project_hook.update!(recent_failures: 4)
+
+ expect(service_instance.execute).to eq({ status: :error, message: 'Hook disabled' })
+ end
+
it 'handles exceptions' do
- exceptions = [SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError, Gitlab::HTTP::RedirectionTooDeep]
+ exceptions = [
+ SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED,
+ Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout,
+ Gitlab::HTTP::BlockedUrlError, Gitlab::HTTP::RedirectionTooDeep
+ ]
exceptions.each do |exception_class|
exception = exception_class.new('Exception message')
+ project_hook.enable!
stub_full_request(project_hook.url, method: :post).to_raise(exception)
expect(service_instance.execute).to eq({ status: :error, message: exception.to_s })
@@ -132,7 +144,7 @@ RSpec.describe WebHookService do
end
context 'when url is not encoded' do
- let(:project_hook) { create(:project_hook, url: 'http://server.com/my path/') }
+ let_it_be(:project_hook) { create(:project_hook, url: 'http://server.com/my path/') }
it 'handles exceptions' do
expect(service_instance.execute).to eq(status: :error, message: 'bad URI(is not URI?): "http://server.com/my path/"')
@@ -166,10 +178,11 @@ RSpec.describe WebHookService do
context 'with success' do
before do
stub_full_request(project_hook.url, method: :post).to_return(status: 200, body: 'Success')
- service_instance.execute
end
it 'log successful execution' do
+ service_instance.execute
+
expect(hook_log.trigger).to eq('push_hooks')
expect(hook_log.url).to eq(project_hook.url)
expect(hook_log.request_headers).to eq(headers)
@@ -178,15 +191,81 @@ RSpec.describe WebHookService do
expect(hook_log.execution_duration).to be > 0
expect(hook_log.internal_error_message).to be_nil
end
+
+ it 'does not increment the failure count' do
+ expect { service_instance.execute }.not_to change(project_hook, :recent_failures)
+ end
+
+ it 'does not change the disabled_until attribute' do
+ expect { service_instance.execute }.not_to change(project_hook, :disabled_until)
+ end
+
+ context 'when the hook had previously failed' do
+ before do
+ project_hook.update!(recent_failures: 2)
+ end
+
+ it 'resets the failure count' do
+ expect { service_instance.execute }.to change(project_hook, :recent_failures).to(0)
+ end
+ end
+ end
+
+ context 'with bad request' do
+ before do
+ stub_full_request(project_hook.url, method: :post).to_return(status: 400, body: 'Bad request')
+ end
+
+ it 'logs failed execution' do
+ service_instance.execute
+
+ expect(hook_log).to have_attributes(
+ trigger: eq('push_hooks'),
+ url: eq(project_hook.url),
+ request_headers: eq(headers),
+ response_body: eq('Bad request'),
+ response_status: eq('400'),
+ execution_duration: be > 0,
+ internal_error_message: be_nil
+ )
+ end
+
+ it 'increments the failure count' do
+ expect { service_instance.execute }.to change(project_hook, :recent_failures).by(1)
+ end
+
+ it 'does not change the disabled_until attribute' do
+ expect { service_instance.execute }.not_to change(project_hook, :disabled_until)
+ end
+
+ it 'does not allow the failure count to overflow' do
+ project_hook.update!(recent_failures: 32767)
+
+ expect { service_instance.execute }.not_to change(project_hook, :recent_failures)
+ end
+
+ context 'when the web_hooks_disable_failed FF is disabled' do
+ before do
+ # Hook will only be executed if the flag is disabled.
+ stub_feature_flags(web_hooks_disable_failed: false)
+ end
+
+ it 'does not allow the failure count to overflow' do
+ project_hook.update!(recent_failures: 32767)
+
+ expect { service_instance.execute }.not_to change(project_hook, :recent_failures)
+ end
+ end
end
context 'with exception' do
before do
stub_full_request(project_hook.url, method: :post).to_raise(SocketError.new('Some HTTP Post error'))
- service_instance.execute
end
it 'log failed execution' do
+ service_instance.execute
+
expect(hook_log.trigger).to eq('push_hooks')
expect(hook_log.url).to eq(project_hook.url)
expect(hook_log.request_headers).to eq(headers)
@@ -195,6 +274,47 @@ RSpec.describe WebHookService do
expect(hook_log.execution_duration).to be > 0
expect(hook_log.internal_error_message).to eq('Some HTTP Post error')
end
+
+ it 'does not increment the failure count' do
+ expect { service_instance.execute }.not_to change(project_hook, :recent_failures)
+ end
+
+ it 'sets the disabled_until attribute' do
+ expect { service_instance.execute }
+ .to change(project_hook, :disabled_until).to(project_hook.next_backoff.from_now)
+ end
+
+ it 'increases the backoff count' do
+ expect { service_instance.execute }.to change(project_hook, :backoff_count).by(1)
+ end
+
+ context 'when the previous cool-off was near the maximum' do
+ before do
+ project_hook.update!(disabled_until: 5.minutes.ago, backoff_count: 8)
+ end
+
+ it 'sets the disabled_until attribute' do
+ expect { service_instance.execute }.to change(project_hook, :disabled_until).to(1.day.from_now)
+ end
+
+ it 'sets the last_backoff attribute' do
+ expect { service_instance.execute }.to change(project_hook, :backoff_count).by(1)
+ end
+ end
+
+ context 'when we have backed-off many many times' do
+ before do
+ project_hook.update!(disabled_until: 5.minutes.ago, backoff_count: 365)
+ end
+
+ it 'sets the disabled_until attribute' do
+ expect { service_instance.execute }.to change(project_hook, :disabled_until).to(1.day.from_now)
+ end
+
+ it 'sets the last_backoff attribute' do
+ expect { service_instance.execute }.to change(project_hook, :backoff_count).by(1)
+ end
+ end
end
context 'with unsafe response body' do
@@ -217,12 +337,98 @@ RSpec.describe WebHookService do
end
describe '#async_execute' do
- let(:system_hook) { create(:system_hook) }
+ def expect_to_perform_worker(hook)
+ expect(WebHookWorker).to receive(:perform_async).with(hook.id, data, 'push_hooks')
+ end
+
+ def expect_to_rate_limit(hook, threshold:, throttled: false)
+ expect(Gitlab::ApplicationRateLimiter).to receive(:throttled?)
+ .with(:web_hook_calls, scope: [hook], threshold: threshold)
+ .and_return(throttled)
+ end
+
+ context 'when rate limiting is not configured' do
+ it 'queues a worker without tracking the call' do
+ expect(Gitlab::ApplicationRateLimiter).not_to receive(:throttled?)
+ expect_to_perform_worker(project_hook)
+
+ service_instance.async_execute
+ end
+ end
+
+ context 'when rate limiting is configured' do
+ let_it_be(:threshold) { 3 }
+ let_it_be(:plan_limits) { create(:plan_limits, :default_plan, web_hook_calls: threshold) }
+
+ it 'queues a worker and tracks the call' do
+ expect_to_rate_limit(project_hook, threshold: threshold)
+ expect_to_perform_worker(project_hook)
+
+ service_instance.async_execute
+ end
+
+ context 'when the hook is throttled (via mock)' do
+ before do
+ expect_to_rate_limit(project_hook, threshold: threshold, throttled: true)
+ end
+
+ it 'does not queue a worker and logs an error' do
+ expect(WebHookWorker).not_to receive(:perform_async)
- it 'enqueue WebHookWorker' do
- expect(WebHookWorker).to receive(:perform_async).with(project_hook.id, data, 'push_hooks')
+ payload = {
+ message: 'Webhook rate limit exceeded',
+ hook_id: project_hook.id,
+ hook_type: 'ProjectHook',
+ hook_name: 'push_hooks'
+ }
- described_class.new(project_hook, data, 'push_hooks').async_execute
+ expect(Gitlab::AuthLogger).to receive(:error).with(payload)
+ expect(Gitlab::AppLogger).to receive(:error).with(payload)
+
+ service_instance.async_execute
+ end
+ end
+
+ context 'when the hook is throttled (via Redis)', :clean_gitlab_redis_cache do
+ before do
+ # Set a high interval to avoid intermittent failures in CI
+ allow(Gitlab::ApplicationRateLimiter).to receive(:rate_limits).and_return(
+ web_hook_calls: { interval: 1.day }
+ )
+
+ expect_to_perform_worker(project_hook).exactly(threshold).times
+
+ threshold.times { service_instance.async_execute }
+ end
+
+ it 'stops queueing workers and logs errors' do
+ expect(Gitlab::AuthLogger).to receive(:error).twice
+ expect(Gitlab::AppLogger).to receive(:error).twice
+
+ 2.times { service_instance.async_execute }
+ end
+
+ it 'still queues workers for other hooks' do
+ other_hook = create(:project_hook)
+
+ expect_to_perform_worker(other_hook)
+
+ described_class.new(other_hook, data, :push_hooks).async_execute
+ end
+ end
+
+ context 'when the feature flag is disabled' do
+ before do
+ stub_feature_flags(web_hooks_rate_limit: false)
+ end
+
+ it 'queues a worker without tracking the call' do
+ expect(Gitlab::ApplicationRateLimiter).not_to receive(:throttled?)
+ expect_to_perform_worker(project_hook)
+
+ service_instance.async_execute
+ end
+ end
end
end
end
diff --git a/spec/services/web_hooks/destroy_service_spec.rb b/spec/services/web_hooks/destroy_service_spec.rb
index fda40eb01e2..5269fe08ac0 100644
--- a/spec/services/web_hooks/destroy_service_spec.rb
+++ b/spec/services/web_hooks/destroy_service_spec.rb
@@ -41,15 +41,15 @@ RSpec.describe WebHooks::DestroyService do
end
context 'with system hook' do
- let_it_be(:hook) { create(:system_hook, url: "http://example.com") }
- let_it_be(:log) { create_list(:web_hook_log, 3, web_hook: hook) }
+ let!(:hook) { create(:system_hook, url: "http://example.com") }
+ let!(:log) { create_list(:web_hook_log, 3, web_hook: hook) }
it_behaves_like 'batched destroys'
end
context 'with project hook' do
- let_it_be(:hook) { create(:project_hook) }
- let_it_be(:log) { create_list(:web_hook_log, 3, web_hook: hook) }
+ let!(:hook) { create(:project_hook) }
+ let!(:log) { create_list(:web_hook_log, 3, web_hook: hook) }
it_behaves_like 'batched destroys'
end