summaryrefslogtreecommitdiff
path: root/spec/services
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-06-20 11:10:13 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-06-20 11:10:13 +0000
commit0ea3fcec397b69815975647f5e2aa5fe944a8486 (patch)
tree7979381b89d26011bcf9bdc989a40fcc2f1ed4ff /spec/services
parent72123183a20411a36d607d70b12d57c484394c8e (diff)
downloadgitlab-ce-0ea3fcec397b69815975647f5e2aa5fe944a8486.tar.gz
Add latest changes from gitlab-org/gitlab@15-1-stable-eev15.1.0-rc42
Diffstat (limited to 'spec/services')
-rw-r--r--spec/services/alert_management/alerts/update_service_spec.rb8
-rw-r--r--spec/services/bulk_create_integration_service_spec.rb42
-rw-r--r--spec/services/bulk_imports/create_pipeline_trackers_service_spec.rb168
-rw-r--r--spec/services/bulk_imports/file_export_service_spec.rb41
-rw-r--r--spec/services/bulk_imports/lfs_objects_export_service_spec.rb12
-rw-r--r--spec/services/bulk_imports/repository_bundle_export_service_spec.rb46
-rw-r--r--spec/services/bulk_update_integration_service_spec.rb30
-rw-r--r--spec/services/ci/abort_pipelines_service_spec.rb23
-rw-r--r--spec/services/ci/after_requeue_job_service_spec.rb45
-rw-r--r--spec/services/ci/create_pipeline_service/rate_limit_spec.rb6
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb11
-rw-r--r--spec/services/ci/job_artifacts/destroy_all_expired_service_spec.rb10
-rw-r--r--spec/services/ci/job_artifacts/destroy_batch_service_spec.rb131
-rw-r--r--spec/services/ci/pipeline_artifacts/coverage_report_service_spec.rb32
-rw-r--r--spec/services/ci/process_sync_events_service_spec.rb5
-rw-r--r--spec/services/clusters/applications/schedule_update_service_spec.rb63
-rw-r--r--spec/services/deployments/create_service_spec.rb37
-rw-r--r--spec/services/deployments/update_environment_service_spec.rb4
-rw-r--r--spec/services/emails/confirm_service_spec.rb6
-rw-r--r--spec/services/environments/auto_stop_service_spec.rb2
-rw-r--r--spec/services/environments/stop_service_spec.rb60
-rw-r--r--spec/services/event_create_service_spec.rb80
-rw-r--r--spec/services/git/branch_push_service_spec.rb2
-rw-r--r--spec/services/import/fogbugz_service_spec.rb150
-rw-r--r--spec/services/incident_management/issuable_escalation_statuses/prepare_update_service_spec.rb8
-rw-r--r--spec/services/incident_management/timeline_events/create_service_spec.rb30
-rw-r--r--spec/services/incident_management/timeline_events/destroy_service_spec.rb16
-rw-r--r--spec/services/incident_management/timeline_events/update_service_spec.rb13
-rw-r--r--spec/services/integrations/propagate_service_spec.rb2
-rw-r--r--spec/services/issues/create_service_spec.rb45
-rw-r--r--spec/services/issues/move_service_spec.rb14
-rw-r--r--spec/services/issues/update_service_spec.rb8
-rw-r--r--spec/services/jira_connect_subscriptions/create_service_spec.rb46
-rw-r--r--spec/services/jira_import/start_import_service_spec.rb2
-rw-r--r--spec/services/jira_import/users_importer_spec.rb2
-rw-r--r--spec/services/markdown_content_rewriter_service_spec.rb83
-rw-r--r--spec/services/members/approve_access_request_service_spec.rb53
-rw-r--r--spec/services/members/create_service_spec.rb12
-rw-r--r--spec/services/members/creator_service_spec.rb4
-rw-r--r--spec/services/members/destroy_service_spec.rb42
-rw-r--r--spec/services/members/groups/bulk_creator_service_spec.rb10
-rw-r--r--spec/services/members/groups/creator_service_spec.rb16
-rw-r--r--spec/services/members/invite_service_spec.rb15
-rw-r--r--spec/services/members/mailgun/process_webhook_service_spec.rb42
-rw-r--r--spec/services/members/projects/bulk_creator_service_spec.rb10
-rw-r--r--spec/services/members/projects/creator_service_spec.rb16
-rw-r--r--spec/services/members/update_service_spec.rb76
-rw-r--r--spec/services/merge_requests/build_service_spec.rb104
-rw-r--r--spec/services/merge_requests/create_pipeline_service_spec.rb13
-rw-r--r--spec/services/merge_requests/create_service_spec.rb6
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb9
-rw-r--r--spec/services/merge_requests/mergeability/run_checks_service_spec.rb22
-rw-r--r--spec/services/merge_requests/post_merge_service_spec.rb26
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb34
-rw-r--r--spec/services/metrics/dashboard/panel_preview_service_spec.rb1
-rw-r--r--spec/services/notes/copy_service_spec.rb28
-rw-r--r--spec/services/notes/create_service_spec.rb4
-rw-r--r--spec/services/notification_recipients/build_service_spec.rb10
-rw-r--r--spec/services/notification_service_spec.rb27
-rw-r--r--spec/services/packages/cleanup/update_policy_service_spec.rb105
-rw-r--r--spec/services/packages/go/create_package_service_spec.rb16
-rw-r--r--spec/services/packages/maven/metadata/append_package_file_service_spec.rb21
-rw-r--r--spec/services/packages/rubygems/create_gemspec_service_spec.rb13
-rw-r--r--spec/services/pages/delete_service_spec.rb6
-rw-r--r--spec/services/pages_domains/create_acme_order_service_spec.rb10
-rw-r--r--spec/services/projects/after_rename_service_spec.rb34
-rw-r--r--spec/services/projects/autocomplete_service_spec.rb1
-rw-r--r--spec/services/projects/destroy_rollback_service_spec.rb46
-rw-r--r--spec/services/projects/destroy_service_spec.rb57
-rw-r--r--spec/services/projects/refresh_build_artifacts_size_statistics_service_spec.rb13
-rw-r--r--spec/services/projects/transfer_service_spec.rb12
-rw-r--r--spec/services/projects/update_pages_service_spec.rb19
-rw-r--r--spec/services/quick_actions/interpret_service_spec.rb166
-rw-r--r--spec/services/releases/create_service_spec.rb21
-rw-r--r--spec/services/repositories/changelog_service_spec.rb48
-rw-r--r--spec/services/repositories/destroy_rollback_service_spec.rb85
-rw-r--r--spec/services/repositories/destroy_service_spec.rb74
-rw-r--r--spec/services/repositories/shell_destroy_service_spec.rb26
-rw-r--r--spec/services/resource_access_tokens/create_service_spec.rb40
-rw-r--r--spec/services/service_response_spec.rb75
-rw-r--r--spec/services/snippets/bulk_destroy_service_spec.rb55
-rw-r--r--spec/services/snippets/destroy_service_spec.rb13
-rw-r--r--spec/services/static_site_editor/config_service_spec.rb126
-rw-r--r--spec/services/terraform/remote_state_handler_spec.rb40
-rw-r--r--spec/services/terraform/states/destroy_service_spec.rb36
-rw-r--r--spec/services/terraform/states/trigger_destroy_service_spec.rb45
-rw-r--r--spec/services/user_project_access_changed_service_spec.rb13
-rw-r--r--spec/services/web_hook_service_spec.rb201
-rw-r--r--spec/services/web_hooks/destroy_service_spec.rb68
-rw-r--r--spec/services/web_hooks/log_destroy_service_spec.rb56
-rw-r--r--spec/services/work_items/update_service_spec.rb15
91 files changed, 2330 insertions, 1089 deletions
diff --git a/spec/services/alert_management/alerts/update_service_spec.rb b/spec/services/alert_management/alerts/update_service_spec.rb
index f02607b8174..9bdc9970807 100644
--- a/spec/services/alert_management/alerts/update_service_spec.rb
+++ b/spec/services/alert_management/alerts/update_service_spec.rb
@@ -291,14 +291,6 @@ RSpec.describe AlertManagement::Alerts::UpdateService do
it_behaves_like 'does not sync with the incident status'
end
-
- context 'when feature flag is disabled' do
- before do
- stub_feature_flags(incident_escalations: false)
- end
-
- it_behaves_like 'does not sync with the incident status'
- end
end
end
end
diff --git a/spec/services/bulk_create_integration_service_spec.rb b/spec/services/bulk_create_integration_service_spec.rb
index 68c5af33fd8..22bb1736f9f 100644
--- a/spec/services/bulk_create_integration_service_spec.rb
+++ b/spec/services/bulk_create_integration_service_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe BulkCreateIntegrationService do
- include JiraServiceHelper
+ include JiraIntegrationHelpers
before_all do
stub_jira_integration_test
@@ -21,7 +21,7 @@ RSpec.describe BulkCreateIntegrationService do
]
end
- shared_examples 'creates integration from batch ids' do
+ shared_examples 'creates integration successfully' do
def attributes(record)
record.reload.attributes.except(*excluded_attributes)
end
@@ -41,15 +41,31 @@ RSpec.describe BulkCreateIntegrationService do
expect(attributes(created_integration.data_fields))
.to eq attributes(integration.data_fields)
end
+
+ it 'sets created_at and updated_at timestamps', :freeze_time do
+ described_class.new(integration, batch, association).execute
+
+ expect(created_integration.data_fields.reload).to have_attributes(
+ created_at: eq(Time.current),
+ updated_at: eq(Time.current)
+ )
+ end
end
- end
- shared_examples 'updates inherit_from_id' do
it 'updates inherit_from_id attributes' do
described_class.new(integration, batch, association).execute
expect(created_integration.reload.inherit_from_id).to eq(inherit_from_id)
end
+
+ it 'sets created_at and updated_at timestamps', :freeze_time do
+ described_class.new(integration, batch, association).execute
+
+ expect(created_integration.reload).to have_attributes(
+ created_at: eq(Time.current),
+ updated_at: eq(Time.current)
+ )
+ end
end
context 'passing an instance-level integration' do
@@ -62,8 +78,7 @@ RSpec.describe BulkCreateIntegrationService do
let(:batch) { Project.where(id: project.id) }
let(:association) { 'project' }
- it_behaves_like 'creates integration from batch ids'
- it_behaves_like 'updates inherit_from_id'
+ it_behaves_like 'creates integration successfully'
end
context 'with a group association' do
@@ -72,8 +87,7 @@ RSpec.describe BulkCreateIntegrationService do
let(:batch) { Group.where(id: group.id) }
let(:association) { 'group' }
- it_behaves_like 'creates integration from batch ids'
- it_behaves_like 'updates inherit_from_id'
+ it_behaves_like 'creates integration successfully'
end
end
@@ -88,15 +102,13 @@ RSpec.describe BulkCreateIntegrationService do
let(:association) { 'project' }
let(:inherit_from_id) { integration.id }
- it_behaves_like 'creates integration from batch ids'
- it_behaves_like 'updates inherit_from_id'
+ it_behaves_like 'creates integration successfully'
context 'with different foreign key of data_fields' do
let(:integration) { create(:zentao_integration, :group, group: group) }
let(:created_integration) { project.zentao_integration }
- it_behaves_like 'creates integration from batch ids'
- it_behaves_like 'updates inherit_from_id'
+ it_behaves_like 'creates integration successfully'
end
end
@@ -108,14 +120,12 @@ RSpec.describe BulkCreateIntegrationService do
let(:association) { 'group' }
let(:inherit_from_id) { instance_integration.id }
- it_behaves_like 'creates integration from batch ids'
- it_behaves_like 'updates inherit_from_id'
+ it_behaves_like 'creates integration successfully'
context 'with different foreign key of data_fields' do
let(:integration) { create(:zentao_integration, :group, group: group, inherit_from_id: instance_integration.id) }
- it_behaves_like 'creates integration from batch ids'
- it_behaves_like 'updates inherit_from_id'
+ it_behaves_like 'creates integration successfully'
end
end
end
diff --git a/spec/services/bulk_imports/create_pipeline_trackers_service_spec.rb b/spec/services/bulk_imports/create_pipeline_trackers_service_spec.rb
new file mode 100644
index 00000000000..d7b00ba04ab
--- /dev/null
+++ b/spec/services/bulk_imports/create_pipeline_trackers_service_spec.rb
@@ -0,0 +1,168 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::CreatePipelineTrackersService do
+ describe '#execute!' do
+ context 'when entity is group' do
+ it 'creates trackers for group entity' do
+ bulk_import = create(:bulk_import)
+ entity = create(:bulk_import_entity, :group_entity, bulk_import: bulk_import)
+
+ described_class.new(entity).execute!
+
+ expect(entity.trackers.to_a).to include(
+ have_attributes(
+ stage: 0, status_name: :created, relation: BulkImports::Groups::Pipelines::GroupPipeline.to_s
+ ),
+ have_attributes(
+ stage: 1, status_name: :created, relation: BulkImports::Groups::Pipelines::GroupAttributesPipeline.to_s
+ )
+ )
+ end
+ end
+
+ context 'when entity is project' do
+ it 'creates trackers for project entity' do
+ bulk_import = create(:bulk_import)
+ entity = create(:bulk_import_entity, :project_entity, bulk_import: bulk_import)
+
+ described_class.new(entity).execute!
+
+ expect(entity.trackers.to_a).to include(
+ have_attributes(
+ stage: 0, status_name: :created, relation: BulkImports::Projects::Pipelines::ProjectPipeline.to_s
+ ),
+ have_attributes(
+ stage: 1, status_name: :created, relation: BulkImports::Projects::Pipelines::RepositoryPipeline.to_s
+ )
+ )
+ end
+ end
+
+ context 'when tracker configuration has a minimum version defined' do
+ before do
+ allow_next_instance_of(BulkImports::Groups::Stage) do |stage|
+ allow(stage).to receive(:config).and_return(
+ {
+ pipeline1: { pipeline: 'PipelineClass1', stage: 0 },
+ pipeline2: { pipeline: 'PipelineClass2', stage: 1, minimum_source_version: '14.10.0' },
+ pipeline3: { pipeline: 'PipelineClass3', stage: 1, minimum_source_version: '15.0.0' },
+ pipeline5: { pipeline: 'PipelineClass4', stage: 1, minimum_source_version: '15.1.0' },
+ pipeline6: { pipeline: 'PipelineClass5', stage: 1, minimum_source_version: '16.0.0' }
+ }
+ )
+ end
+ end
+
+ context 'when the source instance version is older than the tracker mininum version' do
+ let_it_be(:bulk_import) { create(:bulk_import, source_version: '15.0.0') }
+ let_it_be(:entity) { create(:bulk_import_entity, :group_entity, bulk_import: bulk_import) }
+
+ it 'creates trackers as skipped if version requirement does not meet' do
+ described_class.new(entity).execute!
+
+ expect(entity.trackers.collect { |tracker| [tracker.status_name, tracker.relation] }).to contain_exactly(
+ [:created, 'PipelineClass1'],
+ [:created, 'PipelineClass2'],
+ [:created, 'PipelineClass3'],
+ [:skipped, 'PipelineClass4'],
+ [:skipped, 'PipelineClass5']
+ )
+ end
+
+ it 'logs an info message for the skipped pipelines' do
+ expect_next_instance_of(Gitlab::Import::Logger) do |logger|
+ expect(logger).to receive(:info).with({
+ message: 'Pipeline skipped as source instance version not compatible with pipeline',
+ entity_id: entity.id,
+ pipeline_name: 'PipelineClass4',
+ minimum_source_version: '15.1.0',
+ maximum_source_version: nil,
+ source_version: '15.0.0'
+ })
+
+ expect(logger).to receive(:info).with({
+ message: 'Pipeline skipped as source instance version not compatible with pipeline',
+ entity_id: entity.id,
+ pipeline_name: 'PipelineClass5',
+ minimum_source_version: '16.0.0',
+ maximum_source_version: nil,
+ source_version: '15.0.0'
+ })
+ end
+
+ described_class.new(entity).execute!
+ end
+ end
+
+ context 'when the source instance version is undefined' do
+ it 'creates trackers as created' do
+ bulk_import = create(:bulk_import, source_version: nil)
+ entity = create(:bulk_import_entity, :group_entity, bulk_import: bulk_import)
+
+ described_class.new(entity).execute!
+
+ expect(entity.trackers.collect { |tracker| [tracker.status_name, tracker.relation] }).to contain_exactly(
+ [:created, 'PipelineClass1'],
+ [:created, 'PipelineClass2'],
+ [:created, 'PipelineClass3'],
+ [:created, 'PipelineClass4'],
+ [:created, 'PipelineClass5']
+ )
+ end
+ end
+ end
+
+ context 'when tracker configuration has a maximum version defined' do
+ before do
+ allow_next_instance_of(BulkImports::Groups::Stage) do |stage|
+ allow(stage).to receive(:config).and_return(
+ {
+ pipeline1: { pipeline: 'PipelineClass1', stage: 0 },
+ pipeline2: { pipeline: 'PipelineClass2', stage: 1, maximum_source_version: '14.10.0' },
+ pipeline3: { pipeline: 'PipelineClass3', stage: 1, maximum_source_version: '15.0.0' },
+ pipeline5: { pipeline: 'PipelineClass4', stage: 1, maximum_source_version: '15.1.0' },
+ pipeline6: { pipeline: 'PipelineClass5', stage: 1, maximum_source_version: '16.0.0' }
+ }
+ )
+ end
+ end
+
+ context 'when the source instance version is older than the tracker maximum version' do
+ it 'creates trackers as skipped if version requirement does not meet' do
+ bulk_import = create(:bulk_import, source_version: '15.0.0')
+ entity = create(:bulk_import_entity, :group_entity, bulk_import: bulk_import)
+
+ described_class.new(entity).execute!
+
+ expect(entity.trackers.collect { |tracker| [tracker.status_name, tracker.relation] }).to contain_exactly(
+ [:created, 'PipelineClass1'],
+ [:skipped, 'PipelineClass2'],
+ [:created, 'PipelineClass3'],
+ [:created, 'PipelineClass4'],
+ [:created, 'PipelineClass5']
+ )
+ end
+ end
+
+ context 'when the source instance version is a patch version' do
+ it 'creates trackers with the same status as the non-patch source version' do
+ bulk_import_1 = create(:bulk_import, source_version: '15.0.1')
+ entity_1 = create(:bulk_import_entity, :group_entity, bulk_import: bulk_import_1)
+
+ bulk_import_2 = create(:bulk_import, source_version: '15.0.0')
+ entity_2 = create(:bulk_import_entity, :group_entity, bulk_import: bulk_import_2)
+
+ described_class.new(entity_1).execute!
+ described_class.new(entity_2).execute!
+
+ trackers_1 = entity_1.trackers.collect { |tracker| [tracker.status_name, tracker.relation] }
+ trackers_2 = entity_2.trackers.collect { |tracker| [tracker.status_name, tracker.relation] }
+
+ expect(trackers_1).to eq(trackers_2)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/bulk_imports/file_export_service_spec.rb b/spec/services/bulk_imports/file_export_service_spec.rb
index 94efceff6c6..453fc1d0c0d 100644
--- a/spec/services/bulk_imports/file_export_service_spec.rb
+++ b/spec/services/bulk_imports/file_export_service_spec.rb
@@ -4,39 +4,34 @@ require 'spec_helper'
RSpec.describe BulkImports::FileExportService do
let_it_be(:project) { create(:project) }
- let_it_be(:export_path) { Dir.mktmpdir }
- let_it_be(:relation) { BulkImports::FileTransfer::BaseConfig::UPLOADS_RELATION }
-
- subject(:service) { described_class.new(project, export_path, relation) }
describe '#execute' do
- it 'executes export service and archives exported data' do
- expect_next_instance_of(BulkImports::UploadsExportService) do |service|
- expect(service).to receive(:execute)
- end
+ it 'executes export service and archives exported data for each file relation' do
+ relations = {
+ 'uploads' => BulkImports::UploadsExportService,
+ 'lfs_objects' => BulkImports::LfsObjectsExportService,
+ 'repository' => BulkImports::RepositoryBundleExportService,
+ 'design' => BulkImports::RepositoryBundleExportService
+ }
- expect(subject).to receive(:tar_cf).with(archive: File.join(export_path, 'uploads.tar'), dir: export_path)
+ relations.each do |relation, klass|
+ Dir.mktmpdir do |export_path|
+ service = described_class.new(project, export_path, relation)
- subject.execute
- end
+ expect_next_instance_of(klass) do |service|
+ expect(service).to receive(:execute)
+ end
- context 'when relation is lfs objects' do
- let_it_be(:relation) { BulkImports::FileTransfer::ProjectConfig::LFS_OBJECTS_RELATION }
+ expect(service).to receive(:tar_cf).with(archive: File.join(export_path, "#{relation}.tar"), dir: export_path)
- it 'executes lfs objects export service' do
- expect_next_instance_of(BulkImports::LfsObjectsExportService) do |service|
- expect(service).to receive(:execute)
+ service.execute
end
-
- expect(subject).to receive(:tar_cf).with(archive: File.join(export_path, 'lfs_objects.tar'), dir: export_path)
-
- subject.execute
end
end
context 'when unsupported relation is passed' do
it 'raises an error' do
- service = described_class.new(project, export_path, 'unsupported')
+ service = described_class.new(project, nil, 'unsupported')
expect { service.execute }.to raise_error(BulkImports::Error, 'Unsupported relation export type')
end
@@ -45,7 +40,9 @@ RSpec.describe BulkImports::FileExportService do
describe '#exported_filename' do
it 'returns filename of the exported file' do
- expect(subject.exported_filename).to eq('uploads.tar')
+ service = described_class.new(project, nil, 'uploads')
+
+ expect(service.exported_filename).to eq('uploads.tar')
end
end
end
diff --git a/spec/services/bulk_imports/lfs_objects_export_service_spec.rb b/spec/services/bulk_imports/lfs_objects_export_service_spec.rb
index 5ae54ed309b..894789c7941 100644
--- a/spec/services/bulk_imports/lfs_objects_export_service_spec.rb
+++ b/spec/services/bulk_imports/lfs_objects_export_service_spec.rb
@@ -53,6 +53,18 @@ RSpec.describe BulkImports::LfsObjectsExportService do
)
end
+ context 'when lfs object has file on disk missing' do
+ it 'does not attempt to copy non-existent file' do
+ FileUtils.rm(lfs_object.file.path)
+
+ expect(service).not_to receive(:copy_files)
+
+ service.execute
+
+ expect(File).not_to exist(File.join(export_path, lfs_object.oid))
+ end
+ end
+
context 'when lfs object is remotely stored' do
let(:lfs_object) { create(:lfs_object, :object_storage) }
diff --git a/spec/services/bulk_imports/repository_bundle_export_service_spec.rb b/spec/services/bulk_imports/repository_bundle_export_service_spec.rb
new file mode 100644
index 00000000000..a7d98a7474a
--- /dev/null
+++ b/spec/services/bulk_imports/repository_bundle_export_service_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::RepositoryBundleExportService do
+ let(:project) { create(:project) }
+ let(:export_path) { Dir.mktmpdir }
+
+ subject(:service) { described_class.new(repository, export_path, export_filename) }
+
+ after do
+ FileUtils.remove_entry(export_path) if Dir.exist?(export_path)
+ end
+
+ describe '#execute' do
+ shared_examples 'repository export' do
+ context 'when repository exists' do
+ it 'bundles repository to disk' do
+ allow(repository).to receive(:exists?).and_return(true)
+ expect(repository).to receive(:bundle_to_disk).with(File.join(export_path, "#{export_filename}.bundle"))
+
+ service.execute
+ end
+ end
+
+ context 'when repository does not exist' do
+ it 'does not bundle repository to disk' do
+ allow(repository).to receive(:exists?).and_return(false)
+ expect(repository).not_to receive(:bundle_to_disk)
+
+ service.execute
+ end
+ end
+ end
+
+ include_examples 'repository export' do
+ let(:repository) { project.repository }
+ let(:export_filename) { 'repository' }
+ end
+
+ include_examples 'repository export' do
+ let(:repository) { project.design_repository }
+ let(:export_filename) { 'design' }
+ end
+ end
+end
diff --git a/spec/services/bulk_update_integration_service_spec.rb b/spec/services/bulk_update_integration_service_spec.rb
index dcc8d2df36d..e3e38aacaa2 100644
--- a/spec/services/bulk_update_integration_service_spec.rb
+++ b/spec/services/bulk_update_integration_service_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe BulkUpdateIntegrationService do
- include JiraServiceHelper
+ include JiraIntegrationHelpers
before_all do
stub_jira_integration_test
@@ -55,6 +55,20 @@ RSpec.describe BulkUpdateIntegrationService do
.not_to eq(subgroup_integration.attributes.except(*excluded_attributes))
end
+ it 'does not change the created_at timestamp' do
+ subgroup_integration.update_column(:created_at, Time.utc('2022-01-01'))
+
+ expect do
+ described_class.new(subgroup_integration, batch).execute
+ end.not_to change { integration.reload.created_at }
+ end
+
+ it 'sets the updated_at timestamp to the current time', time_travel_to: Time.utc('2022-01-01') do
+ expect do
+ described_class.new(subgroup_integration, batch).execute
+ end.to change { integration.reload.updated_at }.to(Time.current)
+ end
+
context 'with integration with data fields' do
let(:excluded_attributes) do
%w[id service_id created_at updated_at encrypted_properties encrypted_properties_iv]
@@ -69,6 +83,20 @@ RSpec.describe BulkUpdateIntegrationService do
expect(integration.data_fields.attributes.except(*excluded_attributes))
.not_to eq(excluded_integration.data_fields.attributes.except(*excluded_attributes))
end
+
+ it 'does not change the created_at timestamp' do
+ subgroup_integration.data_fields.update_column(:created_at, Time.utc('2022-01-02'))
+
+ expect do
+ described_class.new(subgroup_integration, batch).execute
+ end.not_to change { integration.data_fields.reload.created_at }
+ end
+
+ it 'sets the updated_at timestamp to the current time', time_travel_to: Time.utc('2022-01-01') do
+ expect do
+ described_class.new(subgroup_integration, batch).execute
+ end.to change { integration.data_fields.reload.updated_at }.to(Time.current)
+ end
end
end
diff --git a/spec/services/ci/abort_pipelines_service_spec.rb b/spec/services/ci/abort_pipelines_service_spec.rb
index db25faff70f..9f9519d6829 100644
--- a/spec/services/ci/abort_pipelines_service_spec.rb
+++ b/spec/services/ci/abort_pipelines_service_spec.rb
@@ -94,28 +94,5 @@ RSpec.describe Ci::AbortPipelinesService do
end
end
end
-
- context 'with user pipelines' do
- def abort_user_pipelines
- described_class.new.execute(user.pipelines, :user_blocked)
- end
-
- it 'fails all running pipelines and related jobs' do
- expect(abort_user_pipelines).to be_success
-
- expect_correct_cancellations
-
- expect(other_users_pipeline.status).not_to eq('failed')
- end
-
- it 'avoids N+1 queries' do
- control_count = ActiveRecord::QueryRecorder.new { abort_user_pipelines }.count
-
- pipelines = create_list(:ci_pipeline, 5, :running, project: project, user: user)
- create_list(:ci_build, 5, :running, pipeline: pipelines.first)
-
- expect { abort_user_pipelines }.not_to exceed_query_limit(control_count)
- end
- end
end
end
diff --git a/spec/services/ci/after_requeue_job_service_spec.rb b/spec/services/ci/after_requeue_job_service_spec.rb
index c9bd44f78e2..fb67ee18fb2 100644
--- a/spec/services/ci/after_requeue_job_service_spec.rb
+++ b/spec/services/ci/after_requeue_job_service_spec.rb
@@ -26,6 +26,11 @@ RSpec.describe Ci::AfterRequeueJobService, :sidekiq_inline do
script: exit 0
needs: [a1]
+ a3:
+ stage: a
+ script: exit 0
+ needs: [a2]
+
b1:
stage: b
script: exit 0
@@ -59,6 +64,7 @@ RSpec.describe Ci::AfterRequeueJobService, :sidekiq_inline do
check_jobs_statuses(
a1: 'pending',
a2: 'created',
+ a3: 'created',
b1: 'pending',
b2: 'created',
c1: 'created',
@@ -69,6 +75,7 @@ RSpec.describe Ci::AfterRequeueJobService, :sidekiq_inline do
check_jobs_statuses(
a1: 'pending',
a2: 'created',
+ a3: 'created',
b1: 'success',
b2: 'created',
c1: 'created',
@@ -79,6 +86,7 @@ RSpec.describe Ci::AfterRequeueJobService, :sidekiq_inline do
check_jobs_statuses(
a1: 'failed',
a2: 'skipped',
+ a3: 'skipped',
b1: 'success',
b2: 'skipped',
c1: 'skipped',
@@ -90,6 +98,7 @@ RSpec.describe Ci::AfterRequeueJobService, :sidekiq_inline do
check_jobs_statuses(
a1: 'pending',
a2: 'skipped',
+ a3: 'skipped',
b1: 'success',
b2: 'skipped',
c1: 'skipped',
@@ -103,12 +112,42 @@ RSpec.describe Ci::AfterRequeueJobService, :sidekiq_inline do
check_jobs_statuses(
a1: 'pending',
a2: 'created',
+ a3: 'skipped',
b1: 'success',
b2: 'created',
c1: 'created',
c2: 'created'
)
end
+
+ context 'when executed by a different user than the original owner' do
+ let(:retryer) { create(:user).tap { |u| project.add_maintainer(u) } }
+ let(:service) { described_class.new(project, retryer) }
+
+ it 'reassigns jobs with updated statuses to the retryer' do
+ expect(jobs_name_status_owner_needs).to contain_exactly(
+ { 'name' => 'a1', 'status' => 'pending', 'user_id' => user.id, 'needs' => [] },
+ { 'name' => 'a2', 'status' => 'skipped', 'user_id' => user.id, 'needs' => ['a1'] },
+ { 'name' => 'a3', 'status' => 'skipped', 'user_id' => user.id, 'needs' => ['a2'] },
+ { 'name' => 'b1', 'status' => 'success', 'user_id' => user.id, 'needs' => [] },
+ { 'name' => 'b2', 'status' => 'skipped', 'user_id' => user.id, 'needs' => ['a2'] },
+ { 'name' => 'c1', 'status' => 'skipped', 'user_id' => user.id, 'needs' => ['b2'] },
+ { 'name' => 'c2', 'status' => 'skipped', 'user_id' => user.id, 'needs' => [] }
+ )
+
+ execute_after_requeue_service(a1)
+
+ expect(jobs_name_status_owner_needs).to contain_exactly(
+ { 'name' => 'a1', 'status' => 'pending', 'user_id' => user.id, 'needs' => [] },
+ { 'name' => 'a2', 'status' => 'created', 'user_id' => retryer.id, 'needs' => ['a1'] },
+ { 'name' => 'a3', 'status' => 'skipped', 'user_id' => user.id, 'needs' => ['a2'] },
+ { 'name' => 'b1', 'status' => 'success', 'user_id' => user.id, 'needs' => [] },
+ { 'name' => 'b2', 'status' => 'created', 'user_id' => retryer.id, 'needs' => ['a2'] },
+ { 'name' => 'c1', 'status' => 'created', 'user_id' => retryer.id, 'needs' => ['b2'] },
+ { 'name' => 'c2', 'status' => 'created', 'user_id' => retryer.id, 'needs' => [] }
+ )
+ end
+ end
end
context 'stage-dag mixed pipeline with some same-stage needs' do
@@ -212,6 +251,12 @@ RSpec.describe Ci::AfterRequeueJobService, :sidekiq_inline do
pipeline.processables.latest
end
+ def jobs_name_status_owner_needs
+ processables.reload.map do |job|
+ job.attributes.slice('name', 'status', 'user_id').merge('needs' => job.needs.map(&:name))
+ end
+ end
+
def execute_after_requeue_service(processable)
service.execute(processable)
end
diff --git a/spec/services/ci/create_pipeline_service/rate_limit_spec.rb b/spec/services/ci/create_pipeline_service/rate_limit_spec.rb
index caea165cc6c..0000296230f 100644
--- a/spec/services/ci/create_pipeline_service/rate_limit_spec.rb
+++ b/spec/services/ci/create_pipeline_service/rate_limit_spec.rb
@@ -10,10 +10,8 @@ RSpec.describe Ci::CreatePipelineService, :freeze_time, :clean_gitlab_redis_rate
before do
stub_ci_pipeline_yaml_file(gitlab_ci_yaml)
- stub_feature_flags(ci_throttle_pipelines_creation_dry_run: false)
-
- allow(Gitlab::ApplicationRateLimiter).to receive(:rate_limits)
- .and_return(pipelines_create: { threshold: 1, interval: 1.minute })
+ stub_application_setting(pipeline_limit_per_project_user_sha: 1)
+ stub_feature_flags(ci_enforce_throttle_pipelines_creation_override: false)
end
context 'when user is under the limit' do
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index c39a76ad2fc..aac059f2104 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -12,10 +12,6 @@ RSpec.describe Ci::CreatePipelineService do
before do
stub_ci_pipeline_to_return_yaml_file
-
- # Disable rate limiting for pipeline creation
- allow(Gitlab::ApplicationRateLimiter).to receive(:rate_limits)
- .and_return(pipelines_create: { threshold: 0, interval: 1.minute })
end
describe '#execute' do
@@ -1541,11 +1537,12 @@ RSpec.describe Ci::CreatePipelineService do
expect(pipeline.target_sha).to be_nil
end
- it 'schedules update for the head pipeline of the merge request', :sidekiq_inline do
- expect(UpdateHeadPipelineForMergeRequestWorker)
- .to receive(:perform_async).with(merge_request.id)
+ it 'schedules update for the head pipeline of the merge request' do
+ allow(MergeRequests::UpdateHeadPipelineWorker).to receive(:perform_async)
pipeline
+
+ expect(MergeRequests::UpdateHeadPipelineWorker).to have_received(:perform_async).with('Ci::PipelineCreatedEvent', { 'pipeline_id' => pipeline.id })
end
it 'schedules a namespace onboarding create action worker' do
diff --git a/spec/services/ci/job_artifacts/destroy_all_expired_service_spec.rb b/spec/services/ci/job_artifacts/destroy_all_expired_service_spec.rb
index 1c6963e4a31..4f7663d7996 100644
--- a/spec/services/ci/job_artifacts/destroy_all_expired_service_spec.rb
+++ b/spec/services/ci/job_artifacts/destroy_all_expired_service_spec.rb
@@ -99,6 +99,16 @@ RSpec.describe Ci::JobArtifacts::DestroyAllExpiredService, :clean_gitlab_redis_s
expect { subject }.not_to change { artifact.file.exists? }
end
end
+
+ context 'when the project in which the arfifact belongs to is undergoing stats refresh' do
+ before do
+ create(:project_build_artifacts_size_refresh, :pending, project: artifact.project)
+ end
+
+ it 'does not destroy job artifact' do
+ expect { subject }.not_to change { Ci::JobArtifact.count }
+ end
+ end
end
context 'when artifact is locked' do
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 5e77041a632..3a04a3af03e 100644
--- a/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb
+++ b/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb
@@ -4,7 +4,14 @@ require 'spec_helper'
RSpec.describe Ci::JobArtifacts::DestroyBatchService do
let(:artifacts) { Ci::JobArtifact.where(id: [artifact_with_file.id, artifact_without_file.id, trace_artifact.id]) }
- let(:service) { described_class.new(artifacts, pick_up_at: Time.current) }
+ let(:skip_projects_on_refresh) { false }
+ let(:service) do
+ described_class.new(
+ artifacts,
+ pick_up_at: Time.current,
+ skip_projects_on_refresh: skip_projects_on_refresh
+ )
+ end
let_it_be(:artifact_with_file, refind: true) do
create(:ci_job_artifact, :zip)
@@ -52,6 +59,128 @@ RSpec.describe Ci::JobArtifacts::DestroyBatchService do
.and not_change { Ci::JobArtifact.exists?(trace_artifact.id) }
end
+ context 'when artifact belongs to a project that is undergoing stats refresh' do
+ let!(:artifact_under_refresh_1) do
+ create(:ci_job_artifact, :zip)
+ end
+
+ let!(:artifact_under_refresh_2) do
+ create(:ci_job_artifact, :zip)
+ end
+
+ let!(:artifact_under_refresh_3) do
+ create(:ci_job_artifact, :zip, project: artifact_under_refresh_2.project)
+ end
+
+ let(:artifacts) do
+ Ci::JobArtifact.where(id: [artifact_with_file.id, artifact_under_refresh_1.id, artifact_under_refresh_2.id,
+ artifact_under_refresh_3.id])
+ end
+
+ before do
+ create(:project_build_artifacts_size_refresh, :created, project: artifact_with_file.project)
+ create(:project_build_artifacts_size_refresh, :pending, project: artifact_under_refresh_1.project)
+ create(:project_build_artifacts_size_refresh, :running, project: artifact_under_refresh_2.project)
+ end
+
+ shared_examples 'avoiding N+1 queries' do
+ let!(:control_artifact_on_refresh) do
+ create(:ci_job_artifact, :zip)
+ end
+
+ let!(:control_artifact_non_refresh) do
+ create(:ci_job_artifact, :zip)
+ end
+
+ let!(:other_artifact_on_refresh) do
+ create(:ci_job_artifact, :zip)
+ end
+
+ let!(:other_artifact_on_refresh_2) do
+ create(:ci_job_artifact, :zip)
+ end
+
+ let!(:other_artifact_non_refresh) do
+ create(:ci_job_artifact, :zip)
+ end
+
+ let!(:control_artifacts) do
+ Ci::JobArtifact.where(
+ id: [
+ control_artifact_on_refresh.id,
+ control_artifact_non_refresh.id
+ ]
+ )
+ end
+
+ let!(:artifacts) do
+ Ci::JobArtifact.where(
+ id: [
+ other_artifact_on_refresh.id,
+ other_artifact_on_refresh_2.id,
+ other_artifact_non_refresh.id
+ ]
+ )
+ end
+
+ let(:control_service) do
+ described_class.new(
+ control_artifacts,
+ pick_up_at: Time.current,
+ skip_projects_on_refresh: skip_projects_on_refresh
+ )
+ end
+
+ before do
+ create(:project_build_artifacts_size_refresh, :pending, project: control_artifact_on_refresh.project)
+ create(:project_build_artifacts_size_refresh, :pending, project: other_artifact_on_refresh.project)
+ create(:project_build_artifacts_size_refresh, :pending, project: other_artifact_on_refresh_2.project)
+ end
+
+ it 'does not make multiple queries when fetching multiple project refresh records' do
+ control = ActiveRecord::QueryRecorder.new { control_service.execute }
+
+ expect { subject }.not_to exceed_query_limit(control)
+ end
+ end
+
+ context 'and skip_projects_on_refresh is set to false (default)' do
+ it 'logs the projects undergoing refresh and continues with the delete', :aggregate_failures do
+ expect(Gitlab::ProjectStatsRefreshConflictsLogger).to receive(:warn_artifact_deletion_during_stats_refresh).with(
+ method: 'Ci::JobArtifacts::DestroyBatchService#execute',
+ project_id: artifact_under_refresh_1.project.id
+ ).once
+
+ expect(Gitlab::ProjectStatsRefreshConflictsLogger).to receive(:warn_artifact_deletion_during_stats_refresh).with(
+ method: 'Ci::JobArtifacts::DestroyBatchService#execute',
+ project_id: artifact_under_refresh_2.project.id
+ ).once
+
+ expect { subject }.to change { Ci::JobArtifact.count }.by(-4)
+ end
+
+ it_behaves_like 'avoiding N+1 queries'
+ end
+
+ context 'and skip_projects_on_refresh is set to true' do
+ let(:skip_projects_on_refresh) { true }
+
+ it 'logs the projects undergoing refresh and excludes the artifacts from deletion', :aggregate_failures do
+ expect(Gitlab::ProjectStatsRefreshConflictsLogger).to receive(:warn_skipped_artifact_deletion_during_stats_refresh).with(
+ method: 'Ci::JobArtifacts::DestroyBatchService#execute',
+ project_ids: match_array([artifact_under_refresh_1.project.id, artifact_under_refresh_2.project.id])
+ )
+
+ expect { subject }.to change { Ci::JobArtifact.count }.by(-1)
+ expect(Ci::JobArtifact.where(id: artifact_under_refresh_1.id)).to exist
+ expect(Ci::JobArtifact.where(id: artifact_under_refresh_2.id)).to exist
+ expect(Ci::JobArtifact.where(id: artifact_under_refresh_3.id)).to exist
+ end
+
+ it_behaves_like 'avoiding N+1 queries'
+ end
+ end
+
context 'ProjectStatistics' do
it 'resets project statistics' do
expect(ProjectStatistics).to receive(:increment_statistic).once
diff --git a/spec/services/ci/pipeline_artifacts/coverage_report_service_spec.rb b/spec/services/ci/pipeline_artifacts/coverage_report_service_spec.rb
index 98b01e2b303..403afde5da3 100644
--- a/spec/services/ci/pipeline_artifacts/coverage_report_service_spec.rb
+++ b/spec/services/ci/pipeline_artifacts/coverage_report_service_spec.rb
@@ -4,17 +4,14 @@ require 'spec_helper'
RSpec.describe ::Ci::PipelineArtifacts::CoverageReportService do
describe '#execute' do
- subject { described_class.new.execute(pipeline) }
+ let_it_be(:project) { create(:project, :repository) }
- context 'when pipeline has coverage reports' do
- let(:project) { create(:project, :repository) }
- let(:pipeline) { create(:ci_pipeline, :with_coverage_reports, project: project) }
+ subject { described_class.new(pipeline).execute }
+ shared_examples 'creating a pipeline coverage report' do
context 'when pipeline is finished' do
it 'creates a pipeline artifact' do
- subject
-
- expect(Ci::PipelineArtifact.count).to eq(1)
+ expect { subject }.to change { Ci::PipelineArtifact.count }.from(0).to(1)
end
it 'persists the default file name' do
@@ -37,21 +34,32 @@ RSpec.describe ::Ci::PipelineArtifacts::CoverageReportService do
end
context 'when pipeline artifact has already been created' do
- it 'do not raise an error and do not persist the same artifact twice' do
- expect { 2.times { described_class.new.execute(pipeline) } }.not_to raise_error
+ it 'does not raise an error and does not persist the same artifact twice' do
+ expect { 2.times { described_class.new(pipeline).execute } }.not_to raise_error
expect(Ci::PipelineArtifact.count).to eq(1)
end
end
end
+ context 'when pipeline has coverage report' do
+ let!(:pipeline) { create(:ci_pipeline, :with_coverage_reports, project: project) }
+
+ it_behaves_like 'creating a pipeline coverage report'
+ end
+
+ context 'when pipeline has coverage report from child pipeline' do
+ let!(:pipeline) { create(:ci_pipeline, :success, project: project) }
+ let!(:child_pipeline) { create(:ci_pipeline, :with_coverage_reports, project: project, child_of: pipeline) }
+
+ it_behaves_like 'creating a pipeline coverage report'
+ end
+
context 'when pipeline is running and coverage report does not exist' do
let(:pipeline) { create(:ci_pipeline, :running) }
it 'does not persist data' do
- subject
-
- expect(Ci::PipelineArtifact.count).to eq(0)
+ expect { subject }.not_to change { Ci::PipelineArtifact.count }
end
end
end
diff --git a/spec/services/ci/process_sync_events_service_spec.rb b/spec/services/ci/process_sync_events_service_spec.rb
index 6b9717fe57d..241ac4995ff 100644
--- a/spec/services/ci/process_sync_events_service_spec.rb
+++ b/spec/services/ci/process_sync_events_service_spec.rb
@@ -137,10 +137,9 @@ RSpec.describe Ci::ProcessSyncEventsService do
end
end
- context 'when the FFs sync_traversal_ids, use_traversal_ids and use_traversal_ids_for_ancestors are disabled' do
+ context 'when the FFs use_traversal_ids and use_traversal_ids_for_ancestors are disabled' do
before do
- stub_feature_flags(sync_traversal_ids: false,
- use_traversal_ids: false,
+ stub_feature_flags(use_traversal_ids: false,
use_traversal_ids_for_ancestors: false)
end
diff --git a/spec/services/clusters/applications/schedule_update_service_spec.rb b/spec/services/clusters/applications/schedule_update_service_spec.rb
deleted file mode 100644
index 2cbcb861938..00000000000
--- a/spec/services/clusters/applications/schedule_update_service_spec.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Clusters::Applications::ScheduleUpdateService do
- describe '#execute' do
- let(:project) { create(:project) }
-
- around do |example|
- 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
- application = create(:clusters_applications_prometheus, :installed, last_update_started_at: Time.current + 5.minutes)
- service = described_class.new(application, project)
-
- expect(::ClusterUpdateAppWorker).to receive(:perform_in).with(described_class::BACKOFF_DELAY, application.name, application.id, project.id, Time.current).once
-
- service.execute
- end
- end
-
- context 'when the application has not been recently updated' do
- it 'schedules worker' do
- application = create(:clusters_applications_prometheus, :installed)
- service = described_class.new(application, project)
-
- expect(::ClusterUpdateAppWorker).to receive(:perform_async).with(application.name, application.id, project.id, Time.current).once
-
- service.execute
- end
- end
- end
- end
-end
diff --git a/spec/services/deployments/create_service_spec.rb b/spec/services/deployments/create_service_spec.rb
index 0f2a6ce32e1..f6f4c68a6f1 100644
--- a/spec/services/deployments/create_service_spec.rb
+++ b/spec/services/deployments/create_service_spec.rb
@@ -21,11 +21,34 @@ RSpec.describe Deployments::CreateService do
expect(Deployments::UpdateEnvironmentWorker).to receive(:perform_async)
expect(Deployments::LinkMergeRequestWorker).to receive(:perform_async)
- expect(Deployments::HooksWorker).to receive(:perform_async)
+ expect_next_instance_of(Deployment) do |deployment|
+ expect(deployment).to receive(:execute_hooks)
+ end
expect(service.execute).to be_persisted
end
+ context 'when `deployment_hooks_skip_worker` flag is disabled' do
+ before do
+ stub_feature_flags(deployment_hooks_skip_worker: false)
+ end
+
+ it 'executes Deployments::HooksWorker asynchronously' do
+ service = described_class.new(
+ environment,
+ user,
+ sha: 'b83d6e391c22777fca1ed3012fce84f633d7fed0',
+ ref: 'master',
+ tag: false,
+ status: 'success'
+ )
+
+ expect(Deployments::HooksWorker).to receive(:perform_async)
+
+ service.execute
+ end
+ end
+
it 'does not change the status if no status is given' do
service = described_class.new(
environment,
@@ -37,7 +60,9 @@ RSpec.describe Deployments::CreateService do
expect(Deployments::UpdateEnvironmentWorker).not_to receive(:perform_async)
expect(Deployments::LinkMergeRequestWorker).not_to receive(:perform_async)
- expect(Deployments::HooksWorker).not_to receive(:perform_async)
+ expect_next_instance_of(Deployment) do |deployment|
+ expect(deployment).not_to receive(:execute_hooks)
+ end
expect(service.execute).to be_persisted
end
@@ -55,11 +80,9 @@ RSpec.describe Deployments::CreateService do
it 'does not create a new deployment' do
described_class.new(environment, user, params).execute
- expect(Deployments::UpdateEnvironmentWorker).not_to receive(:perform_async)
- expect(Deployments::LinkMergeRequestWorker).not_to receive(:perform_async)
- expect(Deployments::HooksWorker).not_to receive(:perform_async)
-
- described_class.new(environment.reload, user, params).execute
+ expect do
+ described_class.new(environment.reload, user, params).execute
+ end.not_to change { Deployment.count }
end
end
end
diff --git a/spec/services/deployments/update_environment_service_spec.rb b/spec/services/deployments/update_environment_service_spec.rb
index 0859aa2c9d1..e2d7a80fde3 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::HooksWorker).to receive(:perform_async)
+ allow(deployment).to receive(:execute_hooks)
job.success! # Create/Succeed deployment
end
@@ -84,7 +84,7 @@ RSpec.describe Deployments::UpdateEnvironmentService do
context 'and environment is stopped' do
before do
- environment.stop
+ environment.stop_complete
end
it 'makes environment available' do
diff --git a/spec/services/emails/confirm_service_spec.rb b/spec/services/emails/confirm_service_spec.rb
index d3a745bc744..e8d3c0d673b 100644
--- a/spec/services/emails/confirm_service_spec.rb
+++ b/spec/services/emails/confirm_service_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Emails::ConfirmService do
- let(:user) { create(:user) }
+ let_it_be(:user) { create(:user) }
subject(:service) { described_class.new(user) }
@@ -11,7 +11,9 @@ RSpec.describe Emails::ConfirmService do
it 'enqueues a background job to send confirmation email again' do
email = user.emails.create!(email: 'new@email.com')
- expect { service.execute(email) }.to have_enqueued_job.on_queue('mailers')
+ travel_to(10.minutes.from_now) do
+ expect { service.execute(email) }.to have_enqueued_job.on_queue('mailers')
+ end
end
end
end
diff --git a/spec/services/environments/auto_stop_service_spec.rb b/spec/services/environments/auto_stop_service_spec.rb
index 8dad59cbefd..d688690c376 100644
--- a/spec/services/environments/auto_stop_service_spec.rb
+++ b/spec/services/environments/auto_stop_service_spec.rb
@@ -37,7 +37,7 @@ RSpec.describe Environments::AutoStopService, :clean_gitlab_redis_shared_state,
it 'stops environments and play stop jobs' do
expect { subject }
.to change { Environment.all.map(&:state).uniq }
- .from(['available']).to(['stopped'])
+ .from(['available']).to(['stopping'])
expect(Ci::Build.where(name: 'stop_review_app').map(&:status).uniq).to eq(['pending'])
end
diff --git a/spec/services/environments/stop_service_spec.rb b/spec/services/environments/stop_service_spec.rb
index afbc0ba70f9..3ed8a0b1da0 100644
--- a/spec/services/environments/stop_service_spec.rb
+++ b/spec/services/environments/stop_service_spec.rb
@@ -29,14 +29,27 @@ RSpec.describe Environments::StopService do
review_job.success!
end
- it 'stops the environment' do
- expect { subject }.to change { environment.reload.state }.from('available').to('stopped')
+ context 'without stop action' do
+ let!(:environment) { create(:environment, :available, project: project) }
+
+ it 'stops the environment' do
+ expect { subject }.to change { environment.reload.state }.from('available').to('stopped')
+ end
end
it 'plays the stop action' do
expect { subject }.to change { stop_review_job.reload.status }.from('manual').to('pending')
end
+ context 'force option' do
+ let(:service) { described_class.new(project, user, { force: true }) }
+
+ it 'does not play the stop action when forced' do
+ expect { subject }.to change { environment.reload.state }.from('available').to('stopped')
+ expect(stop_review_job.reload.status).to eq('manual')
+ end
+ end
+
context 'when an environment has already been stopped' do
let!(:environment) { create(:environment, :stopped, project: project) }
@@ -65,11 +78,6 @@ RSpec.describe Environments::StopService do
describe '#execute_for_branch' do
context 'when environment with review app exists' do
- before do
- create(:environment, :with_review_app, project: project,
- ref: 'feature')
- end
-
context 'when user has permission to stop environment' do
before do
project.add_developer(user)
@@ -77,25 +85,25 @@ RSpec.describe Environments::StopService do
context 'when environment is associated with removed branch' do
it 'stops environment' do
- expect_environment_stopped_on('feature')
+ expect_environment_stopping_on('feature', feature_environment)
end
end
context 'when environment is associated with different branch' do
it 'does not stop environment' do
- expect_environment_not_stopped_on('master')
+ expect_environment_not_stopped_on('master', feature_environment)
end
end
context 'when specified branch does not exist' do
it 'does not stop environment' do
- expect_environment_not_stopped_on('non/existent/branch')
+ expect_environment_not_stopped_on('non/existent/branch', feature_environment)
end
end
context 'when no branch not specified' do
it 'does not stop environment' do
- expect_environment_not_stopped_on(nil)
+ expect_environment_not_stopped_on(nil, feature_environment)
end
end
@@ -107,7 +115,7 @@ RSpec.describe Environments::StopService do
end
it 'does not stop environment' do
- expect_environment_not_stopped_on('feature')
+ expect_environment_not_stopped_on('feature', feature_environment)
end
end
end
@@ -119,7 +127,7 @@ RSpec.describe Environments::StopService do
end
it 'does not stop environment' do
- expect_environment_not_stopped_on('master')
+ expect_environment_not_stopped_on('master', feature_environment)
end
end
end
@@ -132,7 +140,7 @@ RSpec.describe Environments::StopService do
end
it 'does not stop environment' do
- expect_environment_not_stopped_on('master')
+ expect_environment_not_stopped_on('master', feature_environment)
end
end
end
@@ -148,7 +156,7 @@ RSpec.describe Environments::StopService do
end
it 'does not stop environment' do
- expect_environment_not_stopped_on('master')
+ expect_environment_not_stopped_on('master', feature_environment)
end
end
end
@@ -177,7 +185,7 @@ RSpec.describe Environments::StopService do
merge_requests_as_head_pipeline: [merge_request])
end
- let!(:review_job) { create(:ci_build, :with_deployment, :start_review_app, pipeline: pipeline, project: project) }
+ let!(:review_job) { create(:ci_build, :with_deployment, :start_review_app, :success, pipeline: pipeline, project: project) }
let!(:stop_review_job) { create(:ci_build, :with_deployment, :stop_review_app, :manual, pipeline: pipeline, project: project) }
before do
@@ -195,8 +203,7 @@ RSpec.describe Environments::StopService do
it 'stops the active environment' do
subject
-
- expect(pipeline.environments_in_self_and_descendants.first).to be_stopped
+ expect(pipeline.environments_in_self_and_descendants.first).to be_stopping
end
context 'when pipeline is a branch pipeline for merge request' do
@@ -263,13 +270,22 @@ RSpec.describe Environments::StopService do
end
end
- def expect_environment_stopped_on(branch)
+ def expect_environment_stopped_on(branch, environment)
+ expect { service.execute_for_branch(branch) }
+ .to change { environment.reload.state }.from('available').to('stopped')
+ end
+
+ def expect_environment_stopping_on(branch, environment)
expect { service.execute_for_branch(branch) }
- .to change { Environment.last.state }.from('available').to('stopped')
+ .to change { environment.reload.state }.from('available').to('stopping')
end
- def expect_environment_not_stopped_on(branch)
+ def expect_environment_not_stopped_on(branch, environment)
expect { service.execute_for_branch(branch) }
- .not_to change { Environment.last.state }
+ .not_to change { environment.reload.state }.from('available')
+ end
+
+ def feature_environment
+ create(:environment, :with_review_app, project: project, ref: 'feature')
end
end
diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb
index c22099fe410..56da85cc4a0 100644
--- a/spec/services/event_create_service_spec.rb
+++ b/spec/services/event_create_service_spec.rb
@@ -21,8 +21,10 @@ RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redi
end
shared_examples 'Snowplow event' do
+ let(:label) { nil }
+
it 'is not emitted if FF is disabled' do
- stub_feature_flags(route_hll_to_snowplow: false)
+ stub_feature_flags(feature_flag_name => false)
subject
@@ -30,15 +32,18 @@ RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redi
end
it 'is emitted' do
+ params = {
+ category: category,
+ action: action,
+ namespace: namespace,
+ user: user,
+ project: project,
+ label: label
+ }.compact
+
subject
- expect_snowplow_event(
- category: described_class.to_s,
- action: 'action_active_users_project_repo',
- namespace: project.namespace,
- user: user,
- project: project
- )
+ expect_snowplow_event(**params)
end
end
@@ -74,7 +79,7 @@ RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redi
end
end
- describe 'Merge Requests' do
+ describe 'Merge Requests', :snowplow do
describe '#open_mr' do
subject(:open_mr) { service.open_mr(merge_request, merge_request.author) }
@@ -89,6 +94,16 @@ RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redi
it_behaves_like "it records the event in the event counter" do
let(:event_action) { Gitlab::UsageDataCounters::TrackUniqueEvents::MERGE_REQUEST_ACTION }
end
+
+ it_behaves_like 'Snowplow event' do
+ let(:category) { Gitlab::UsageDataCounters::TrackUniqueEvents::MERGE_REQUEST_ACTION.to_s }
+ let(:label) { 'merge_requests_users' }
+ let(:action) { 'create' }
+ let(:namespace) { project.namespace }
+ let(:project) { merge_request.project }
+ let(:user) { merge_request.author }
+ let(:feature_flag_name) { :route_hll_to_snowplow_phase2 }
+ end
end
describe '#close_mr' do
@@ -105,6 +120,16 @@ RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redi
it_behaves_like "it records the event in the event counter" do
let(:event_action) { Gitlab::UsageDataCounters::TrackUniqueEvents::MERGE_REQUEST_ACTION }
end
+
+ it_behaves_like 'Snowplow event' do
+ let(:category) { Gitlab::UsageDataCounters::TrackUniqueEvents::MERGE_REQUEST_ACTION.to_s }
+ let(:label) { 'merge_requests_users' }
+ let(:action) { 'close' }
+ let(:namespace) { project.namespace }
+ let(:project) { merge_request.project }
+ let(:user) { merge_request.author }
+ let(:feature_flag_name) { :route_hll_to_snowplow_phase2 }
+ end
end
describe '#merge_mr' do
@@ -121,6 +146,16 @@ RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redi
it_behaves_like "it records the event in the event counter" do
let(:event_action) { Gitlab::UsageDataCounters::TrackUniqueEvents::MERGE_REQUEST_ACTION }
end
+
+ it_behaves_like 'Snowplow event' do
+ let(:category) { Gitlab::UsageDataCounters::TrackUniqueEvents::MERGE_REQUEST_ACTION.to_s }
+ let(:label) { 'merge_requests_users' }
+ let(:action) { 'merge' }
+ let(:namespace) { project.namespace }
+ let(:project) { merge_request.project }
+ let(:user) { merge_request.author }
+ let(:feature_flag_name) { :route_hll_to_snowplow_phase2 }
+ end
end
describe '#reopen_mr' do
@@ -295,7 +330,12 @@ RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redi
let(:event_action) { Gitlab::UsageDataCounters::TrackUniqueEvents::PUSH_ACTION }
end
- it_behaves_like 'Snowplow event'
+ it_behaves_like 'Snowplow event' do
+ let(:category) { described_class.to_s }
+ let(:action) { 'action_active_users_project_repo' }
+ let(:namespace) { project.namespace }
+ let(:feature_flag_name) { :route_hll_to_snowplow }
+ end
end
describe '#bulk_push', :snowplow do
@@ -315,7 +355,12 @@ RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redi
let(:event_action) { Gitlab::UsageDataCounters::TrackUniqueEvents::PUSH_ACTION }
end
- it_behaves_like 'Snowplow event'
+ it_behaves_like 'Snowplow event' do
+ let(:category) { described_class.to_s }
+ let(:action) { 'action_active_users_project_repo' }
+ let(:namespace) { project.namespace }
+ let(:feature_flag_name) { :route_hll_to_snowplow }
+ end
end
describe 'Project' do
@@ -392,7 +437,7 @@ RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redi
end
end
- describe '#leave_note' do
+ describe '#leave_note', :snowplow do
subject(:leave_note) { service.leave_note(note, author) }
let(:note) { create(:note) }
@@ -409,6 +454,17 @@ RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redi
it_behaves_like "it records the event in the event counter" do
let(:note) { create(:diff_note_on_merge_request) }
end
+
+ it_behaves_like 'Snowplow event' do
+ let(:note) { create(:diff_note_on_merge_request) }
+ let(:category) { Gitlab::UsageDataCounters::TrackUniqueEvents::MERGE_REQUEST_ACTION.to_s }
+ let(:label) { 'merge_requests_users' }
+ let(:action) { 'comment' }
+ let(:project) { note.project }
+ let(:namespace) { project.namespace }
+ let(:feature_flag_name) { :route_hll_to_snowplow_phase2 }
+ let(:user) { author }
+ end
end
context 'when it is not a diff note' do
diff --git a/spec/services/git/branch_push_service_spec.rb b/spec/services/git/branch_push_service_spec.rb
index 57c130f76a4..befa9598964 100644
--- a/spec/services/git/branch_push_service_spec.rb
+++ b/spec/services/git/branch_push_service_spec.rb
@@ -410,7 +410,7 @@ RSpec.describe Git::BranchPushService, services: true do
end
context "for jira issue tracker" do
- include JiraServiceHelper
+ include JiraIntegrationHelpers
let(:jira_tracker) { project.create_jira_integration if project.jira_integration.nil? }
diff --git a/spec/services/import/fogbugz_service_spec.rb b/spec/services/import/fogbugz_service_spec.rb
new file mode 100644
index 00000000000..c9477dba7a5
--- /dev/null
+++ b/spec/services/import/fogbugz_service_spec.rb
@@ -0,0 +1,150 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Import::FogbugzService do
+ let_it_be(:user) { create(:user) }
+
+ let(:base_uri) { "https://test:7990" }
+ let(:token) { "asdasd12345" }
+ let(:repo_id) { "fogbugz_id" }
+ let(:repo) { instance_double(Gitlab::FogbugzImport::Repository, name: 'test', raw_data: nil) }
+
+ let(:client) { instance_double(Gitlab::FogbugzImport::Client) }
+ let(:credentials) { { uri: base_uri, token: token } }
+ let(:params) { { repo_id: repo_id } }
+
+ subject { described_class.new(client, user, params) }
+
+ before do
+ allow(subject).to receive(:authorized?).and_return(true)
+ end
+
+ context 'when no repo is found' do
+ before do
+ allow(client).to receive(:repo).with(repo_id).and_return(nil)
+ end
+
+ it 'returns an error' do
+ result = subject.execute(credentials)
+
+ expect(result).to include(
+ message: "Project #{repo_id} could not be found",
+ status: :error,
+ http_status: :unprocessable_entity
+ )
+ end
+ end
+
+ context 'when import source is disabled' do
+ before do
+ stub_application_setting(import_sources: nil)
+ allow(client).to receive(:repo).with(repo_id).and_return(repo)
+ end
+
+ it 'returns forbidden' do
+ result = subject.execute(credentials)
+
+ expect(result).to include(
+ status: :error,
+ http_status: :forbidden
+ )
+ end
+ end
+
+ context 'when user is unauthorized' do
+ before do
+ allow(subject).to receive(:authorized?).and_return(false)
+ end
+
+ it 'returns an error' do
+ result = subject.execute(credentials)
+
+ expect(result).to include(
+ message: "You don't have permissions to create this project",
+ status: :error,
+ http_status: :unauthorized
+ )
+ end
+ end
+
+ context 'verify url' do
+ shared_examples 'denies local request' do
+ before do
+ allow(client).to receive(:repo).with(repo_id).and_return(repo)
+ end
+
+ it 'does not allow requests' do
+ result = subject.execute(credentials)
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to include("Invalid URL:")
+ end
+ end
+
+ context 'when host is localhost' do
+ let(:base_uri) { 'http://localhost:3000' }
+
+ include_examples 'denies local request'
+ end
+
+ context 'when host is on local network' do
+ let(:base_uri) { 'https://192.168.0.191' }
+
+ include_examples 'denies local request'
+ end
+
+ context 'when host is ftp protocol' do
+ let(:base_uri) { 'ftp://testing' }
+
+ include_examples 'denies local request'
+ end
+ end
+
+ context 'when import starts succesfully' do
+ before do
+ allow(client).to receive(:repo).with(repo_id).and_return(
+ instance_double(Gitlab::FogbugzImport::Repository, name: 'test', raw_data: nil)
+ )
+ end
+
+ it 'returns success' do
+ result = subject.execute(credentials)
+
+ expect(result[:status]).to eq(:success)
+ expect(result[:project].name).to eq('test')
+ end
+ end
+
+ context 'when import fails to start' do
+ let(:error_messages_array) { instance_double(Array, join: "something went wrong") }
+ let(:errors_double) { instance_double(ActiveModel::Errors, full_messages: error_messages_array, :[] => nil) }
+ let(:project_double) { instance_double(Project, persisted?: false, errors: errors_double) }
+ let(:project_creator) { instance_double(Gitlab::FogbugzImport::ProjectCreator, execute: project_double )}
+
+ before do
+ allow(Gitlab::FogbugzImport::ProjectCreator).to receive(:new).and_return(project_creator)
+ allow(client).to receive(:repo).with(repo_id).and_return(
+ instance_double(Gitlab::FogbugzImport::Repository, name: 'test', raw_data: nil)
+ )
+ end
+
+ it 'returns error' do
+ result = subject.execute(credentials)
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq("something went wrong")
+ end
+ end
+
+ it 'returns error for unknown error causes' do
+ message = 'Not Implemented'
+ exception = StandardError.new(message)
+
+ allow(client).to receive(:repo).and_raise(exception)
+
+ expect(subject.execute(credentials)).to include({
+ status: :error,
+ message: "Fogbugz import failed due to an error: #{message}"
+ })
+ end
+end
diff --git a/spec/services/incident_management/issuable_escalation_statuses/prepare_update_service_spec.rb b/spec/services/incident_management/issuable_escalation_statuses/prepare_update_service_spec.rb
index 25164df40ca..6c99631fcb0 100644
--- a/spec/services/incident_management/issuable_escalation_statuses/prepare_update_service_spec.rb
+++ b/spec/services/incident_management/issuable_escalation_statuses/prepare_update_service_spec.rb
@@ -42,14 +42,6 @@ RSpec.describe IncidentManagement::IssuableEscalationStatuses::PrepareUpdateServ
it_behaves_like 'successful response', { status_event: :acknowledge }
- context 'when feature flag is disabled' do
- before do
- stub_feature_flags(incident_escalations: false)
- end
-
- it_behaves_like 'availability error response'
- end
-
context 'when user is anonymous' do
let(:current_user) { nil }
diff --git a/spec/services/incident_management/timeline_events/create_service_spec.rb b/spec/services/incident_management/timeline_events/create_service_spec.rb
index 38ce15e74f1..133a644f243 100644
--- a/spec/services/incident_management/timeline_events/create_service_spec.rb
+++ b/spec/services/incident_management/timeline_events/create_service_spec.rb
@@ -18,6 +18,7 @@ RSpec.describe IncidentManagement::TimelineEvents::CreateService do
}
end
+ let(:editable) { false }
let(:current_user) { user_with_permissions }
let(:service) { described_class.new(incident, current_user, args) }
@@ -32,6 +33,8 @@ RSpec.describe IncidentManagement::TimelineEvents::CreateService do
expect(execute).to be_error
expect(execute.message).to eq(message)
end
+
+ it_behaves_like 'does not track incident management event', :incident_management_timeline_event_created
end
shared_examples 'success response' do
@@ -45,7 +48,10 @@ RSpec.describe IncidentManagement::TimelineEvents::CreateService do
expect(result.project).to eq(project)
expect(result.note).to eq(args[:note])
expect(result.promoted_from_note).to eq(comment)
+ expect(result.editable).to eq(editable)
end
+
+ it_behaves_like 'an incident management tracked event', :incident_management_timeline_event_created
end
subject(:execute) { service.execute }
@@ -90,6 +96,30 @@ RSpec.describe IncidentManagement::TimelineEvents::CreateService do
end
end
+ context 'with editable param' do
+ let(:args) do
+ {
+ note: 'note',
+ occurred_at: Time.current,
+ action: 'new comment',
+ promoted_from_note: comment,
+ editable: editable
+ }
+ end
+
+ context 'when editable is true' do
+ let(:editable) { true }
+
+ it_behaves_like 'success response'
+ end
+
+ context 'when editable is false' do
+ let(:editable) { false }
+
+ it_behaves_like 'success response'
+ end
+ end
+
it 'successfully creates a database record', :aggregate_failures do
expect { execute }.to change { ::IncidentManagement::TimelineEvent.count }.by(1)
end
diff --git a/spec/services/incident_management/timeline_events/destroy_service_spec.rb b/spec/services/incident_management/timeline_events/destroy_service_spec.rb
index 01daee2b749..09026f87116 100644
--- a/spec/services/incident_management/timeline_events/destroy_service_spec.rb
+++ b/spec/services/incident_management/timeline_events/destroy_service_spec.rb
@@ -24,6 +24,8 @@ RSpec.describe IncidentManagement::TimelineEvents::DestroyService do
expect(execute).to be_error
expect(execute.message).to eq(message)
end
+
+ it_behaves_like 'does not track incident management event', :incident_management_timeline_event_deleted
end
subject(:execute) { service.execute }
@@ -49,12 +51,16 @@ RSpec.describe IncidentManagement::TimelineEvents::DestroyService do
it_behaves_like 'error response', 'Note cannot be removed'
end
- it 'successfully returns the timeline event', :aggregate_failures do
- expect(execute).to be_success
+ context 'success response' do
+ it 'successfully returns the timeline event', :aggregate_failures do
+ expect(execute).to be_success
+
+ result = execute.payload[:timeline_event]
+ expect(result).to be_a(::IncidentManagement::TimelineEvent)
+ expect(result.id).to eq(timeline_event.id)
+ end
- result = execute.payload[:timeline_event]
- expect(result).to be_a(::IncidentManagement::TimelineEvent)
- expect(result.id).to eq(timeline_event.id)
+ it_behaves_like 'an incident management tracked event', :incident_management_timeline_event_deleted
end
context 'when incident_timeline feature flag is enabled' do
diff --git a/spec/services/incident_management/timeline_events/update_service_spec.rb b/spec/services/incident_management/timeline_events/update_service_spec.rb
index 8bc0e5ce0ed..3da533fb2a6 100644
--- a/spec/services/incident_management/timeline_events/update_service_spec.rb
+++ b/spec/services/incident_management/timeline_events/update_service_spec.rb
@@ -10,6 +10,7 @@ RSpec.describe IncidentManagement::TimelineEvents::UpdateService do
let!(:timeline_event) { create(:incident_management_timeline_event, project: project, incident: incident) }
let(:occurred_at) { 1.minute.ago }
let(:params) { { note: 'Updated note', occurred_at: occurred_at } }
+ let(:current_user) { user }
before do
stub_feature_flags(incident_timeline: project)
@@ -21,6 +22,8 @@ RSpec.describe IncidentManagement::TimelineEvents::UpdateService do
expect(execute).to be_success
expect(execute.payload).to eq(timeline_event: timeline_event.reload)
end
+
+ it_behaves_like 'an incident management tracked event', :incident_management_timeline_event_edited
end
shared_examples 'error response' do |message|
@@ -28,6 +31,8 @@ RSpec.describe IncidentManagement::TimelineEvents::UpdateService do
expect(execute).to be_error
expect(execute.message).to eq(message)
end
+
+ it_behaves_like 'does not track incident management event', :incident_management_timeline_event_edited
end
shared_examples 'passing the correct was_changed value' do |was_changed|
@@ -135,6 +140,14 @@ RSpec.describe IncidentManagement::TimelineEvents::UpdateService do
execute
end
end
+
+ context 'when timeline event is non-editable' do
+ let!(:timeline_event) do
+ create(:incident_management_timeline_event, :non_editable, project: project, incident: incident)
+ end
+
+ it_behaves_like 'error response', 'You cannot edit this timeline event.'
+ end
end
context 'when user does not have permissions' do
diff --git a/spec/services/integrations/propagate_service_spec.rb b/spec/services/integrations/propagate_service_spec.rb
index 7ae843f6aeb..c971c4a0ad0 100644
--- a/spec/services/integrations/propagate_service_spec.rb
+++ b/spec/services/integrations/propagate_service_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe Integrations::PropagateService do
describe '.propagate' do
- include JiraServiceHelper
+ include JiraIntegrationHelpers
before do
stub_jira_integration_test
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index 3934ca04a00..5c1544d8ebc 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -66,6 +66,7 @@ RSpec.describe Issues::CreateService do
expect(issue.milestone).to eq(milestone)
expect(issue.due_date).to eq(Date.tomorrow)
expect(issue.work_item_type.base_type).to eq('issue')
+ expect(issue.issue_customer_relations_contacts).to be_empty
end
context 'when a build_service is provided' do
@@ -444,6 +445,50 @@ RSpec.describe Issues::CreateService do
expect(issue.issue_customer_relations_contacts.last.contact).to eq(contact)
end
end
+
+ context 'with external_author' do
+ let_it_be(:contact) { create(:contact, group: group) }
+
+ context 'when CRM contact exists with matching e-mail' do
+ let(:opts) do
+ {
+ title: 'Title',
+ external_author: contact.email
+ }
+ end
+
+ context 'with permission' do
+ it 'assigns contact to issue' do
+ group.add_reporter(user)
+ expect(issue).to be_persisted
+ expect(issue.issue_customer_relations_contacts.last.contact).to eq(contact)
+ end
+ end
+
+ context 'without permission' do
+ it 'does not assign contact to issue' do
+ group.add_guest(user)
+ expect(issue).to be_persisted
+ expect(issue.issue_customer_relations_contacts).to be_empty
+ end
+ end
+ end
+
+ context 'when no CRM contact exists with matching e-mail' do
+ let(:opts) do
+ {
+ title: 'Title',
+ external_author: 'example@gitlab.com'
+ }
+ end
+
+ it 'does not assign contact to issue' do
+ group.add_reporter(user)
+ expect(issue).to be_persisted
+ expect(issue.issue_customer_relations_contacts).to be_empty
+ end
+ end
+ end
end
context 'resolving discussions' do
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
index 35a380e01d0..56a3c22cd7f 100644
--- a/spec/services/issues/move_service_spec.rb
+++ b/spec/services/issues/move_service_spec.rb
@@ -194,20 +194,6 @@ RSpec.describe Issues::MoveService do
expect(new_issue.customer_relations_contacts).to be_empty
end
end
-
- context 'when customer_relations feature is disabled' do
- let(:another_project) { create(:project, namespace: create(:group)) }
-
- before do
- stub_feature_flags(customer_relations: false)
- end
-
- it 'does not preserve contacts' do
- new_issue = move_service.execute(old_issue, new_project)
-
- expect(new_issue.customer_relations_contacts).to be_empty
- end
- end
end
context 'moving to same project' do
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index d496857bb25..d11fe772023 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -1230,14 +1230,6 @@ RSpec.describe Issues::UpdateService, :mailer do
it_behaves_like 'updates the escalation status record', :acknowledged
end
-
- context 'with :incident_escalations feature flag disabled' do
- before do
- stub_feature_flags(incident_escalations: false)
- end
-
- it_behaves_like 'does not change the status record'
- end
end
context 'when issue type is not incident' do
diff --git a/spec/services/jira_connect_subscriptions/create_service_spec.rb b/spec/services/jira_connect_subscriptions/create_service_spec.rb
index cde4753cde7..85208a30c30 100644
--- a/spec/services/jira_connect_subscriptions/create_service_spec.rb
+++ b/spec/services/jira_connect_subscriptions/create_service_spec.rb
@@ -3,9 +3,10 @@
require 'spec_helper'
RSpec.describe JiraConnectSubscriptions::CreateService do
- let(:installation) { create(:jira_connect_installation) }
- let(:current_user) { create(:user) }
- let(:group) { create(:group) }
+ let_it_be(:installation) { create(:jira_connect_installation) }
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+
let(:path) { group.full_path }
let(:params) { { namespace_path: path, jira_user: jira_user } }
let(:jira_user) { double(:JiraUser, site_admin?: true) }
@@ -16,38 +17,31 @@ RSpec.describe JiraConnectSubscriptions::CreateService do
group.add_maintainer(current_user)
end
- shared_examples 'a failed execution' do
+ shared_examples 'a failed execution' do |**status_attributes|
it 'does not create a subscription' do
expect { subject }.not_to change { installation.subscriptions.count }
end
it 'returns an error status' do
expect(subject[:status]).to eq(:error)
+ expect(subject).to include(status_attributes)
end
end
context 'remote user does not have access' do
let(:jira_user) { double(site_admin?: false) }
- it 'does not create a subscription' do
- expect { subject }.not_to change { installation.subscriptions.count }
- end
-
- it 'returns error' do
- expect(subject[:status]).to eq(:error)
- end
+ it_behaves_like 'a failed execution',
+ http_status: 403,
+ message: 'The Jira user is not a site administrator. Check the permissions in Jira and try again.'
end
context 'remote user cannot be retrieved' do
let(:jira_user) { nil }
- it 'does not create a subscription' do
- expect { subject }.not_to change { installation.subscriptions.count }
- end
-
- it 'returns error' do
- expect(subject[:status]).to eq(:error)
- end
+ it_behaves_like 'a failed execution',
+ http_status: 403,
+ message: 'Could not fetch user information from Jira. Check the permissions in Jira and try again.'
end
context 'when user does have access' do
@@ -60,8 +54,8 @@ RSpec.describe JiraConnectSubscriptions::CreateService do
end
context 'namespace has projects' do
- let!(:project_1) { create(:project, group: group) }
- let!(:project_2) { create(:project, group: group) }
+ let_it_be(:project_1) { create(:project, group: group) }
+ let_it_be(:project_2) { create(:project, group: group) }
before do
stub_const("#{described_class}::MERGE_REQUEST_SYNC_BATCH_SIZE", 1)
@@ -81,12 +75,18 @@ RSpec.describe JiraConnectSubscriptions::CreateService do
context 'when path is invalid' do
let(:path) { 'some_invalid_namespace_path' }
- it_behaves_like 'a failed execution'
+ it_behaves_like 'a failed execution',
+ http_status: 401,
+ message: 'Cannot find namespace. Make sure you have sufficient permissions.'
end
context 'when user does not have access' do
- subject { described_class.new(installation, create(:user), namespace_path: path).execute }
+ let_it_be(:other_group) { create(:group) }
+
+ let(:path) { other_group.full_path }
- it_behaves_like 'a failed execution'
+ it_behaves_like 'a failed execution',
+ http_status: 401,
+ message: 'Cannot find namespace. Make sure you have sufficient permissions.'
end
end
diff --git a/spec/services/jira_import/start_import_service_spec.rb b/spec/services/jira_import/start_import_service_spec.rb
index e04e3314158..510f58f0e75 100644
--- a/spec/services/jira_import/start_import_service_spec.rb
+++ b/spec/services/jira_import/start_import_service_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe JiraImport::StartImportService do
- include JiraServiceHelper
+ include JiraIntegrationHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project) }
diff --git a/spec/services/jira_import/users_importer_spec.rb b/spec/services/jira_import/users_importer_spec.rb
index af408847260..ace9e0d5779 100644
--- a/spec/services/jira_import/users_importer_spec.rb
+++ b/spec/services/jira_import/users_importer_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe JiraImport::UsersImporter do
- include JiraServiceHelper
+ include JiraIntegrationHelpers
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
diff --git a/spec/services/markdown_content_rewriter_service_spec.rb b/spec/services/markdown_content_rewriter_service_spec.rb
index 37c8a210ba5..91a117536ca 100644
--- a/spec/services/markdown_content_rewriter_service_spec.rb
+++ b/spec/services/markdown_content_rewriter_service_spec.rb
@@ -8,38 +8,63 @@ RSpec.describe MarkdownContentRewriterService do
let_it_be(:target_parent) { create(:project, :public) }
let(:content) { 'My content' }
+ let(:issue) { create(:issue, project: source_parent, description: content)}
describe '#initialize' do
it 'raises an error if source_parent is not a Project' do
expect do
- described_class.new(user, content, create(:group), target_parent)
+ described_class.new(user, issue, :description, create(:group), target_parent)
end.to raise_error(ArgumentError, 'The rewriter classes require that `source_parent` is a `Project`')
end
+
+ it 'raises an error if field does not have cached markdown' do
+ expect do
+ described_class.new(user, issue, :author, source_parent, target_parent)
+ end.to raise_error(ArgumentError, 'The `field` attribute does not contain cached markdown')
+ end
end
describe '#execute' do
- subject { described_class.new(user, content, source_parent, target_parent).execute }
+ subject { described_class.new(user, issue, :description, source_parent, target_parent).execute }
- it 'calls the rewriter classes successfully', :aggregate_failures do
- [Gitlab::Gfm::ReferenceRewriter, Gitlab::Gfm::UploadsRewriter].each do |rewriter_class|
- service = double
-
- expect(service).to receive(:rewrite).with(target_parent)
- expect(rewriter_class).to receive(:new).and_return(service)
+ context 'when content does not need a rewrite' do
+ it 'returns original content and cached html' do
+ expect(subject).to eq({
+ 'description' => issue.description,
+ 'description_html' => issue.description_html,
+ 'skip_markdown_cache_validation' => true
+ })
end
+ end
+
+ context 'when content needs a rewrite' do
+ it 'calls the rewriter classes successfully', :aggregate_failures do
+ described_class::REWRITERS.each do |rewriter_class|
+ service = double
- subject
+ allow(service).to receive(:needs_rewrite?).and_return(true)
+
+ expect(service).to receive(:rewrite).with(target_parent)
+ expect(rewriter_class).to receive(:new).and_return(service)
+ end
+
+ subject
+ end
end
# Perform simple integration-style tests for each rewriter class.
# to prove they run correctly.
- context 'when content contains a reference' do
- let_it_be(:issue) { create(:issue, project: source_parent) }
+ context 'when content has references' do
+ let_it_be(:issue_to_reference) { create(:issue, project: source_parent) }
- let(:content) { "See ##{issue.iid}" }
+ let(:content) { "See ##{issue_to_reference.iid}" }
it 'rewrites content' do
- expect(subject).to eq("See #{source_parent.full_path}##{issue.iid}")
+ expect(subject).to eq({
+ 'description' => "See #{source_parent.full_path}##{issue_to_reference.iid}",
+ 'description_html' => nil,
+ 'skip_markdown_cache_validation' => false
+ })
end
end
@@ -50,9 +75,37 @@ RSpec.describe MarkdownContentRewriterService do
it 'rewrites content' do
new_content = subject
- expect(new_content).not_to eq(content)
- expect(new_content.length).to eq(content.length)
+ expect(new_content[:description]).not_to eq(content)
+ expect(new_content[:description].length).to eq(content.length)
+ expect(new_content[1]).to eq(nil)
end
end
end
+
+ describe '#safe_to_copy_markdown?' do
+ subject do
+ rewriter = described_class.new(user, issue, :description, source_parent, target_parent)
+ rewriter.safe_to_copy_markdown?
+ end
+
+ context 'when content has references' do
+ let(:milestone) { create(:milestone, project: source_parent) }
+ let(:content) { "Description that references #{milestone.to_reference}" }
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when content has uploaded file references' do
+ let(:image_uploader) { build(:file_uploader, project: source_parent) }
+ let(:content) { "Text and #{image_uploader.markdown_link}" }
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when content does not have references or uploads' do
+ let(:content) { "simples text with ```code```" }
+
+ it { is_expected.to eq(true) }
+ end
+ end
end
diff --git a/spec/services/members/approve_access_request_service_spec.rb b/spec/services/members/approve_access_request_service_spec.rb
index f7fbac612ee..d26bab7bb0a 100644
--- a/spec/services/members/approve_access_request_service_spec.rb
+++ b/spec/services/members/approve_access_request_service_spec.rb
@@ -9,36 +9,34 @@ RSpec.describe Members::ApproveAccessRequestService do
let(:access_requester_user) { create(:user) }
let(:access_requester) { source.requesters.find_by!(user_id: access_requester_user.id) }
let(:opts) { {} }
-
- shared_examples 'a service raising ActiveRecord::RecordNotFound' do
- it 'raises ActiveRecord::RecordNotFound' do
- expect { described_class.new(current_user).execute(access_requester, **opts) }.to raise_error(ActiveRecord::RecordNotFound)
- end
- end
+ let(:params) { {} }
+ let(:custom_access_level) { Gitlab::Access::MAINTAINER }
shared_examples 'a service raising Gitlab::Access::AccessDeniedError' do
it 'raises Gitlab::Access::AccessDeniedError' do
- expect { described_class.new(current_user).execute(access_requester, **opts) }.to raise_error(Gitlab::Access::AccessDeniedError)
+ expect { described_class.new(current_user, params).execute(access_requester, **opts) }.to raise_error(Gitlab::Access::AccessDeniedError)
end
end
shared_examples 'a service approving an access request' do
it 'succeeds' do
- expect { described_class.new(current_user).execute(access_requester, **opts) }.to change { source.requesters.count }.by(-1)
+ expect { described_class.new(current_user, params).execute(access_requester, **opts) }.to change { source.requesters.count }.by(-1)
end
it 'returns a <Source>Member' do
- member = described_class.new(current_user).execute(access_requester, **opts)
+ member = described_class.new(current_user, params).execute(access_requester, **opts)
expect(member).to be_a "#{source.class}Member".constantize
expect(member.requested_at).to be_nil
end
context 'with a custom access level' do
+ let(:params) { { access_level: custom_access_level } }
+
it 'returns a ProjectMember with the custom access level' do
- member = described_class.new(current_user, access_level: Gitlab::Access::MAINTAINER).execute(access_requester, **opts)
+ member = described_class.new(current_user, params).execute(access_requester, **opts)
- expect(member.access_level).to eq(Gitlab::Access::MAINTAINER)
+ expect(member.access_level).to eq(custom_access_level)
end
end
end
@@ -111,5 +109,38 @@ RSpec.describe Members::ApproveAccessRequestService do
let(:source) { group }
end
end
+
+ context 'in a project' do
+ let_it_be(:group_project) { create(:project, :public, group: create(:group, :public)) }
+
+ let(:source) { group_project }
+ let(:custom_access_level) { Gitlab::Access::OWNER }
+ let(:params) { { access_level: custom_access_level } }
+
+ before do
+ group_project.request_access(access_requester_user)
+ end
+
+ context 'maintainers' do
+ before do
+ group_project.add_maintainer(current_user)
+ end
+
+ context 'cannot approve the access request of a requester to give them OWNER permissions' do
+ it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
+ end
+ end
+
+ context 'owners' do
+ before do
+ # so that `current_user` is considered an `OWNER` in the project via inheritance.
+ group_project.group.add_owner(current_user)
+ end
+
+ context 'can approve the access request of a requester to give them OWNER permissions' do
+ it_behaves_like 'a service approving an access request'
+ end
+ end
+ end
end
end
diff --git a/spec/services/members/create_service_spec.rb b/spec/services/members/create_service_spec.rb
index 730175af0bb..e79e13af769 100644
--- a/spec/services/members/create_service_spec.rb
+++ b/spec/services/members/create_service_spec.rb
@@ -33,6 +33,18 @@ RSpec.describe Members::CreateService, :aggregate_failures, :clean_gitlab_redis_
it 'raises a Gitlab::Access::AccessDeniedError' do
expect { execute_service }.to raise_error(Gitlab::Access::AccessDeniedError)
end
+
+ context 'when a project maintainer attempts to add owners' do
+ let(:access_level) { Gitlab::Access::OWNER }
+
+ before do
+ source.add_maintainer(current_user)
+ end
+
+ it 'raises a Gitlab::Access::AccessDeniedError' do
+ expect { execute_service }.to raise_error(Gitlab::Access::AccessDeniedError)
+ end
+ end
end
context 'when passing an invalid source' do
diff --git a/spec/services/members/creator_service_spec.rb b/spec/services/members/creator_service_spec.rb
index ff5bf705b6c..8b1df2ab86d 100644
--- a/spec/services/members/creator_service_spec.rb
+++ b/spec/services/members/creator_service_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe Members::CreatorService do
describe '#execute' do
it 'raises error for new member on authorization check implementation' do
expect do
- described_class.new(source, user, :maintainer, current_user: current_user).execute
+ described_class.add_user(source, user, :maintainer, current_user: current_user)
end.to raise_error(NotImplementedError)
end
@@ -19,7 +19,7 @@ RSpec.describe Members::CreatorService do
source.add_developer(user)
expect do
- described_class.new(source, user, :maintainer, current_user: current_user).execute
+ described_class.add_user(source, user, :maintainer, current_user: current_user)
end.to raise_error(NotImplementedError)
end
end
diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb
index 1a1283b1078..9f0daba3327 100644
--- a/spec/services/members/destroy_service_spec.rb
+++ b/spec/services/members/destroy_service_spec.rb
@@ -105,26 +105,46 @@ RSpec.describe Members::DestroyService do
context 'with a project member' do
let(:member) { group_project.members.find_by(user_id: member_user.id) }
- before do
- group_project.add_developer(member_user)
+ context 'when current user does not have any membership management permissions' do
+ before do
+ group_project.add_developer(member_user)
+ end
+
+ it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
+
+ context 'when skipping authorisation' do
+ it_behaves_like 'a service destroying a member with access' do
+ let(:opts) { { skip_authorization: true, unassign_issuables: true } }
+ end
+ end
end
- it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
+ context 'when a project maintainer tries to destroy a project owner' do
+ before do
+ group_project.add_owner(member_user)
+ end
- it_behaves_like 'a service destroying a member with access' do
- let(:opts) { { skip_authorization: true, unassign_issuables: true } }
+ it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
+
+ context 'when skipping authorisation' do
+ it_behaves_like 'a service destroying a member with access' do
+ let(:opts) { { skip_authorization: true, unassign_issuables: true } }
+ end
+ end
end
end
+ end
- context 'with a group member' do
- let(:member) { group.members.find_by(user_id: member_user.id) }
+ context 'with a group member' do
+ let(:member) { group.members.find_by(user_id: member_user.id) }
- before do
- group.add_developer(member_user)
- end
+ before do
+ group.add_developer(member_user)
+ end
- it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
+ it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
+ context 'when skipping authorisation' do
it_behaves_like 'a service destroying a member with access' do
let(:opts) { { skip_authorization: true, unassign_issuables: true } }
end
diff --git a/spec/services/members/groups/bulk_creator_service_spec.rb b/spec/services/members/groups/bulk_creator_service_spec.rb
deleted file mode 100644
index 0623ae00080..00000000000
--- a/spec/services/members/groups/bulk_creator_service_spec.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Members::Groups::BulkCreatorService do
- it_behaves_like 'bulk member creation' do
- let_it_be(:source, reload: true) { create(:group, :public) }
- let_it_be(:member_type) { GroupMember }
- end
-end
diff --git a/spec/services/members/groups/creator_service_spec.rb b/spec/services/members/groups/creator_service_spec.rb
index c3ba7c0374d..b80b7998eac 100644
--- a/spec/services/members/groups/creator_service_spec.rb
+++ b/spec/services/members/groups/creator_service_spec.rb
@@ -3,16 +3,24 @@
require 'spec_helper'
RSpec.describe Members::Groups::CreatorService do
+ let_it_be(:source, reload: true) { create(:group, :public) }
+ let_it_be(:user) { create(:user) }
+
describe '.access_levels' do
it 'returns Gitlab::Access.options_with_owner' do
expect(described_class.access_levels).to eq(Gitlab::Access.sym_options_with_owner)
end
end
- describe '#execute' do
- let_it_be(:source, reload: true) { create(:group, :public) }
- let_it_be(:user) { create(:user) }
+ it_behaves_like 'owner management'
+
+ describe '.add_users' do
+ it_behaves_like 'bulk member creation' do
+ let_it_be(:member_type) { GroupMember }
+ end
+ end
+ describe '.add_user' do
it_behaves_like 'member creation' do
let_it_be(:member_type) { GroupMember }
end
@@ -22,7 +30,7 @@ RSpec.describe Members::Groups::CreatorService do
expect(AuthorizedProjectsWorker).to receive(:bulk_perform_and_wait).once
1.upto(3) do
- described_class.new(source, user, :maintainer).execute
+ described_class.add_user(source, user, :maintainer)
end
end
end
diff --git a/spec/services/members/invite_service_spec.rb b/spec/services/members/invite_service_spec.rb
index 8213e8baae0..a948041479b 100644
--- a/spec/services/members/invite_service_spec.rb
+++ b/spec/services/members/invite_service_spec.rb
@@ -367,20 +367,21 @@ RSpec.describe Members::InviteService, :aggregate_failures, :clean_gitlab_redis_
context 'when email is already a member with a user on the project' do
let!(:existing_member) { create(:project_member, :guest, project: project) }
- let(:params) { { email: "#{existing_member.user.email}" } }
+ let(:params) { { email: "#{existing_member.user.email}", access_level: ProjectMember::MAINTAINER } }
- it 'returns an error for the already invited email' do
- expect_not_to_create_members
- expect(result[:message][existing_member.user.email]).to eq("User already exists in source")
+ it 'allows re-invite of an already invited email and updates the access_level' do
+ expect { result }.not_to change(ProjectMember, :count)
+ expect(result[:status]).to eq(:success)
+ expect(existing_member.reset.access_level).to eq ProjectMember::MAINTAINER
end
context 'when email belongs to an existing user as a secondary email' do
let(:secondary_email) { create(:email, email: 'secondary@example.com', user: existing_member.user) }
let(:params) { { email: "#{secondary_email.email}" } }
- it 'returns an error for the already invited email' do
- expect_not_to_create_members
- expect(result[:message][secondary_email.email]).to eq("User already exists in source")
+ it 'allows re-invite to an already invited email' do
+ expect_to_create_members(count: 0)
+ expect(result[:status]).to eq(:success)
end
end
end
diff --git a/spec/services/members/mailgun/process_webhook_service_spec.rb b/spec/services/members/mailgun/process_webhook_service_spec.rb
deleted file mode 100644
index d6a21183395..00000000000
--- a/spec/services/members/mailgun/process_webhook_service_spec.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Members::Mailgun::ProcessWebhookService do
- describe '#execute', :aggregate_failures do
- let_it_be(:member) { create(:project_member, :invited) }
-
- let(:raw_invite_token) { member.raw_invite_token }
- let(:payload) { { 'user-variables' => { ::Members::Mailgun::INVITE_EMAIL_TOKEN_KEY => raw_invite_token } } }
-
- subject(:service) { described_class.new(payload).execute }
-
- it 'marks the member invite email success as false' do
- expect(Gitlab::AppLogger).to receive(:info).with(/^UPDATED MEMBER INVITE_EMAIL_SUCCESS/).and_call_original
-
- expect { service }.to change { member.reload.invite_email_success }.from(true).to(false)
- end
-
- context 'when member can not be found' do
- let(:raw_invite_token) { '_foobar_' }
-
- it 'does not change member status' do
- expect(Gitlab::AppLogger).not_to receive(:info).with(/^UPDATED MEMBER INVITE_EMAIL_SUCCESS/)
-
- expect { service }.not_to change { member.reload.invite_email_success }
- end
- end
-
- context 'when invite token is not found in payload' do
- let(:payload) { {} }
-
- it 'does not change member status and logs an error' do
- expect(Gitlab::AppLogger).not_to receive(:info).with(/^UPDATED MEMBER INVITE_EMAIL_SUCCESS/)
- expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
- an_instance_of(described_class::ProcessWebhookServiceError))
-
- expect { service }.not_to change { member.reload.invite_email_success }
- end
- end
- end
-end
diff --git a/spec/services/members/projects/bulk_creator_service_spec.rb b/spec/services/members/projects/bulk_creator_service_spec.rb
deleted file mode 100644
index 7acb7d79fe7..00000000000
--- a/spec/services/members/projects/bulk_creator_service_spec.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Members::Projects::BulkCreatorService do
- it_behaves_like 'bulk member creation' do
- let_it_be(:source, reload: true) { create(:project, :public) }
- let_it_be(:member_type) { ProjectMember }
- end
-end
diff --git a/spec/services/members/projects/creator_service_spec.rb b/spec/services/members/projects/creator_service_spec.rb
index 7605238c3c5..38955122ab0 100644
--- a/spec/services/members/projects/creator_service_spec.rb
+++ b/spec/services/members/projects/creator_service_spec.rb
@@ -3,16 +3,24 @@
require 'spec_helper'
RSpec.describe Members::Projects::CreatorService do
+ let_it_be(:source, reload: true) { create(:project, :public) }
+ let_it_be(:user) { create(:user) }
+
describe '.access_levels' do
it 'returns Gitlab::Access.sym_options_with_owner' do
expect(described_class.access_levels).to eq(Gitlab::Access.sym_options_with_owner)
end
end
- describe '#execute' do
- let_it_be(:source, reload: true) { create(:project, :public) }
- let_it_be(:user) { create(:user) }
+ it_behaves_like 'owner management'
+
+ describe '.add_users' do
+ it_behaves_like 'bulk member creation' do
+ let_it_be(:member_type) { ProjectMember }
+ end
+ end
+ describe '.add_user' do
it_behaves_like 'member creation' do
let_it_be(:member_type) { ProjectMember }
end
@@ -22,7 +30,7 @@ RSpec.describe Members::Projects::CreatorService do
expect(AuthorizedProjectUpdate::UserRefreshFromReplicaWorker).to receive(:bulk_perform_in).once
1.upto(3) do
- described_class.new(source, user, :maintainer).execute
+ described_class.add_user(source, user, :maintainer)
end
end
end
diff --git a/spec/services/members/update_service_spec.rb b/spec/services/members/update_service_spec.rb
index a1b1397d444..f919d6d1516 100644
--- a/spec/services/members/update_service_spec.rb
+++ b/spec/services/members/update_service_spec.rb
@@ -9,8 +9,9 @@ RSpec.describe Members::UpdateService do
let(:member_user) { create(:user) }
let(:permission) { :update }
let(:member) { source.members_and_requesters.find_by!(user_id: member_user.id) }
+ let(:access_level) { Gitlab::Access::MAINTAINER }
let(:params) do
- { access_level: Gitlab::Access::MAINTAINER }
+ { access_level: access_level }
end
subject { described_class.new(current_user, params).execute(member, permission: permission) }
@@ -29,7 +30,7 @@ RSpec.describe Members::UpdateService do
updated_member = subject.fetch(:member)
expect(updated_member).to be_valid
- expect(updated_member.access_level).to eq(Gitlab::Access::MAINTAINER)
+ expect(updated_member.access_level).to eq(access_level)
end
it 'returns success status' do
@@ -111,4 +112,75 @@ RSpec.describe Members::UpdateService do
let(:source) { group }
end
end
+
+ context 'in a project' do
+ let_it_be(:group_project) { create(:project, group: create(:group)) }
+
+ let(:source) { group_project }
+
+ context 'a project maintainer' do
+ before do
+ group_project.add_maintainer(current_user)
+ end
+
+ context 'cannot update a member to OWNER' do
+ before do
+ group_project.add_developer(member_user)
+ end
+
+ it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
+ let(:access_level) { Gitlab::Access::OWNER }
+ end
+ end
+
+ context 'cannot update themselves to OWNER' do
+ let(:member) { source.members_and_requesters.find_by!(user_id: current_user.id) }
+
+ before do
+ group_project.add_developer(member_user)
+ end
+
+ it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
+ let(:access_level) { Gitlab::Access::OWNER }
+ end
+ end
+
+ context 'cannot downgrade a member from OWNER' do
+ before do
+ group_project.add_owner(member_user)
+ end
+
+ it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
+ let(:access_level) { Gitlab::Access::MAINTAINER }
+ end
+ end
+ end
+
+ context 'owners' do
+ before do
+ # so that `current_user` is considered an `OWNER` in the project via inheritance.
+ group_project.group.add_owner(current_user)
+ end
+
+ context 'can update a member to OWNER' do
+ before do
+ group_project.add_developer(member_user)
+ end
+
+ it_behaves_like 'a service updating a member' do
+ let(:access_level) { Gitlab::Access::OWNER }
+ end
+ end
+
+ context 'can downgrade a member from OWNER' do
+ before do
+ group_project.add_owner(member_user)
+ end
+
+ it_behaves_like 'a service updating a member' do
+ let(:access_level) { Gitlab::Access::MAINTAINER }
+ end
+ end
+ end
+ end
end
diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb
index ab3d9880d29..3c9d2271ddc 100644
--- a/spec/services/merge_requests/build_service_spec.rb
+++ b/spec/services/merge_requests/build_service_spec.rb
@@ -79,6 +79,15 @@ RSpec.describe MergeRequests::BuildService do
end
end
+ shared_examples 'with a Default.md template' do
+ let(:files) { { '.gitlab/merge_request_templates/Default.md' => 'Default template contents' } }
+ let(:project) { create(:project, :custom_repo, files: files ) }
+
+ it 'the template description is preferred' do
+ expect(merge_request.description).to eq('Default template contents')
+ end
+ end
+
describe '#execute' do
it 'calls the compare service with the correct arguments' do
allow_any_instance_of(described_class).to receive(:projects_and_branches_valid?).and_return(true)
@@ -221,6 +230,7 @@ RSpec.describe MergeRequests::BuildService do
end
it_behaves_like 'allows the merge request to be created'
+ it_behaves_like 'with a Default.md template'
it 'uses the title of the commit as the title of the merge request' do
expect(merge_request.title).to eq(commit_2.safe_message.split("\n").first)
@@ -241,6 +251,8 @@ RSpec.describe MergeRequests::BuildService do
context 'commit has no description' do
let(:commits) { Commit.decorate([commit_3], project) }
+ it_behaves_like 'with a Default.md template'
+
it 'uses the title of the commit as the title of the merge request' do
expect(merge_request.title).to eq(commit_3.safe_message)
end
@@ -279,6 +291,17 @@ RSpec.describe MergeRequests::BuildService do
expect(merge_request.description).to eq(expected_description)
end
+
+ context 'a Default.md template is defined' do
+ let(:files) { { '.gitlab/merge_request_templates/Default.md' => 'Default template contents' } }
+ let(:project) { create(:project, :custom_repo, files: files ) }
+
+ it 'appends the closing description to a Default.md template' do
+ expected_description = ['Default template contents', closing_message].compact.join("\n\n")
+
+ expect(merge_request.description).to eq(expected_description)
+ end
+ end
end
context 'when the source branch matches an internal issue' do
@@ -332,6 +355,7 @@ RSpec.describe MergeRequests::BuildService do
end
it_behaves_like 'allows the merge request to be created'
+ it_behaves_like 'with a Default.md template'
it 'uses the title of the branch as the merge request title' do
expect(merge_request.title).to eq('Feature branch')
@@ -347,6 +371,15 @@ RSpec.describe MergeRequests::BuildService do
it 'keeps the description from the initial params' do
expect(merge_request.description).to eq(description)
end
+
+ context 'a Default.md template is defined' do
+ let(:files) { { '.gitlab/merge_request_templates/Default.md' => 'Default template contents' } }
+ let(:project) { create(:project, :custom_repo, files: files ) }
+
+ it 'keeps the description from the initial params' do
+ expect(merge_request.description).to eq(description)
+ end
+ end
end
context 'when the source branch matches an issue' do
@@ -377,6 +410,17 @@ RSpec.describe MergeRequests::BuildService do
it 'sets the closing description' do
expect(merge_request.description).to eq(closing_message)
end
+
+ context 'a Default.md template is defined' do
+ let(:files) { { '.gitlab/merge_request_templates/Default.md' => 'Default template contents' } }
+ let(:project) { create(:project, :custom_repo, files: files ) }
+
+ it 'appends the closing description to a Default.md template' do
+ expected_description = ['Default template contents', closing_message].compact.join("\n\n")
+
+ expect(merge_request.description).to eq(expected_description)
+ end
+ end
end
end
end
@@ -389,6 +433,7 @@ RSpec.describe MergeRequests::BuildService do
end
it_behaves_like 'allows the merge request to be created'
+ it_behaves_like 'with a Default.md template'
it 'uses the first line of the first multi-line commit message as the title' do
expect(merge_request.title).to eq('Closes #1234 Second commit')
@@ -426,6 +471,17 @@ RSpec.describe MergeRequests::BuildService do
it 'sets the closing description' do
expect(merge_request.description).to eq("Create the app#{closing_message ? "\n\n" + closing_message : ''}")
end
+
+ context 'a Default.md template is defined' do
+ let(:files) { { '.gitlab/merge_request_templates/Default.md' => 'Default template contents' } }
+ let(:project) { create(:project, :custom_repo, files: files ) }
+
+ it 'appends the closing description to a Default.md template' do
+ expected_description = ['Default template contents', closing_message].compact.join("\n\n")
+
+ expect(merge_request.description).to eq(expected_description)
+ end
+ end
end
end
@@ -626,4 +682,52 @@ RSpec.describe MergeRequests::BuildService do
end
end
end
+
+ describe '#assign_description_from_repository_template' do
+ subject { service.send(:assign_description_from_repository_template) }
+
+ it 'performs no action if the merge request description is not blank' do
+ merge_request.description = 'foo'
+ subject
+ expect(merge_request.description).to eq 'foo'
+ end
+
+ context 'when a Default template is not found' do
+ it 'does not modify the merge request description' do
+ merge_request.description = nil
+ subject
+ expect(merge_request.description).to be_nil
+ end
+ end
+
+ context 'when a Default template is found' do
+ context 'when its contents cannot be retrieved' do
+ let(:files) { { '.gitlab/merge_request_templates/OtherTemplate.md' => 'Other template contents' } }
+ let(:project) { create(:project, :custom_repo, files: files ) }
+
+ it 'does not modify the merge request description' do
+ allow(TemplateFinder).to receive(:all_template_names).and_return({
+ merge_requests: [
+ { name: 'Default', id: 'default', key: 'default', project_id: project.id }
+ ]
+ })
+
+ merge_request.description = nil
+ subject
+ expect(merge_request.description).to be_nil
+ end
+ end
+
+ context 'when its contents can be retrieved' do
+ let(:files) { { '.gitlab/merge_request_templates/Default.md' => 'Default template contents' } }
+ let(:project) { create(:project, :custom_repo, files: files ) }
+
+ it 'modifies the merge request description' do
+ merge_request.description = nil
+ subject
+ expect(merge_request.description).to eq 'Default template contents'
+ end
+ end
+ end
+ end
end
diff --git a/spec/services/merge_requests/create_pipeline_service_spec.rb b/spec/services/merge_requests/create_pipeline_service_spec.rb
index d84ce8d15b4..08ad05b54da 100644
--- a/spec/services/merge_requests/create_pipeline_service_spec.rb
+++ b/spec/services/merge_requests/create_pipeline_service_spec.rb
@@ -50,6 +50,19 @@ RSpec.describe MergeRequests::CreatePipelineService do
expect(response.payload.source).to eq('merge_request_event')
end
+ context 'when push options contain ci.skip' do
+ let(:params) { { push_options: { ci: { skip: true } } } }
+
+ it 'creates a skipped pipeline' do
+ expect { response }.to change { Ci::Pipeline.count }.by(1)
+
+ expect(response).to be_success
+ expect(response.payload).to be_persisted
+ expect(response.payload.builds).to be_empty
+ expect(response.payload).to be_skipped
+ end
+ end
+
context 'with fork merge request' do
let_it_be(:forked_project) { fork_project(project, nil, repository: true, target_project: create(:project, :private, :repository)) }
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index 49f691e97e2..c0c56a72192 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -32,7 +32,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
it 'creates an MR' do
expect(merge_request).to be_valid
- expect(merge_request.work_in_progress?).to be(false)
+ expect(merge_request.draft?).to be(false)
expect(merge_request.title).to eq('Awesome merge_request')
expect(merge_request.assignees).to be_empty
expect(merge_request.merge_params['force_remove_source_branch']).to eq('1')
@@ -74,7 +74,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
end
it 'sets MR to draft' do
- expect(merge_request.work_in_progress?).to be(true)
+ expect(merge_request.draft?).to be(true)
end
end
@@ -90,7 +90,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
end
it 'sets MR to draft' do
- expect(merge_request.work_in_progress?).to be(true)
+ expect(merge_request.draft?).to be(true)
end
end
end
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index 78deab64b1c..a2d73d8c9b1 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -161,7 +161,7 @@ RSpec.describe MergeRequests::MergeService do
end
context 'with Jira integration' do
- include JiraServiceHelper
+ include JiraIntegrationHelpers
let(:jira_tracker) { project.create_jira_integration }
let(:jira_issue) { ExternalIssue.new('JIRA-123', project) }
@@ -263,10 +263,13 @@ RSpec.describe MergeRequests::MergeService do
merge_request.update_attribute(:merge_params, { 'force_remove_source_branch' => '1' })
end
+ # Not a real use case. When a merger merges a MR , merge param 'should_remove_source_branch' is defined
it 'removes the source branch using the author user' do
expect(::MergeRequests::DeleteSourceBranchWorker).to receive(:perform_async).with(merge_request.id, merge_request.source_branch_sha, merge_request.author.id)
service.execute(merge_request)
+
+ expect(merge_request.reload.should_remove_source_branch?).to be nil
end
context 'when the merger set the source branch not to be removed' do
@@ -276,6 +279,8 @@ RSpec.describe MergeRequests::MergeService do
expect(::MergeRequests::DeleteSourceBranchWorker).not_to receive(:perform_async)
service.execute(merge_request)
+
+ expect(merge_request.reload.should_remove_source_branch?).to be false
end
end
end
@@ -289,6 +294,8 @@ RSpec.describe MergeRequests::MergeService do
expect(::MergeRequests::DeleteSourceBranchWorker).to receive(:perform_async).with(merge_request.id, merge_request.source_branch_sha, user.id)
service.execute(merge_request)
+
+ expect(merge_request.reload.should_remove_source_branch?).to be true
end
end
end
diff --git a/spec/services/merge_requests/mergeability/run_checks_service_spec.rb b/spec/services/merge_requests/mergeability/run_checks_service_spec.rb
index d4ee4afd71d..2bb7dc3eef7 100644
--- a/spec/services/merge_requests/mergeability/run_checks_service_spec.rb
+++ b/spec/services/merge_requests/mergeability/run_checks_service_spec.rb
@@ -7,12 +7,6 @@ RSpec.describe MergeRequests::Mergeability::RunChecksService do
let_it_be(:merge_request) { create(:merge_request) }
- describe '#CHECKS' do
- it 'contains every subclass of the base checks service', :eager_load do
- expect(described_class::CHECKS).to contain_exactly(*MergeRequests::Mergeability::CheckBaseService.subclasses)
- end
- end
-
describe '#execute' do
subject(:execute) { run_checks.execute }
@@ -22,8 +16,8 @@ RSpec.describe MergeRequests::Mergeability::RunChecksService do
context 'when every check is skipped', :eager_load do
before do
MergeRequests::Mergeability::CheckBaseService.subclasses.each do |subclass|
- expect_next_instance_of(subclass) do |service|
- expect(service).to receive(:skip?).and_return(true)
+ allow_next_instance_of(subclass) do |service|
+ allow(service).to receive(:skip?).and_return(true)
end
end
end
@@ -35,7 +29,7 @@ RSpec.describe MergeRequests::Mergeability::RunChecksService do
context 'when a check is skipped' do
it 'does not execute the check' do
- described_class::CHECKS.each do |check|
+ merge_request.mergeability_checks.each do |check|
allow_next_instance_of(check) do |service|
allow(service).to receive(:skip?).and_return(false)
allow(service).to receive(:execute).and_return(success_result)
@@ -47,7 +41,13 @@ RSpec.describe MergeRequests::Mergeability::RunChecksService do
expect(service).not_to receive(:execute)
end
- expect(execute).to match_array([success_result, success_result, success_result, success_result])
+ # Since we're only marking one check to be skipped, we expect to receive
+ # `# of checks - 1` success result objects in return
+ #
+ check_count = merge_request.mergeability_checks.count - 1
+ success_array = (1..check_count).each_with_object([]) { |_, array| array << success_result }
+
+ expect(execute).to match_array(success_array)
end
end
@@ -56,7 +56,7 @@ RSpec.describe MergeRequests::Mergeability::RunChecksService do
let(:merge_check) { instance_double(MergeRequests::Mergeability::CheckCiStatusService) }
before do
- described_class::CHECKS.each do |check|
+ merge_request.mergeability_checks.each do |check|
allow_next_instance_of(check) do |service|
allow(service).to receive(:skip?).and_return(true)
end
diff --git a/spec/services/merge_requests/post_merge_service_spec.rb b/spec/services/merge_requests/post_merge_service_spec.rb
index f0885365f96..e486daae15e 100644
--- a/spec/services/merge_requests/post_merge_service_spec.rb
+++ b/spec/services/merge_requests/post_merge_service_spec.rb
@@ -106,32 +106,6 @@ RSpec.describe MergeRequests::PostMergeService do
expect(merge_request.reload).to be_merged
end
end
-
- context 'when async_mr_close_issue feature flag is disabled' do
- before do
- stub_feature_flags(async_mr_close_issue: false)
- end
-
- it 'executes Issues::CloseService' do
- expect_next_instance_of(Issues::CloseService) do |close_service|
- expect(close_service).to receive(:execute).with(issue, commit: merge_request)
- end
-
- subject
-
- expect(merge_request.reload).to be_merged
- end
-
- it 'marks MR as merged regardless of errors when closing issues' do
- expect_next_instance_of(Issues::CloseService) do |close_service|
- allow(close_service).to receive(:execute).with(issue, commit: merge_request).and_raise(RuntimeError)
- end
-
- expect { subject }.to raise_error(RuntimeError)
-
- expect(merge_request.reload).to be_merged
- end
- end
end
context 'when the merge request has review apps' do
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 6e6b4a91e0d..eecf7c21cba 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -228,6 +228,21 @@ RSpec.describe MergeRequests::RefreshService do
expect(@another_merge_request.has_commits?).to be_falsy
end
+ context 'when "push_options: nil" is passed' do
+ let(:service_instance) { service.new(project: project, current_user: @user, params: { push_options: nil }) }
+
+ subject { service_instance.execute(@oldrev, @newrev, ref) }
+
+ it 'creates a detached merge request pipeline with commits' do
+ expect { subject }
+ .to change { @merge_request.pipelines_for_merge_request.count }.by(1)
+ .and change { @another_merge_request.pipelines_for_merge_request.count }.by(0)
+
+ expect(@merge_request.has_commits?).to be_truthy
+ expect(@another_merge_request.has_commits?).to be_falsy
+ end
+ end
+
it 'does not create detached merge request pipeline for forked project' do
expect { subject }
.not_to change { @fork_merge_request.pipelines_for_merge_request.count }
@@ -741,47 +756,48 @@ RSpec.describe MergeRequests::RefreshService do
refresh_service.execute(oldrev, newrev, 'refs/heads/wip')
fixup_merge_request.reload
- expect(fixup_merge_request.work_in_progress?).to eq(true)
+ expect(fixup_merge_request.draft?).to eq(true)
expect(fixup_merge_request.notes.last.note).to match(
/marked this merge request as \*\*draft\*\* from #{Commit.reference_pattern}/
)
end
it 'references the commit that caused the draft status' do
- wip_merge_request = create(:merge_request,
+ draft_merge_request = create(:merge_request,
source_project: @project,
source_branch: 'wip',
target_branch: 'master',
target_project: @project)
- commits = wip_merge_request.commits
+ commits = draft_merge_request.commits
oldrev = commits.last.id
newrev = commits.first.id
- wip_commit = wip_merge_request.commits.find(&:work_in_progress?)
+ draft_commit = draft_merge_request.commits.find(&:draft?)
refresh_service.execute(oldrev, newrev, 'refs/heads/wip')
- expect(wip_merge_request.reload.notes.last.note).to eq(
- "marked this merge request as **draft** from #{wip_commit.id}"
+ expect(draft_merge_request.reload.notes.last.note).to eq(
+ "marked this merge request as **draft** from #{draft_commit.id}"
)
end
it 'does not mark as draft based on commits that do not belong to an MR' do
allow(refresh_service).to receive(:find_new_commits)
+
refresh_service.instance_variable_set("@commits", [
double(
id: 'aaaaaaa',
sha: 'aaaaaaa',
short_id: 'aaaaaaa',
title: 'Fix issue',
- work_in_progress?: false
+ draft?: false
),
double(
id: 'bbbbbbb',
sha: 'bbbbbbbb',
short_id: 'bbbbbbb',
title: 'fixup! Fix issue',
- work_in_progress?: true,
+ draft?: true,
to_reference: 'bbbbbbb'
)
])
@@ -789,7 +805,7 @@ RSpec.describe MergeRequests::RefreshService do
refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
reload_mrs
- expect(@merge_request.work_in_progress?).to be_falsey
+ expect(@merge_request.draft?).to be_falsey
end
end
diff --git a/spec/services/metrics/dashboard/panel_preview_service_spec.rb b/spec/services/metrics/dashboard/panel_preview_service_spec.rb
index 2877d22d1f3..787c61cc918 100644
--- a/spec/services/metrics/dashboard/panel_preview_service_spec.rb
+++ b/spec/services/metrics/dashboard/panel_preview_service_spec.rb
@@ -45,7 +45,6 @@ RSpec.describe Metrics::Dashboard::PanelPreviewService do
::Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter,
::Gitlab::Metrics::Dashboard::Stages::MetricEndpointInserter,
::Gitlab::Metrics::Dashboard::Stages::PanelIdsInserter,
- ::Gitlab::Metrics::Dashboard::Stages::AlertsInserter,
::Gitlab::Metrics::Dashboard::Stages::UrlValidator
]
processor_params = [project, dashboard, sequence, environment: environment]
diff --git a/spec/services/notes/copy_service_spec.rb b/spec/services/notes/copy_service_spec.rb
index dd11fa15ea8..fd8802e6640 100644
--- a/spec/services/notes/copy_service_spec.rb
+++ b/spec/services/notes/copy_service_spec.rb
@@ -16,9 +16,8 @@ RSpec.describe Notes::CopyService do
let_it_be(:group) { create(:group) }
let_it_be(:from_project) { create(:project, :public, group: group) }
let_it_be(:to_project) { create(:project, :public, group: group) }
-
- let(:from_noteable) { create(:issue, project: from_project) }
- let(:to_noteable) { create(:issue, project: to_project) }
+ let_it_be(:from_noteable) { create(:issue, project: from_project) }
+ let_it_be(:to_noteable) { create(:issue, project: to_project) }
subject(:execute_service) { described_class.new(user, from_noteable, to_noteable).execute }
@@ -85,6 +84,15 @@ RSpec.describe Notes::CopyService do
expect(execute_service).to be_success
end
end
+
+ it 'copies rendered markdown from note_html' do
+ expect(Banzai::Renderer).not_to receive(:cacheless_render_field)
+
+ execute_service
+
+ new_note = to_noteable.notes.first
+ expect(new_note.note_html).to eq(notes.first.note_html)
+ end
end
context 'notes with mentions' do
@@ -119,6 +127,13 @@ RSpec.describe Notes::CopyService do
expect(new_note.author).to eq(note.author)
end
end
+
+ it 'does not copy rendered markdown from note_html' do
+ execute_service
+
+ new_note = to_noteable.notes.first
+ expect(new_note.note_html).not_to eq(note.note_html)
+ end
end
context 'notes with upload' do
@@ -137,6 +152,13 @@ RSpec.describe Notes::CopyService do
expect(note.note_html).not_to eq(new_note.note_html)
end
end
+
+ it 'does not copy rendered markdown from note_html' do
+ execute_service
+
+ new_note = to_noteable.notes.first
+ expect(new_note.note_html).not_to eq(note.note_html)
+ end
end
context 'discussion notes' do
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index c72a9465f20..53b75a3c991 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -359,7 +359,7 @@ RSpec.describe Notes::CreateService do
issuable.reload.update!(title: "title")
},
expectation: ->(issuable, can_use_quick_action) {
- expect(issuable.work_in_progress?).to eq(can_use_quick_action)
+ expect(issuable.draft?).to eq(can_use_quick_action)
}
),
# Remove draft status
@@ -369,7 +369,7 @@ RSpec.describe Notes::CreateService do
issuable.reload.update!(title: "Draft: title")
},
expectation: ->(noteable, can_use_quick_action) {
- expect(noteable.work_in_progress?).not_to eq(can_use_quick_action)
+ expect(noteable.draft?).not_to eq(can_use_quick_action)
}
)
]
diff --git a/spec/services/notification_recipients/build_service_spec.rb b/spec/services/notification_recipients/build_service_spec.rb
index ff54d6ccd2f..899d23ec641 100644
--- a/spec/services/notification_recipients/build_service_spec.rb
+++ b/spec/services/notification_recipients/build_service_spec.rb
@@ -14,6 +14,9 @@ RSpec.describe NotificationRecipients::BuildService do
shared_examples 'no N+1 queries' do
it 'avoids N+1 queries', :request_store do
+ # existing N+1 due to multiple users having to be looked up in the project_authorizations table
+ threshold = project.private? ? 1 : 0
+
create_user
service.build_new_note_recipients(note)
@@ -24,7 +27,7 @@ RSpec.describe NotificationRecipients::BuildService do
create_user
- expect { service.build_new_note_recipients(note) }.not_to exceed_query_limit(control_count)
+ expect { service.build_new_note_recipients(note) }.not_to exceed_query_limit(control_count).with_threshold(threshold)
end
end
@@ -66,6 +69,9 @@ RSpec.describe NotificationRecipients::BuildService do
shared_examples 'no N+1 queries' do
it 'avoids N+1 queries', :request_store do
+ # existing N+1 due to multiple users having to be looked up in the project_authorizations table
+ threshold = project.private? ? 1 : 0
+
create_user
service.build_new_review_recipients(review)
@@ -76,7 +82,7 @@ RSpec.describe NotificationRecipients::BuildService do
create_user
- expect { service.build_new_review_recipients(review) }.not_to exceed_query_limit(control_count)
+ expect { service.build_new_review_recipients(review) }.not_to exceed_query_limit(control_count).with_threshold(threshold)
end
end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 743a04eabe6..032f35cfc29 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -975,10 +975,17 @@ RSpec.describe NotificationService, :mailer do
end
describe '#send_new_release_notifications', :deliver_mails_inline do
- let(:release) { create(:release, author: current_user) }
+ let(:release) { create(:release, project: project, author: current_user) }
let(:object) { release }
let(:action) { notification.send_new_release_notifications(release) }
+ before_all do
+ build_team(project)
+
+ update_custom_notification(:new_release, @u_guest_custom, resource: project)
+ update_custom_notification(:new_release, @u_custom_global)
+ end
+
context 'when release author is blocked' do
let(:current_user) { create(:user, :blocked) }
@@ -994,19 +1001,15 @@ RSpec.describe NotificationService, :mailer do
context 'when recipients for a new release exist' do
let(:current_user) { create(:user) }
- it 'calls new_release_email for each relevant recipient' do
- user_1 = create(:user)
- user_2 = create(:user)
- user_3 = create(:user)
- recipient_1 = NotificationRecipient.new(user_1, :custom, custom_action: :new_release)
- recipient_2 = NotificationRecipient.new(user_2, :custom, custom_action: :new_release)
- allow(NotificationRecipients::BuildService).to receive(:build_new_release_recipients).and_return([recipient_1, recipient_2])
-
+ it 'notifies the expected users' do
notification.send_new_release_notifications(release)
- should_email(user_1)
- should_email(user_2)
- should_not_email(user_3)
+ should_only_email(
+ @u_watcher,
+ @u_guest_watcher,
+ @u_custom_global,
+ @u_guest_custom
+ )
end
end
end
diff --git a/spec/services/packages/cleanup/update_policy_service_spec.rb b/spec/services/packages/cleanup/update_policy_service_spec.rb
new file mode 100644
index 00000000000..a11fbb766f5
--- /dev/null
+++ b/spec/services/packages/cleanup/update_policy_service_spec.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Cleanup::UpdatePolicyService do
+ using RSpec::Parameterized::TableSyntax
+
+ let_it_be_with_reload(:project) { create(:project) }
+ let_it_be(:current_user) { create(:user) }
+
+ let(:params) { { keep_n_duplicated_package_files: 50 } }
+
+ describe '#execute' do
+ subject { described_class.new(project: project, current_user: current_user, params: params).execute }
+
+ shared_examples 'creating the policy' do
+ it 'creates a new one' do
+ expect { subject }.to change { ::Packages::Cleanup::Policy.count }.from(0).to(1)
+
+ expect(subject.payload[:packages_cleanup_policy]).to be_present
+ expect(subject.success?).to be_truthy
+ expect(project.packages_cleanup_policy).to be_persisted
+ expect(project.packages_cleanup_policy.keep_n_duplicated_package_files).to eq('50')
+ end
+
+ context 'with invalid parameters' do
+ let(:params) { { keep_n_duplicated_package_files: 100 } }
+
+ it 'does not create one' do
+ expect { subject }.not_to change { ::Packages::Cleanup::Policy.count }
+
+ expect(subject.status).to eq(:error)
+ expect(subject.message).to eq('Keep n duplicated package files is invalid')
+ end
+ end
+ end
+
+ shared_examples 'updating the policy' do
+ it 'updates the existing one' do
+ expect { subject }.not_to change { ::Packages::Cleanup::Policy.count }
+
+ expect(subject.payload[:packages_cleanup_policy]).to be_present
+ expect(subject.success?).to be_truthy
+ expect(project.packages_cleanup_policy.keep_n_duplicated_package_files).to eq('50')
+ end
+
+ context 'with invalid parameters' do
+ let(:params) { { keep_n_duplicated_package_files: 100 } }
+
+ it 'does not update one' do
+ expect { subject }.not_to change { policy.keep_n_duplicated_package_files }
+
+ expect(subject.status).to eq(:error)
+ expect(subject.message).to eq('Keep n duplicated package files is invalid')
+ end
+ end
+ end
+
+ shared_examples 'denying access' do
+ it 'returns an error' do
+ subject
+
+ expect(subject.message).to eq('Access denied')
+ expect(subject.status).to eq(:error)
+ end
+ end
+
+ context 'with existing container expiration policy' do
+ let_it_be(:policy) { create(:packages_cleanup_policy, project: project) }
+
+ where(:user_role, :shared_examples_name) do
+ :maintainer | 'updating the policy'
+ :developer | 'denying access'
+ :reporter | 'denying access'
+ :guest | 'denying access'
+ :anonymous | 'denying access'
+ end
+
+ with_them do
+ before do
+ project.send("add_#{user_role}", current_user) unless user_role == :anonymous
+ end
+
+ it_behaves_like params[:shared_examples_name]
+ end
+ end
+
+ context 'without existing container expiration policy' do
+ where(:user_role, :shared_examples_name) do
+ :maintainer | 'creating the policy'
+ :developer | 'denying access'
+ :reporter | 'denying access'
+ :guest | 'denying access'
+ :anonymous | 'denying access'
+ end
+
+ with_them do
+ before do
+ project.send("add_#{user_role}", current_user) unless user_role == :anonymous
+ end
+
+ it_behaves_like params[:shared_examples_name]
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/go/create_package_service_spec.rb b/spec/services/packages/go/create_package_service_spec.rb
index 5c5fec0aa3a..4ca1119fbaa 100644
--- a/spec/services/packages/go/create_package_service_spec.rb
+++ b/spec/services/packages/go/create_package_service_spec.rb
@@ -35,6 +35,22 @@ RSpec.describe Packages::Go::CreatePackageService do
expect(file.file_sha1).not_to be_nil
expect(file.file_sha256).not_to be_nil
end
+
+ context 'with FIPS mode', :fips_mode do
+ it 'does not generate file_md5' do
+ file_name = "#{version.name}.#{type}"
+ expect(subject.package_files.map { |f| f.file_name }).to include(file_name)
+
+ file = subject.package_files.with_file_name(file_name).first
+ expect(file).not_to be_nil
+ expect(file.file).not_to be_nil
+ expect(file.size).to eq(file.file.size)
+ expect(file.file_name).to eq(file_name)
+ expect(file.file_md5).to be_nil
+ expect(file.file_sha1).not_to be_nil
+ expect(file.file_sha256).not_to be_nil
+ end
+ end
end
describe '#execute' do
diff --git a/spec/services/packages/maven/metadata/append_package_file_service_spec.rb b/spec/services/packages/maven/metadata/append_package_file_service_spec.rb
index c406ab93630..f3a90d31158 100644
--- a/spec/services/packages/maven/metadata/append_package_file_service_spec.rb
+++ b/spec/services/packages/maven/metadata/append_package_file_service_spec.rb
@@ -22,6 +22,18 @@ RSpec.describe ::Packages::Maven::Metadata::AppendPackageFileService do
expect_file("#{metadata_file_name}.sha256")
expect_file("#{metadata_file_name}.sha512")
end
+
+ context 'with FIPS mode', :fips_mode do
+ it 'does not generate file_md5' do
+ expect { subject }.to change { package.package_files.count }.by(4)
+ expect(subject).to be_success
+
+ expect_file(metadata_file_name, with_content: content, with_content_type: 'application/xml', fips: true)
+ expect_file("#{metadata_file_name}.sha1", fips: true)
+ expect_file("#{metadata_file_name}.sha256", fips: true)
+ expect_file("#{metadata_file_name}.sha512", fips: true)
+ end
+ end
end
context 'with nil content' do
@@ -36,17 +48,22 @@ RSpec.describe ::Packages::Maven::Metadata::AppendPackageFileService do
it_behaves_like 'returning an error service response', message: 'package is not set'
end
- def expect_file(file_name, with_content: nil, with_content_type: '')
+ def expect_file(file_name, fips: false, with_content: nil, with_content_type: '')
package_file = package.package_files.recent.with_file_name(file_name).first
expect(package_file.file).to be_present
expect(package_file.file_name).to eq(file_name)
expect(package_file.size).to be > 0
- expect(package_file.file_md5).to be_present
expect(package_file.file_sha1).to be_present
expect(package_file.file_sha256).to be_present
expect(package_file.file.content_type).to eq(with_content_type)
+ if fips
+ expect(package_file.file_md5).not_to be_present
+ else
+ expect(package_file.file_md5).to be_present
+ end
+
if with_content
expect(package_file.file.read).to eq(with_content)
end
diff --git a/spec/services/packages/rubygems/create_gemspec_service_spec.rb b/spec/services/packages/rubygems/create_gemspec_service_spec.rb
index 198e978a47e..839fb4d955a 100644
--- a/spec/services/packages/rubygems/create_gemspec_service_spec.rb
+++ b/spec/services/packages/rubygems/create_gemspec_service_spec.rb
@@ -24,5 +24,18 @@ RSpec.describe Packages::Rubygems::CreateGemspecService do
expect(gemspec_file.file_sha1).not_to be_nil
expect(gemspec_file.file_sha256).not_to be_nil
end
+
+ context 'with FIPS mode', :fips_mode do
+ it 'does not generate file_md5' do
+ expect { subject }.to change { package.package_files.count }.by(1)
+
+ gemspec_file = package.package_files.find_by(file_name: "#{gemspec.name}.gemspec")
+ expect(gemspec_file.file).not_to be_nil
+ expect(gemspec_file.size).not_to be_nil
+ expect(gemspec_file.file_md5).to be_nil
+ expect(gemspec_file.file_sha1).not_to be_nil
+ expect(gemspec_file.file_sha256).not_to be_nil
+ end
+ end
end
end
diff --git a/spec/services/pages/delete_service_spec.rb b/spec/services/pages/delete_service_spec.rb
index e02e8e72e0b..0c0b2c0431b 100644
--- a/spec/services/pages/delete_service_spec.rb
+++ b/spec/services/pages/delete_service_spec.rb
@@ -43,4 +43,10 @@ RSpec.describe Pages::DeleteService do
service.execute
end.to change { PagesDeployment.count }.by(-1)
end
+
+ it 'publishes a ProjectDeleted event with project id and namespace id' do
+ expected_data = { project_id: project.id, namespace_id: project.namespace_id }
+
+ expect { service.execute }.to publish_event(Pages::PageDeletedEvent).with(expected_data)
+ end
end
diff --git a/spec/services/pages_domains/create_acme_order_service_spec.rb b/spec/services/pages_domains/create_acme_order_service_spec.rb
index 35b2cc56973..b882c253613 100644
--- a/spec/services/pages_domains/create_acme_order_service_spec.rb
+++ b/spec/services/pages_domains/create_acme_order_service_spec.rb
@@ -38,13 +38,21 @@ RSpec.describe PagesDomains::CreateAcmeOrderService do
expect(challenge).to have_received(:request_validation).ordered
end
- it 'generates and saves private key' do
+ it 'generates and saves private key: rsa' do
+ stub_feature_flags(pages_lets_encrypt_ecdsa: false)
service.execute
saved_order = PagesDomainAcmeOrder.last
expect { OpenSSL::PKey::RSA.new(saved_order.private_key) }.not_to raise_error
end
+ it 'generates and saves private key: ec' do
+ service.execute
+
+ saved_order = PagesDomainAcmeOrder.last
+ expect { OpenSSL::PKey::EC.new(saved_order.private_key) }.not_to raise_error
+ end
+
it 'properly saves order attributes' do
service.execute
diff --git a/spec/services/projects/after_rename_service_spec.rb b/spec/services/projects/after_rename_service_spec.rb
index a8db87e48d0..a9329f092fa 100644
--- a/spec/services/projects/after_rename_service_spec.rb
+++ b/spec/services/projects/after_rename_service_spec.rb
@@ -64,22 +64,11 @@ RSpec.describe Projects::AfterRenameService do
allow(project_storage).to receive(:rename_repo) { true }
end
- context 'when the project has pages deployed' do
- it 'schedules a move of the pages directory' do
- allow(project).to receive(:pages_deployed?).and_return(true)
-
- expect(PagesTransferWorker).to receive(:perform_async).with('rename_project', anything)
-
- service_execute
- end
- end
-
context 'when the project does not have pages deployed' do
it 'does nothing with the pages directory' do
allow(project).to receive(:pages_deployed?).and_return(false)
expect(PagesTransferWorker).not_to receive(:perform_async)
- expect(Gitlab::PagesTransfer).not_to receive(:new)
service_execute
end
@@ -172,29 +161,6 @@ RSpec.describe Projects::AfterRenameService do
end
end
- context 'gitlab pages' do
- context 'when the project has pages deployed' do
- it 'schedules a move of the pages directory' do
- allow(project).to receive(:pages_deployed?).and_return(true)
-
- expect(PagesTransferWorker).to receive(:perform_async).with('rename_project', anything)
-
- service_execute
- end
- end
-
- context 'when the project does not have pages deployed' do
- it 'does nothing with the pages directory' do
- allow(project).to receive(:pages_deployed?).and_return(false)
-
- expect(PagesTransferWorker).not_to receive(:perform_async)
- expect(Gitlab::PagesTransfer).not_to receive(:new)
-
- service_execute
- end
- end
- end
-
context 'attachments' do
let(:uploader) { create(:upload, :issuable_upload, :with_file, model: project) }
let(:file_uploader) { build(:file_uploader, project: project) }
diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb
index ed043bacf31..54a21d2f22b 100644
--- a/spec/services/projects/autocomplete_service_spec.rb
+++ b/spec/services/projects/autocomplete_service_spec.rb
@@ -158,7 +158,6 @@ RSpec.describe Projects::AutocompleteService do
subject { described_class.new(project, user).contacts.as_json }
before do
- stub_feature_flags(customer_relations: true)
group.add_developer(user)
end
diff --git a/spec/services/projects/destroy_rollback_service_spec.rb b/spec/services/projects/destroy_rollback_service_spec.rb
deleted file mode 100644
index 3eaacc8c1e7..00000000000
--- a/spec/services/projects/destroy_rollback_service_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Projects::DestroyRollbackService do
- let_it_be(:user) { create(:user) }
- let_it_be(:project) { create(:project, :repository, namespace: user.namespace) }
-
- let(:repository) { project.repository }
- let(:repository_storage) { project.repository_storage }
-
- subject { described_class.new(project, user, {}).execute }
-
- describe '#execute' do
- let(:path) { repository.disk_path + '.git' }
- let(:removal_path) { "#{repository.disk_path}+#{project.id}#{Repositories::DestroyService::DELETED_FLAG}.git" }
-
- before do
- aggregate_failures do
- expect(TestEnv.storage_dir_exists?(repository_storage, path)).to be_truthy
- expect(TestEnv.storage_dir_exists?(repository_storage, removal_path)).to be_falsey
- end
-
- # Don't run sidekiq to check if renamed repository exists
- Sidekiq::Testing.fake! { destroy_project(project, user, {}) }
-
- aggregate_failures do
- expect(TestEnv.storage_dir_exists?(repository_storage, path)).to be_falsey
- expect(TestEnv.storage_dir_exists?(repository_storage, removal_path)).to be_truthy
- end
- end
-
- it 'restores the repositories' do
- Sidekiq::Testing.fake! { subject }
-
- aggregate_failures do
- expect(TestEnv.storage_dir_exists?(repository_storage, path)).to be_truthy
- expect(TestEnv.storage_dir_exists?(repository_storage, removal_path)).to be_falsey
- end
- end
- end
-
- def destroy_project(project, user, params = {})
- Projects::DestroyService.new(project, user, params).execute
- end
-end
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index cd923720631..c00438199fd 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -9,7 +9,6 @@ RSpec.describe Projects::DestroyService, :aggregate_failures, :event_store_publi
let!(:project) { create(:project, :repository, namespace: user.namespace) }
let(:path) { project.repository.disk_path }
- let(:remove_path) { removal_path(path) }
let(:async) { false } # execute or async_execute
before do
@@ -24,7 +23,6 @@ RSpec.describe Projects::DestroyService, :aggregate_failures, :event_store_publi
expect(Project.unscoped.all).not_to include(project)
expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_falsey
- expect(project.gitlab_shell.repository_exists?(project.repository_storage, remove_path + '.git')).to be_falsey
end
it 'publishes a ProjectDeleted event with project id and namespace id' do
@@ -73,6 +71,18 @@ RSpec.describe Projects::DestroyService, :aggregate_failures, :event_store_publi
end
it_behaves_like 'deleting the project'
+
+ context 'when project is undergoing refresh' do
+ let!(:build_artifacts_size_refresh) { create(:project_build_artifacts_size_refresh, :pending, project: project) }
+
+ it 'does not log about artifact deletion but continues to delete artifacts' do
+ expect(Gitlab::ProjectStatsRefreshConflictsLogger).not_to receive(:warn_artifact_deletion_during_stats_refresh)
+
+ expect { destroy_project(project, user, {}) }
+ .to change { Ci::JobArtifact.count }.by(-2)
+ .and change { Projects::BuildArtifactsSizeRefresh.count }.by(-1)
+ end
+ end
end
end
@@ -192,10 +202,6 @@ RSpec.describe Projects::DestroyService, :aggregate_failures, :event_store_publi
it do
expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_falsey
end
-
- it do
- expect(project.gitlab_shell.repository_exists?(project.repository_storage, remove_path + '.git')).to be_truthy
- end
end
context 'when flushing caches fail due to Git errors' do
@@ -392,36 +398,13 @@ RSpec.describe Projects::DestroyService, :aggregate_failures, :event_store_publi
end
end
- context 'repository +deleted path removal' do
- context 'regular phase' do
- it 'schedules +deleted removal of existing repos' do
- service = described_class.new(project, user, {})
- allow(service).to receive(:schedule_stale_repos_removal)
-
- expect(Repositories::ShellDestroyService).to receive(:new).and_call_original
- expect(GitlabShellWorker).to receive(:perform_in)
- .with(5.minutes, :remove_repository, project.repository_storage, removal_path(project.disk_path))
-
- service.execute
+ context 'repository removal' do
+ it 'removal of existing repos' do
+ expect_next_instances_of(Repositories::DestroyService, 2) do |instance|
+ expect(instance).to receive(:execute).and_return(status: :success)
end
- end
-
- context 'stale cleanup' do
- let(:async) { true }
-
- it 'schedules +deleted wiki and repo removal' do
- allow(ProjectDestroyWorker).to receive(:perform_async)
-
- expect(Repositories::ShellDestroyService).to receive(:new).with(project.repository).and_call_original
- expect(GitlabShellWorker).to receive(:perform_in)
- .with(10.minutes, :remove_repository, project.repository_storage, removal_path(project.disk_path))
-
- expect(Repositories::ShellDestroyService).to receive(:new).with(project.wiki.repository).and_call_original
- expect(GitlabShellWorker).to receive(:perform_in)
- .with(10.minutes, :remove_repository, project.repository_storage, removal_path(project.wiki.disk_path))
- destroy_project(project, user, {})
- end
+ described_class.new(project, user, {}).execute
end
end
@@ -480,7 +463,6 @@ RSpec.describe Projects::DestroyService, :aggregate_failures, :event_store_publi
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
@@ -541,7 +523,6 @@ RSpec.describe Projects::DestroyService, :aggregate_failures, :event_store_publi
expect(Project.unscoped.all).not_to include(project)
expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_falsey
- expect(project.gitlab_shell.repository_exists?(project.repository_storage, remove_path + '.git')).to be_falsey
expect(project.all_pipelines).to be_empty
expect(project.builds).to be_empty
end
@@ -550,8 +531,4 @@ RSpec.describe Projects::DestroyService, :aggregate_failures, :event_store_publi
def destroy_project(project, user, params = {})
described_class.new(project, user, params).public_send(async ? :async_execute : :execute)
end
-
- def removal_path(path)
- "#{path}+#{project.id}#{Repositories::DestroyService::DELETED_FLAG}"
- end
end
diff --git a/spec/services/projects/refresh_build_artifacts_size_statistics_service_spec.rb b/spec/services/projects/refresh_build_artifacts_size_statistics_service_spec.rb
index 41487e9ea48..6a715312097 100644
--- a/spec/services/projects/refresh_build_artifacts_size_statistics_service_spec.rb
+++ b/spec/services/projects/refresh_build_artifacts_size_statistics_service_spec.rb
@@ -52,6 +52,12 @@ RSpec.describe Projects::RefreshBuildArtifactsSizeStatisticsService, :clean_gitl
expect { service.execute }.to change { refresh.reload.last_job_artifact_id.to_i }.to(artifact_3.id)
end
+ it 'updates the last_job_artifact_id to the ID of the last artifact from the project' do
+ expect { service.execute }
+ .to change { refresh.reload.last_job_artifact_id_on_refresh_start.to_i }
+ .to(project.job_artifacts.last.id)
+ end
+
it 'requeues the refresh job' do
service.execute
expect(refresh.reload).to be_pending
@@ -63,7 +69,8 @@ RSpec.describe Projects::RefreshBuildArtifactsSizeStatisticsService, :clean_gitl
:project_build_artifacts_size_refresh,
:pending,
project: project,
- last_job_artifact_id: artifact_3.id
+ last_job_artifact_id: artifact_3.id,
+ last_job_artifact_id_on_refresh_start: artifact_4.id
)
end
@@ -77,6 +84,10 @@ RSpec.describe Projects::RefreshBuildArtifactsSizeStatisticsService, :clean_gitl
expect(refresh.reload.last_job_artifact_id).to eq(artifact_3.id)
end
+ it 'keeps the last_job_artifact_id_on_refresh_start unchanged' do
+ expect(refresh.reload.last_job_artifact_id_on_refresh_start).to eq(artifact_4.id)
+ end
+
it 'keeps the state of the refresh record at running' do
expect(refresh.reload).to be_running
end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index e547ace1d9f..bebe80b710b 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -53,9 +53,6 @@ RSpec.describe Projects::TransferService do
allow_next_instance_of(Gitlab::UploadsTransfer) do |service|
allow(service).to receive(:move_project).and_return(true)
end
- allow_next_instance_of(Gitlab::PagesTransfer) do |service|
- allow(service).to receive(:move_project).and_return(true)
- end
group.add_owner(user)
end
@@ -725,15 +722,6 @@ RSpec.describe Projects::TransferService do
group.add_owner(user)
end
- it 'schedules a job when pages are deployed' do
- project.mark_pages_as_deployed
-
- expect(PagesTransferWorker).to receive(:perform_async)
- .with("move_project", [project.path, user.namespace.full_path, group.full_path])
-
- execute_transfer
- end
-
it 'does not schedule a job when no pages are deployed' do
expect(PagesTransferWorker).not_to receive(:perform_async)
diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb
index 777162b6196..cbbed82aa0b 100644
--- a/spec/services/projects/update_pages_service_spec.rb
+++ b/spec/services/projects/update_pages_service_spec.rb
@@ -205,6 +205,25 @@ RSpec.describe Projects::UpdatePagesService do
include_examples 'fails with outdated reference message'
end
end
+
+ context 'when uploaded deployment size is wrong' do
+ it 'raises an error' do
+ allow_next_instance_of(PagesDeployment) do |deployment|
+ allow(deployment)
+ .to receive(:size)
+ .and_return(file.size + 1)
+ end
+
+ expect do
+ expect(execute).not_to eq(:success)
+
+ expect(GenericCommitStatus.last.description).to eq("Error: The uploaded artifact size does not match the expected value.")
+ project.pages_metadatum.reload
+ expect(project.pages_metadatum).not_to be_deployed
+ expect(project.pages_metadatum.pages_deployment).to be_ni
+ end.to raise_error(Projects::UpdatePagesService::WrongUploadedDeploymentSizeError)
+ end
+ end
end
end
diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index f7a22b1b92f..f7ed6006099 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -122,7 +122,7 @@ RSpec.describe QuickActions::InterpretService do
inprogress # populate the label
_, updates, _ = service.execute(content, issuable)
- expect(updates).to eq(add_label_ids: [bug.id, inprogress.id])
+ expect(updates).to match(add_label_ids: contain_exactly(bug.id, inprogress.id))
end
it 'returns the label message' do
@@ -130,7 +130,10 @@ RSpec.describe QuickActions::InterpretService do
inprogress # populate the label
_, _, message = service.execute(content, issuable)
- expect(message).to eq("Added #{bug.to_reference(format: :name)} #{inprogress.to_reference(format: :name)} labels.")
+ # Compare message without making assumptions about ordering.
+ expect(message).to match %r{Added ~".*" ~".*" labels.}
+ expect(message).to include(bug.to_reference(format: :name))
+ expect(message).to include(inprogress.to_reference(format: :name))
end
end
@@ -318,32 +321,40 @@ RSpec.describe QuickActions::InterpretService do
end
shared_examples 'draft command' do
- it 'returns wip_event: "wip" if content contains /draft' do
+ it 'returns wip_event: "draft"' do
_, updates, _ = service.execute(content, issuable)
- expect(updates).to eq(wip_event: 'wip')
+ expect(updates).to eq(wip_event: 'draft')
end
- it 'returns the wip message' do
+ it 'returns the draft message' do
_, _, message = service.execute(content, issuable)
expect(message).to eq("Marked this #{issuable.to_ability_name.humanize(capitalize: false)} as a draft.")
end
end
- shared_examples 'undraft command' do
- it 'returns wip_event: "unwip" if content contains /draft' do
- issuable.update!(title: issuable.wip_title)
+ shared_examples 'draft/ready command no action' do
+ it 'returns the no action message if there is no change to the status' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("No change to this #{issuable.to_ability_name.humanize(capitalize: false)}'s draft status.")
+ end
+ end
+
+ shared_examples 'ready command' do
+ it 'returns wip_event: "ready"' do
+ issuable.update!(title: issuable.draft_title)
_, updates, _ = service.execute(content, issuable)
- expect(updates).to eq(wip_event: 'unwip')
+ expect(updates).to eq(wip_event: 'ready')
end
- it 'returns the unwip message' do
- issuable.update!(title: issuable.wip_title)
+ it 'returns the ready message' do
+ issuable.update!(title: issuable.draft_title)
_, _, message = service.execute(content, issuable)
- expect(message).to eq("Unmarked this #{issuable.to_ability_name.humanize(capitalize: false)} as a draft.")
+ expect(message).to eq("Marked this #{issuable.to_ability_name.humanize(capitalize: false)} as ready.")
end
end
@@ -1196,6 +1207,64 @@ RSpec.describe QuickActions::InterpretService do
let(:issuable) { merge_request }
end
+ context 'with a colon label' do
+ let(:bug) { create(:label, project: project, title: 'Category:Bug') }
+ let(:inprogress) { create(:label, project: project, title: 'status:in:progress') }
+
+ context 'when quoted' do
+ let(:content) { %(/label ~"#{inprogress.title}" ~"#{bug.title}" ~unknown) }
+
+ it_behaves_like 'label command' do
+ let(:issuable) { merge_request }
+ end
+
+ it_behaves_like 'label command' do
+ let(:issuable) { issue }
+ end
+ end
+
+ context 'when unquoted' do
+ let(:content) { %(/label ~#{inprogress.title} ~#{bug.title} ~unknown) }
+
+ it_behaves_like 'label command' do
+ let(:issuable) { merge_request }
+ end
+
+ it_behaves_like 'label command' do
+ let(:issuable) { issue }
+ end
+ end
+ end
+
+ context 'with a scoped label' do
+ let(:bug) { create(:label, :scoped, project: project) }
+ let(:inprogress) { create(:label, project: project, title: 'three::part::label') }
+
+ context 'when quoted' do
+ let(:content) { %(/label ~"#{inprogress.title}" ~"#{bug.title}" ~unknown) }
+
+ it_behaves_like 'label command' do
+ let(:issuable) { merge_request }
+ end
+
+ it_behaves_like 'label command' do
+ let(:issuable) { issue }
+ end
+ end
+
+ context 'when unquoted' do
+ let(:content) { %(/label ~#{inprogress.title} ~#{bug.title} ~unknown) }
+
+ it_behaves_like 'label command' do
+ let(:issuable) { merge_request }
+ end
+
+ it_behaves_like 'label command' do
+ let(:issuable) { issue }
+ end
+ end
+ end
+
it_behaves_like 'multiple label command' do
let(:content) { %(/label ~"#{inprogress.title}" \n/label ~#{bug.title}) }
let(:issuable) { issue }
@@ -1306,11 +1375,21 @@ RSpec.describe QuickActions::InterpretService do
let(:issuable) { merge_request }
end
- it_behaves_like 'undraft command' do
+ it_behaves_like 'ready command' do
let(:content) { '/draft' }
let(:issuable) { merge_request }
end
+ it_behaves_like 'draft/ready command no action' do
+ let(:content) { '/ready' }
+ let(:issuable) { merge_request }
+ end
+
+ it_behaves_like 'ready command' do
+ let(:content) { '/ready' }
+ let(:issuable) { merge_request }
+ end
+
it_behaves_like 'failed command', 'Could not apply remove_due_date command.' do
let(:content) { '/remove_due_date' }
let(:issuable) { merge_request }
@@ -2333,24 +2412,6 @@ RSpec.describe QuickActions::InterpretService do
create(:issue_customer_relations_contact, issue: issue, contact: existing_contact)
end
- context 'with feature flag disabled' do
- before do
- stub_feature_flags(customer_relations: false)
- end
-
- it 'add_contacts command does not add the contact' do
- _, updates, _ = add_command
-
- expect(updates).to be_empty
- end
-
- it 'remove_contacts command does not remove the contact' do
- _, updates, _ = remove_command
-
- expect(updates).to be_empty
- end
- end
-
it 'add_contacts command adds the contact' do
_, updates, message = add_command
@@ -2644,7 +2705,24 @@ RSpec.describe QuickActions::InterpretService do
it 'includes the new status' do
_, explanations = service.explain(content, merge_request)
- expect(explanations).to eq(['Marks this merge request as a draft.'])
+ expect(explanations).to match_array(['Marks this merge request as a draft.'])
+ end
+ end
+
+ describe 'ready command' do
+ let(:content) { '/ready' }
+
+ it 'includes the new status' do
+ merge_request.update!(title: merge_request.draft_title)
+ _, explanations = service.explain(content, merge_request)
+
+ expect(explanations).to match_array(['Marks this merge request as ready.'])
+ end
+
+ it 'includes the no change message when status unchanged' do
+ _, explanations = service.explain(content, merge_request)
+
+ expect(explanations).to match_array(["No change to this merge request's draft status."])
end
end
@@ -2805,12 +2883,6 @@ RSpec.describe QuickActions::InterpretService do
expect(explanations).to be_empty
end
-
- it '/remove_contacts is not available' do
- _, explanations = service.explain(remove_contacts, issue)
-
- expect(explanations).to be_empty
- end
end
context 'when group has contacts' do
@@ -2822,10 +2894,22 @@ RSpec.describe QuickActions::InterpretService do
expect(explanations).to contain_exactly("Add customer relation contact(s).")
end
- it '/remove_contacts is available' do
- _, explanations = service.explain(remove_contacts, issue)
+ context 'when issue has no contacts' do
+ it '/remove_contacts is not available' do
+ _, explanations = service.explain(remove_contacts, issue)
- expect(explanations).to contain_exactly("Remove customer relation contact(s).")
+ expect(explanations).to be_empty
+ end
+ end
+
+ context 'when issue has contacts' do
+ let!(:issue_contact) { create(:issue_customer_relations_contact, issue: issue, contact: contact) }
+
+ it '/remove_contacts is available' do
+ _, explanations = service.explain(remove_contacts, issue)
+
+ expect(explanations).to contain_exactly("Remove customer relation contact(s).")
+ end
end
end
end
diff --git a/spec/services/releases/create_service_spec.rb b/spec/services/releases/create_service_spec.rb
index d53fc968e2a..566d73a3b75 100644
--- a/spec/services/releases/create_service_spec.rb
+++ b/spec/services/releases/create_service_spec.rb
@@ -6,10 +6,11 @@ RSpec.describe Releases::CreateService do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:tag_name) { project.repository.tag_names.first }
+ let(:tag_message) { nil }
let(:tag_sha) { project.repository.find_tag(tag_name).dereferenced_target.sha }
let(:name) { 'Bionic Beaver' }
let(:description) { 'Awesome release!' }
- let(:params) { { tag: tag_name, name: name, description: description, ref: ref } }
+ let(:params) { { tag: tag_name, name: name, description: description, ref: ref, tag_message: tag_message } }
let(:ref) { nil }
let(:service) { described_class.new(project, user, params) }
@@ -68,6 +69,24 @@ RSpec.describe Releases::CreateService do
expect(result[:tag]).not_to be_nil
expect(result[:release]).not_to be_nil
end
+
+ context 'and tag_message is provided' do
+ let(:ref) { 'master' }
+ let(:tag_name) { 'foobar' }
+ let(:tag_message) { 'Annotated tag message' }
+
+ it_behaves_like 'a successful release creation'
+
+ it 'creates a tag if the tag does not exist' do
+ expect(project.repository.ref_exists?("refs/tags/#{tag_name}")).to be_falsey
+
+ result = service.execute
+ expect(result[:status]).to eq(:success)
+ expect(result[:tag]).not_to be_nil
+ expect(result[:release]).not_to be_nil
+ expect(project.repository.find_tag(tag_name).message).to eq(tag_message)
+ end
+ end
end
context 'there already exists a release on a tag' do
diff --git a/spec/services/repositories/changelog_service_spec.rb b/spec/services/repositories/changelog_service_spec.rb
index ddb8e7e1182..82546ae810b 100644
--- a/spec/services/repositories/changelog_service_spec.rb
+++ b/spec/services/repositories/changelog_service_spec.rb
@@ -78,7 +78,7 @@ RSpec.describe Repositories::ChangelogService do
recorder = ActiveRecord::QueryRecorder.new { service.execute(commit_to_changelog: commit_to_changelog) }
changelog = project.repository.blob_at('master', 'CHANGELOG.md')&.data
- expect(recorder.count).to eq(9)
+ expect(recorder.count).to eq(10)
expect(changelog).to include('Title 1', 'Title 2')
end
@@ -148,6 +148,52 @@ RSpec.describe Repositories::ChangelogService do
expect(changelog).to include('Title 1', 'Title 2')
end
end
+
+ it 'avoids N+1 queries', :request_store do
+ RequestStore.clear!
+
+ request = ->(to) do
+ described_class
+ .new(project, creator, version: '1.0.0', from: sha1, to: to)
+ .execute(commit_to_changelog: false)
+ end
+
+ control = ActiveRecord::QueryRecorder.new { request.call(sha2) }
+
+ RequestStore.clear!
+
+ expect { request.call(sha3) }.not_to exceed_query_limit(control.count)
+ end
+
+ context 'when one of commits does not exist' do
+ let(:service) { described_class.new(project, creator, version: '1.0.0', from: 'master', to: '54321') }
+
+ it 'raises an exception' do
+ expect { service.execute(commit_to_changelog: false) }.to raise_error(Gitlab::Changelog::Error)
+ end
+ end
+
+ context 'when commit range exceeds the limit' do
+ let(:service) { described_class.new(project, creator, version: '1.0.0', from: sha1) }
+
+ before do
+ stub_const("#{described_class.name}::COMMITS_LIMIT", 2)
+ end
+
+ it 'raises an exception' do
+ expect { service.execute(commit_to_changelog: false) }.to raise_error(Gitlab::Changelog::Error)
+ end
+
+ context 'when feature flag is off' do
+ before do
+ stub_feature_flags(changelog_commits_limitation: false)
+ end
+
+ it 'returns the changelog' do
+ expect(service.execute(commit_to_changelog: false)).to include('Title 1', 'Title 2', 'Title 3')
+ end
+ end
+ end
end
describe '#start_of_commit_range' do
diff --git a/spec/services/repositories/destroy_rollback_service_spec.rb b/spec/services/repositories/destroy_rollback_service_spec.rb
deleted file mode 100644
index a52dff62760..00000000000
--- a/spec/services/repositories/destroy_rollback_service_spec.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Repositories::DestroyRollbackService do
- let_it_be(:user) { create(:user) }
-
- let!(:project) { create(:project, :repository, namespace: user.namespace) }
- let(:repository) { project.repository }
- let(:path) { repository.disk_path }
- let(:remove_path) { "#{path}+#{project.id}#{described_class::DELETED_FLAG}" }
-
- subject { described_class.new(repository).execute }
-
- before do
- # Dont run sidekiq to check if renamed repository exists
- Sidekiq::Testing.fake! { destroy_project(project, user) }
- end
-
- it 'moves the repository from the +deleted folder' do
- expect(project.gitlab_shell.repository_exists?(project.repository_storage, remove_path + '.git')).to be_truthy
- expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_falsey
-
- subject
-
- expect(project.gitlab_shell.repository_exists?(project.repository_storage, remove_path + '.git')).to be_falsey
- expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_truthy
- end
-
- it 'logs the successful action' do
- expect(Gitlab::AppLogger).to receive(:info)
-
- subject
- end
-
- it 'flushes the repository cache' do
- expect(repository).to receive(:before_delete)
-
- subject
- end
-
- it 'returns success and does not perform any action if repository path does not exist' do
- expect(repository).to receive(:disk_path).and_return('foo')
- expect(repository).not_to receive(:before_delete)
-
- expect(subject[:status]).to eq :success
- end
-
- it 'gracefully handles exception if the repository does not exist on disk' do
- expect(repository).to receive(:before_delete).and_raise(Gitlab::Git::Repository::NoRepository)
- expect(subject[:status]).to eq :success
- end
-
- context 'when move operation cannot be performed' do
- let(:service) { described_class.new(repository) }
-
- before do
- expect(service).to receive(:mv_repository).and_return(false)
- end
-
- it 'returns error' do
- result = service.execute
-
- expect(result[:status]).to eq :error
- end
-
- it 'logs the error' do
- expect(Gitlab::AppLogger).to receive(:error)
-
- service.execute
- end
-
- context 'when repository does not exist' do
- it 'returns success' do
- allow(service).to receive(:repo_exists?).and_return(true, false)
-
- expect(service.execute[:status]).to eq :success
- end
- end
- end
-
- def destroy_project(project, user)
- Projects::DestroyService.new(project, user, {}).execute
- end
-end
diff --git a/spec/services/repositories/destroy_service_spec.rb b/spec/services/repositories/destroy_service_spec.rb
index 3766467d708..565a18d501a 100644
--- a/spec/services/repositories/destroy_service_spec.rb
+++ b/spec/services/repositories/destroy_service_spec.rb
@@ -8,31 +8,19 @@ RSpec.describe Repositories::DestroyService do
let!(:project) { create(:project, :repository, namespace: user.namespace) }
let(:repository) { project.repository }
let(:path) { repository.disk_path }
- let(:remove_path) { "#{path}+#{project.id}#{described_class::DELETED_FLAG}" }
subject { described_class.new(repository).execute }
- it 'moves the repository to a +deleted folder' do
+ it 'removes the repository' do
expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_truthy
- expect(project.gitlab_shell.repository_exists?(project.repository_storage, remove_path + '.git')).to be_falsey
subject
- expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_falsey
- expect(project.gitlab_shell.repository_exists?(project.repository_storage, remove_path + '.git')).to be_truthy
- end
-
- it 'schedules the repository deletion' do
- subject
-
- expect(Repositories::ShellDestroyService).to receive(:new).with(repository).and_call_original
-
- expect(GitlabShellWorker).to receive(:perform_in)
- .with(Repositories::ShellDestroyService::REPO_REMOVAL_DELAY, :remove_repository, project.repository_storage, remove_path)
-
- # Because GitlabShellWorker is inside a run_after_commit callback we need to
+ # Because the removal happens inside a run_after_commit callback we need to
# trigger the callback
project.touch
+
+ expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_falsey
end
context 'on a read-only instance' do
@@ -41,22 +29,12 @@ RSpec.describe Repositories::DestroyService do
end
it 'schedules the repository deletion' do
- expect(Repositories::ShellDestroyService).to receive(:new).with(repository).and_call_original
-
- expect(GitlabShellWorker).to receive(:perform_in)
- .with(Repositories::ShellDestroyService::REPO_REMOVAL_DELAY, :remove_repository, project.repository_storage, remove_path)
+ expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_truthy
subject
- end
- end
-
- it 'removes the repository', :sidekiq_inline do
- subject
- project.touch
-
- expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_falsey
- expect(project.gitlab_shell.repository_exists?(project.repository_storage, remove_path + '.git')).to be_falsey
+ expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_falsey
+ end
end
it 'flushes the repository cache' do
@@ -77,48 +55,20 @@ RSpec.describe Repositories::DestroyService do
expect(subject[:status]).to eq :success
end
- context 'when move operation cannot be performed' do
- let(:service) { described_class.new(repository) }
-
- before do
- expect(service).to receive(:mv_repository).and_return(false)
- end
-
- it 'returns error' do
- expect(service.execute[:status]).to eq :error
- end
-
- it 'logs the error' do
- expect(Gitlab::AppLogger).to receive(:error)
-
- service.execute
- end
-
- context 'when repository does not exist' do
- it 'returns success' do
- allow(service).to receive(:repo_exists?).and_return(true, false)
-
- expect(Repositories::ShellDestroyService).not_to receive(:new)
- expect(service.execute[:status]).to eq :success
- end
- end
- end
-
context 'with a project wiki repository' do
let(:project) { create(:project, :wiki_repo) }
let(:repository) { project.wiki.repository }
it 'schedules the repository deletion' do
- subject
-
- expect(Repositories::ShellDestroyService).to receive(:new).with(repository).and_call_original
+ expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_truthy
- expect(GitlabShellWorker).to receive(:perform_in)
- .with(Repositories::ShellDestroyService::REPO_REMOVAL_DELAY, :remove_repository, project.repository_storage, remove_path)
+ subject
- # Because GitlabShellWorker is inside a run_after_commit callback we need to
+ # Because the removal happens inside a run_after_commit callback we need to
# trigger the callback
project.touch
+
+ expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_falsey
end
end
end
diff --git a/spec/services/repositories/shell_destroy_service_spec.rb b/spec/services/repositories/shell_destroy_service_spec.rb
deleted file mode 100644
index 65168a1784a..00000000000
--- a/spec/services/repositories/shell_destroy_service_spec.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Repositories::ShellDestroyService do
- let_it_be(:user) { create(:user) }
-
- let!(:project) { create(:project, :repository, namespace: user.namespace) }
- let(:path) { project.repository.disk_path }
- let(:remove_path) { "#{path}+#{project.id}#{described_class::DELETED_FLAG}" }
-
- it 'returns success if the repository is nil' do
- expect(GitlabShellWorker).not_to receive(:perform_in)
-
- result = described_class.new(nil).execute
-
- expect(result[:status]).to eq :success
- end
-
- it 'schedules the repository deletion' do
- expect(GitlabShellWorker).to receive(:perform_in)
- .with(described_class::REPO_REMOVAL_DELAY, :remove_repository, project.repository_storage, remove_path)
-
- described_class.new(project.repository).execute
- end
-end
diff --git a/spec/services/resource_access_tokens/create_service_spec.rb b/spec/services/resource_access_tokens/create_service_spec.rb
index 5a88929334b..127948549b0 100644
--- a/spec/services/resource_access_tokens/create_service_spec.rb
+++ b/spec/services/resource_access_tokens/create_service_spec.rb
@@ -268,10 +268,36 @@ RSpec.describe ResourceAccessTokens::CreateService do
end
it_behaves_like 'allows creation of bot with valid params'
+
+ context 'when user specifies an access level of OWNER for the bot' do
+ let_it_be(:params) { { access_level: Gitlab::Access::OWNER } }
+
+ context 'when the executor is a MAINTAINER' do
+ it 'does not add the bot user with the specified access level in the resource' do
+ response = subject
+
+ expect(response.error?).to be true
+ expect(response.errors).to include('Could not provision owner access to project access token')
+ end
+ end
+
+ context 'when the executor is an OWNER' do
+ let_it_be(:user) { project.first_owner }
+
+ it 'adds the bot user with the specified access level in the resource' do
+ response = subject
+
+ access_token = response.payload[:access_token]
+ bot_user = access_token.user
+
+ expect(resource.members.owners.map(&:user_id)).to include(bot_user.id)
+ end
+ end
+ end
end
end
- context 'when resource is a project' do
+ context 'when resource is a group' do
let_it_be(:resource_type) { 'group' }
let_it_be(:resource) { group }
@@ -283,6 +309,18 @@ RSpec.describe ResourceAccessTokens::CreateService do
end
it_behaves_like 'allows creation of bot with valid params'
+
+ context 'when user specifies an access level of OWNER for the bot' do
+ let_it_be(:params) { { access_level: Gitlab::Access::OWNER } }
+
+ it 'adds the bot user with the specified access level in the resource' do
+ response = subject
+ access_token = response.payload[:access_token]
+ bot_user = access_token.user
+
+ expect(resource.members.owners.map(&:user_id)).to include(bot_user.id)
+ end
+ end
end
end
end
diff --git a/spec/services/service_response_spec.rb b/spec/services/service_response_spec.rb
index 082ee4ddc67..3ede90fbc44 100644
--- a/spec/services/service_response_spec.rb
+++ b/spec/services/service_response_spec.rb
@@ -2,7 +2,10 @@
require 'fast_spec_helper'
+require 're2'
+
require_relative '../../app/services/service_response'
+require_relative '../../lib/gitlab/error_tracking'
RSpec.describe ServiceResponse do
describe '.success' do
@@ -94,4 +97,76 @@ RSpec.describe ServiceResponse do
expect(described_class.error(message: 'error message').errors).to eq(['error message'])
end
end
+
+ describe '#track_and_raise_exception' do
+ context 'when successful' do
+ let(:response) { described_class.success }
+
+ it 'returns self' do
+ expect(response.track_and_raise_exception).to be response
+ end
+ end
+
+ context 'when an error' do
+ let(:response) { described_class.error(message: 'bang') }
+
+ it 'tracks and raises' do
+ expect(::Gitlab::ErrorTracking).to receive(:track_and_raise_exception)
+ .with(StandardError.new('bang'), {})
+
+ response.track_and_raise_exception
+ end
+
+ it 'allows specification of error class' do
+ error = Class.new(StandardError)
+ expect(::Gitlab::ErrorTracking).to receive(:track_and_raise_exception)
+ .with(error.new('bang'), {})
+
+ response.track_and_raise_exception(as: error)
+ end
+
+ it 'allows extra data for tracking' do
+ expect(::Gitlab::ErrorTracking).to receive(:track_and_raise_exception)
+ .with(StandardError.new('bang'), { foo: 1, bar: 2 })
+
+ response.track_and_raise_exception(foo: 1, bar: 2)
+ end
+ end
+ end
+
+ describe '#track_exception' do
+ context 'when successful' do
+ let(:response) { described_class.success }
+
+ it 'returns self' do
+ expect(response.track_exception).to be response
+ end
+ end
+
+ context 'when an error' do
+ let(:response) { described_class.error(message: 'bang') }
+
+ it 'tracks' do
+ expect(::Gitlab::ErrorTracking).to receive(:track_exception)
+ .with(StandardError.new('bang'), {})
+
+ expect(response.track_exception).to be response
+ end
+
+ it 'allows specification of error class' do
+ error = Class.new(StandardError)
+ expect(::Gitlab::ErrorTracking).to receive(:track_exception)
+ .with(error.new('bang'), {})
+
+ expect(response.track_exception(as: error)).to be response
+ end
+
+ it 'allows extra data for tracking' do
+ expect(::Gitlab::ErrorTracking).to receive(:track_exception)
+ .with(StandardError.new('bang'), { foo: 1, bar: 2 })
+
+ expect(response.track_exception(foo: 1, bar: 2)).to be response
+ end
+ end
+ end
end
diff --git a/spec/services/snippets/bulk_destroy_service_spec.rb b/spec/services/snippets/bulk_destroy_service_spec.rb
index 2f399d10188..2d2bdd116d1 100644
--- a/spec/services/snippets/bulk_destroy_service_spec.rb
+++ b/spec/services/snippets/bulk_destroy_service_spec.rb
@@ -22,8 +22,8 @@ RSpec.describe Snippets::BulkDestroyService do
it 'deletes the snippets in bulk' do
response = nil
- expect(Repositories::ShellDestroyService).to receive(:new).with(personal_snippet.repository).and_call_original
- expect(Repositories::ShellDestroyService).to receive(:new).with(project_snippet.repository).and_call_original
+ expect(Repositories::DestroyService).to receive(:new).with(personal_snippet.repository).and_call_original
+ expect(Repositories::DestroyService).to receive(:new).with(project_snippet.repository).and_call_original
aggregate_failures do
expect do
@@ -94,12 +94,6 @@ RSpec.describe Snippets::BulkDestroyService do
it_behaves_like 'error is raised' do
let(:error_message) { 'Failed to delete snippet repositories.' }
end
-
- it 'tries to rollback the repository' do
- expect(subject).to receive(:attempt_rollback_repositories)
-
- subject.execute
- end
end
context 'when an error is raised deleting the records' do
@@ -110,22 +104,17 @@ RSpec.describe Snippets::BulkDestroyService do
it_behaves_like 'error is raised' do
let(:error_message) { 'Failed to remove snippets.' }
end
-
- it 'tries to rollback the repository' do
- expect(subject).to receive(:attempt_rollback_repositories)
-
- subject.execute
- end
end
context 'when snippet does not have a repository attached' do
let!(:snippet_without_repo) { create(:personal_snippet, author: user) }
- it 'does not schedule anything for the snippet without repository and return success' do
+ it 'returns success' do
response = nil
- expect(Repositories::ShellDestroyService).to receive(:new).with(personal_snippet.repository).and_call_original
- expect(Repositories::ShellDestroyService).to receive(:new).with(project_snippet.repository).and_call_original
+ expect(Repositories::DestroyService).to receive(:new).with(personal_snippet.repository).and_call_original
+ expect(Repositories::DestroyService).to receive(:new).with(project_snippet.repository).and_call_original
+ expect(Repositories::DestroyService).to receive(:new).with(snippet_without_repo.repository).and_call_original
expect do
response = subject.execute
@@ -136,38 +125,6 @@ RSpec.describe Snippets::BulkDestroyService do
end
end
- describe '#attempt_rollback_repositories' do
- before do
- Repositories::DestroyService.new(personal_snippet.repository).execute
- end
-
- it 'rollbacks the repository' do
- error_msg = personal_snippet.disk_path + "+#{personal_snippet.id}+deleted.git"
- expect(repository_exists?(personal_snippet, error_msg)).to be_truthy
-
- subject.__send__(:attempt_rollback_repositories)
-
- aggregate_failures do
- expect(repository_exists?(personal_snippet, error_msg)).to be_falsey
- expect(repository_exists?(personal_snippet)).to be_truthy
- end
- end
-
- context 'when an error is raised' do
- before do
- allow_next_instance_of(Repositories::DestroyRollbackService) do |instance|
- allow(instance).to receive(:execute).and_return({ status: :error })
- end
- end
-
- it 'logs the error' do
- expect(Gitlab::AppLogger).to receive(:error).with(/\ARepository .* in path .* could not be rolled back\z/).twice
-
- subject.__send__(:attempt_rollback_repositories)
- end
- end
- end
-
def repository_exists?(snippet, path = snippet.disk_path + ".git")
gitlab_shell.repository_exists?(snippet.snippet_repository.shard_name, path)
end
diff --git a/spec/services/snippets/destroy_service_spec.rb b/spec/services/snippets/destroy_service_spec.rb
index e53d00b9ca1..23765243dd6 100644
--- a/spec/services/snippets/destroy_service_spec.rb
+++ b/spec/services/snippets/destroy_service_spec.rb
@@ -41,7 +41,6 @@ RSpec.describe Snippets::DestroyService do
shared_examples 'deletes the snippet repository' do
it 'removes the snippet repository' do
expect(snippet.repository.exists?).to be_truthy
- expect(GitlabShellWorker).to receive(:perform_in)
expect_next_instance_of(Repositories::DestroyService) do |instance|
expect(instance).to receive(:execute).and_call_original
end
@@ -57,12 +56,6 @@ RSpec.describe Snippets::DestroyService do
end
it_behaves_like 'an unsuccessful destroy'
-
- it 'does not try to rollback repository' do
- expect(Repositories::DestroyRollbackService).not_to receive(:new)
-
- subject
- end
end
context 'when a destroy error is raised' do
@@ -71,12 +64,6 @@ RSpec.describe Snippets::DestroyService do
end
it_behaves_like 'an unsuccessful destroy'
-
- it 'attempts to rollback the repository' do
- expect(Repositories::DestroyRollbackService).to receive(:new).and_call_original
-
- subject
- end
end
context 'when repository is nil' do
diff --git a/spec/services/static_site_editor/config_service_spec.rb b/spec/services/static_site_editor/config_service_spec.rb
deleted file mode 100644
index fed373828a1..00000000000
--- a/spec/services/static_site_editor/config_service_spec.rb
+++ /dev/null
@@ -1,126 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe StaticSiteEditor::ConfigService do
- let_it_be(:project) { create(:project, :repository) }
- let_it_be(:user) { create(:user) }
-
- # params
- let(:ref) { 'master' }
- let(:path) { 'README.md' }
- let(:return_url) { double(:return_url) }
-
- # stub data
- let(:generated_data) { { generated: true } }
- let(:file_data) { { file: true } }
-
- describe '#execute' do
- subject(:execute) do
- described_class.new(
- container: project,
- current_user: user,
- params: {
- ref: ref,
- path: path,
- return_url: return_url
- }
- ).execute
- end
-
- context 'when insufficient permission' do
- it 'returns an error' do
- expect(execute).to be_error
- expect(execute.message).to eq('Insufficient permissions to read configuration')
- end
- end
-
- context 'for developer' do
- before do
- project.add_developer(user)
-
- allow_next_instance_of(Gitlab::StaticSiteEditor::Config::GeneratedConfig) do |config|
- allow(config).to receive(:data) { generated_data }
- end
- end
-
- context 'when reading file from repo fails with an unexpected error' do
- let(:unexpected_error) { RuntimeError.new('some unexpected error') }
-
- before do
- allow(project.repository).to receive(:blob_data_at).and_raise(unexpected_error)
- end
-
- it 'returns an error response' do
- expect(Gitlab::ErrorTracking).to receive(:track_and_raise_exception).with(unexpected_error).and_call_original
- expect { execute }.to raise_error(unexpected_error)
- end
- end
-
- context 'when file is missing' do
- before do
- allow(project.repository).to receive(:blob_data_at).and_raise(GRPC::NotFound)
- expect_next_instance_of(Gitlab::StaticSiteEditor::Config::FileConfig, '{}') do |config|
- allow(config).to receive(:valid?) { true }
- allow(config).to receive(:to_hash_with_defaults) { file_data }
- end
- end
-
- it 'returns default config' do
- expect(execute).to be_success
- expect(execute.payload).to eq(generated: true, file: true)
- end
- end
-
- context 'when file is present' do
- before do
- allow(project.repository).to receive(:blob_data_at).with(ref, anything) do
- config_content
- end
- end
-
- context 'and configuration is not valid' do
- let(:config_content) { 'invalid content' }
-
- before do
- expect_next_instance_of(Gitlab::StaticSiteEditor::Config::FileConfig, config_content) do |config|
- error = 'error'
- allow(config).to receive_message_chain('errors.first') { error }
- allow(config).to receive(:valid?) { false }
- end
- end
-
- it 'returns an error' do
- expect(execute).to be_error
- expect(execute.message).to eq('Invalid configuration format')
- end
- end
-
- context 'and configuration is valid' do
- # NOTE: This has to be a valid config, even though it is mocked, because
- # `expect_next_instance_of` executes the constructor logic.
- let(:config_content) { 'static_site_generator: middleman' }
-
- before do
- expect_next_instance_of(Gitlab::StaticSiteEditor::Config::FileConfig, config_content) do |config|
- allow(config).to receive(:valid?) { true }
- allow(config).to receive(:to_hash_with_defaults) { file_data }
- end
- end
-
- it 'returns merged generated data and config file data' do
- expect(execute).to be_success
- expect(execute.payload).to eq(generated: true, file: true)
- end
-
- it 'returns an error if any keys would be overwritten by the merge' do
- generated_data[:duplicate_key] = true
- file_data[:duplicate_key] = true
- expect(execute).to be_error
- expect(execute.message).to match(/duplicate key.*duplicate_key.*found/i)
- end
- end
- end
- end
- end
-end
diff --git a/spec/services/terraform/remote_state_handler_spec.rb b/spec/services/terraform/remote_state_handler_spec.rb
index ca392849d49..19c1d4109e9 100644
--- a/spec/services/terraform/remote_state_handler_spec.rb
+++ b/spec/services/terraform/remote_state_handler_spec.rb
@@ -33,6 +33,14 @@ RSpec.describe Terraform::RemoteStateHandler do
it 'returns the state' do
expect(subject.find_with_lock).to eq(state)
end
+
+ context 'with a state scheduled for deletion' do
+ let!(:state) { create(:terraform_state, :deletion_in_progress, project: project, name: 'state') }
+
+ it 'raises an exception' do
+ expect { subject.find_with_lock }.to raise_error(described_class::StateDeletedError)
+ end
+ end
end
end
end
@@ -84,6 +92,13 @@ RSpec.describe Terraform::RemoteStateHandler do
.to raise_error(described_class::StateLockedError)
end
+ it 'raises an exception if the state is scheduled for deletion' do
+ create(:terraform_state, :deletion_in_progress, project: project, name: 'new-state')
+
+ expect { handler.handle_with_lock }
+ .to raise_error(described_class::StateDeletedError)
+ end
+
context 'user does not have permission to modify state' do
let(:user) { developer }
@@ -127,24 +142,28 @@ RSpec.describe Terraform::RemoteStateHandler do
expect { handler.lock! }.to raise_error(described_class::StateLockedError)
end
+
+ it 'raises an exception when the state exists and is scheduled for deletion' do
+ create(:terraform_state, :deletion_in_progress, project: project, name: 'new-state')
+
+ expect { handler.lock! }.to raise_error(described_class::StateDeletedError)
+ end
end
describe '#unlock!' do
- let(:lock_id) { 'abc-abc' }
+ let_it_be(:state) { create(:terraform_state, :locked, project: project, name: 'new-state', lock_xid: 'abc-abc') }
+
+ let(:lock_id) { state.lock_xid }
subject(:handler) do
described_class.new(
project,
user,
- name: 'new-state',
+ name: state.name,
lock_id: lock_id
)
end
- before do
- create(:terraform_state, :locked, project: project, name: 'new-state', lock_xid: 'abc-abc')
- end
-
it 'unlocks the state' do
state = handler.unlock!
@@ -169,6 +188,15 @@ RSpec.describe Terraform::RemoteStateHandler do
.to raise_error(described_class::StateLockedError)
end
end
+
+ context 'with a state scheduled for deletion' do
+ it 'raises an exception' do
+ state.update!(deleted_at: Time.current)
+
+ expect { handler.unlock! }
+ .to raise_error(described_class::StateDeletedError)
+ end
+ end
end
end
end
diff --git a/spec/services/terraform/states/destroy_service_spec.rb b/spec/services/terraform/states/destroy_service_spec.rb
new file mode 100644
index 00000000000..5acf32cd73c
--- /dev/null
+++ b/spec/services/terraform/states/destroy_service_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Terraform::States::DestroyService do
+ let_it_be(:state) { create(:terraform_state, :with_version, :deletion_in_progress) }
+
+ let(:file) { instance_double(Terraform::StateUploader, relative_path: 'path') }
+
+ before do
+ allow_next_found_instance_of(Terraform::StateVersion) do |version|
+ allow(version).to receive(:file).and_return(file)
+ end
+ end
+
+ describe '#execute' do
+ subject { described_class.new(state).execute }
+
+ it 'removes version files from object storage, followed by the state record' do
+ expect(file).to receive(:remove!).once
+ expect(state).to receive(:destroy!)
+
+ subject
+ end
+
+ context 'state is not marked for deletion' do
+ let(:state) { create(:terraform_state) }
+
+ it 'does not delete the state' do
+ expect(state).not_to receive(:destroy!)
+
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/services/terraform/states/trigger_destroy_service_spec.rb b/spec/services/terraform/states/trigger_destroy_service_spec.rb
new file mode 100644
index 00000000000..2e96331779c
--- /dev/null
+++ b/spec/services/terraform/states/trigger_destroy_service_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Terraform::States::TriggerDestroyService do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user, maintainer_projects: [project]) }
+
+ describe '#execute', :aggregate_failures do
+ let_it_be(:state) { create(:terraform_state, project: project) }
+
+ subject { described_class.new(state, current_user: user).execute }
+
+ it 'marks the state as deleted and schedules a cleanup worker' do
+ expect(Terraform::States::DestroyWorker).to receive(:perform_async).with(state.id).once
+
+ expect(subject).to be_success
+ expect(state.deleted_at).to be_like_time(Time.current)
+ end
+
+ shared_examples 'unable to delete state' do
+ it 'does not modify the state' do
+ expect(Terraform::States::DestroyWorker).not_to receive(:perform_async)
+
+ expect { subject }.not_to change(state, :deleted_at)
+ expect(subject).to be_error
+ expect(subject.message).to eq(message)
+ end
+ end
+
+ context 'user does not have permission' do
+ let(:user) { create(:user, developer_projects: [project]) }
+ let(:message) { 'You have insufficient permissions to delete this state' }
+
+ include_examples 'unable to delete state'
+ end
+
+ context 'state is locked' do
+ let(:state) { create(:terraform_state, :locked, project: project) }
+ let(:message) { 'Cannot remove a locked state' }
+
+ include_examples 'unable to delete state'
+ end
+ end
+end
diff --git a/spec/services/user_project_access_changed_service_spec.rb b/spec/services/user_project_access_changed_service_spec.rb
index 438db6b987b..be4f205afb5 100644
--- a/spec/services/user_project_access_changed_service_spec.rb
+++ b/spec/services/user_project_access_changed_service_spec.rb
@@ -31,6 +31,19 @@ RSpec.describe UserProjectAccessChangedService do
priority: described_class::LOW_PRIORITY)
end
+ it 'permits medium-priority operation' do
+ expect(AuthorizedProjectUpdate::UserRefreshWithLowUrgencyWorker).to(
+ receive(:bulk_perform_in).with(
+ described_class::MEDIUM_DELAY,
+ [[1], [2]],
+ { batch_delay: 30.seconds, batch_size: 100 }
+ )
+ )
+
+ described_class.new([1, 2]).execute(blocking: false,
+ priority: described_class::MEDIUM_PRIORITY)
+ end
+
it 'sets the current caller_id as related_class in the context of all the enqueued jobs' do
Gitlab::ApplicationContext.with_context(caller_id: 'Foo') do
described_class.new([1, 2]).execute(blocking: false,
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
index b99bc860523..068550ec234 100644
--- a/spec/services/web_hook_service_spec.rb
+++ b/spec/services/web_hook_service_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state do
include StubRequests
+ let(:ellipsis) { '…' }
let_it_be(:project) { create(:project) }
let_it_be_with_reload(:project_hook) { create(:project_hook, project: project) }
@@ -268,6 +269,20 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state
end
context 'execution logging' do
+ let(:default_log_data) do
+ {
+ trigger: 'push_hooks',
+ url: project_hook.url,
+ request_headers: headers,
+ request_data: data,
+ response_body: 'Success',
+ response_headers: {},
+ response_status: 200,
+ execution_duration: be > 0,
+ internal_error_message: nil
+ }
+ end
+
context 'with success' do
before do
stub_full_request(project_hook.url, method: :post).to_return(status: 200, body: 'Success')
@@ -280,7 +295,7 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state
expect(::WebHooks::LogExecutionWorker).not_to receive(:perform_async)
expect(::WebHooks::LogExecutionService)
.to receive(:new)
- .with(hook: project_hook, log_data: Hash, response_category: :ok)
+ .with(hook: project_hook, log_data: default_log_data, response_category: :ok)
.and_return(double(execute: nil))
service_instance.execute
@@ -291,17 +306,7 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state
expect(WebHooks::LogExecutionWorker).to receive(:perform_async)
.with(
project_hook.id,
- hash_including(
- trigger: 'push_hooks',
- url: project_hook.url,
- request_headers: headers,
- request_data: data,
- response_body: 'Success',
- response_headers: {},
- response_status: 200,
- execution_duration: be > 0,
- internal_error_message: nil
- ),
+ hash_including(default_log_data),
:ok,
nil
)
@@ -328,15 +333,10 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state
.with(
project_hook.id,
hash_including(
- trigger: 'push_hooks',
- url: project_hook.url,
- request_headers: headers,
- request_data: data,
- response_body: 'Bad request',
- response_headers: {},
- response_status: 400,
- execution_duration: be > 0,
- internal_error_message: nil
+ default_log_data.merge(
+ response_body: 'Bad request',
+ response_status: 400
+ )
),
:failed,
nil
@@ -356,15 +356,11 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state
.with(
project_hook.id,
hash_including(
- trigger: 'push_hooks',
- url: project_hook.url,
- request_headers: headers,
- request_data: data,
- response_body: '',
- response_headers: {},
- response_status: 'internal error',
- execution_duration: be > 0,
- internal_error_message: 'Some HTTP Post error'
+ default_log_data.merge(
+ response_body: '',
+ response_status: 'internal error',
+ internal_error_message: 'Some HTTP Post error'
+ )
),
:error,
nil
@@ -383,23 +379,137 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state
expect(WebHooks::LogExecutionWorker).to receive(:perform_async)
.with(
project_hook.id,
- hash_including(
- trigger: 'push_hooks',
- url: project_hook.url,
- request_headers: headers,
- request_data: data,
- response_body: '',
- response_headers: {},
- response_status: 200,
- execution_duration: be > 0,
- internal_error_message: nil
- ),
+ hash_including(default_log_data.merge(response_body: '')),
+ :ok,
+ nil
+ )
+
+ service_instance.execute
+ end
+ end
+
+ context 'with oversize response body' do
+ let(:oversize_body) { 'a' * (described_class::RESPONSE_BODY_SIZE_LIMIT + 1) }
+ let(:stripped_body) { 'a' * (described_class::RESPONSE_BODY_SIZE_LIMIT - ellipsis.bytesize) + ellipsis }
+
+ before do
+ stub_full_request(project_hook.url, method: :post).to_return(status: 200, body: oversize_body)
+ end
+
+ it 'queues LogExecutionWorker with stripped response_body' do
+ expect(WebHooks::LogExecutionWorker).to receive(:perform_async)
+ .with(
+ project_hook.id,
+ hash_including(default_log_data.merge(response_body: stripped_body)),
+ :ok,
+ nil
+ )
+
+ service_instance.execute
+ end
+ end
+
+ context 'with massive amount of headers' do
+ let(:response_headers) do
+ (1..described_class::RESPONSE_HEADERS_COUNT_LIMIT + 1).to_a.to_h do |num|
+ ["header-#{num}", SecureRandom.hex(num)]
+ end
+ end
+
+ let(:expected_response_headers) do
+ (1..described_class::RESPONSE_HEADERS_COUNT_LIMIT).to_a.to_h do |num|
+ # Capitalized
+ ["Header-#{num}", response_headers["header-#{num}"]]
+ end
+ end
+
+ before do
+ stub_full_request(project_hook.url, method: :post).to_return(
+ status: 200, body: 'Success', headers: response_headers
+ )
+ end
+
+ it 'queues LogExecutionWorker with limited amount of headers' do
+ expect(WebHooks::LogExecutionWorker).to receive(:perform_async)
+ .with(
+ project_hook.id,
+ hash_including(default_log_data.merge(response_headers: expected_response_headers)),
+ :ok,
+ nil
+ )
+
+ service_instance.execute
+ end
+ end
+
+ context 'with oversize header' do
+ let(:oversize_header) { 'a' * (described_class::RESPONSE_HEADERS_SIZE_LIMIT + 1) }
+ let(:stripped_header) { 'a' * (described_class::RESPONSE_HEADERS_SIZE_LIMIT - ellipsis.bytesize) + ellipsis }
+ let(:response_headers) { { 'oversized-header' => oversize_header } }
+ let(:expected_response_headers) { { 'Oversized-Header' => stripped_header } }
+
+ before do
+ stub_full_request(project_hook.url, method: :post).to_return(
+ status: 200, body: 'Success', headers: response_headers
+ )
+ end
+
+ it 'queues LogExecutionWorker with stripped header value' do
+ expect(WebHooks::LogExecutionWorker).to receive(:perform_async)
+ .with(
+ project_hook.id,
+ hash_including(default_log_data.merge(response_headers: expected_response_headers)),
+ :ok,
+ nil
+ )
+
+ service_instance.execute
+ end
+ end
+
+ context 'with log data exceeding Sidekiq limit' do
+ before do
+ stub_full_request(project_hook.url, method: :post).to_return(status: 200, body: 'Success')
+ end
+
+ it 'queues LogExecutionWorker with request_data overrided in the second attempt' do
+ expect(WebHooks::LogExecutionWorker).to receive(:perform_async)
+ .with(
+ project_hook.id,
+ hash_including(default_log_data),
+ :ok,
+ nil
+ )
+ .and_raise(
+ Gitlab::SidekiqMiddleware::SizeLimiter::ExceedLimitError.new(WebHooks::LogExecutionWorker, 100, 50)
+ )
+ .ordered
+ expect(WebHooks::LogExecutionWorker).to receive(:perform_async)
+ .with(
+ project_hook.id,
+ hash_including(default_log_data.merge(request_data: WebHookLog::OVERSIZE_REQUEST_DATA)),
:ok,
nil
)
+ .and_call_original
+ .ordered
service_instance.execute
end
+
+ context 'new log data still exceeds limit' do
+ before do
+ allow(WebHooks::LogExecutionWorker).to receive(:perform_async).and_raise(
+ Gitlab::SidekiqMiddleware::SizeLimiter::ExceedLimitError.new(WebHooks::LogExecutionWorker, 100, 50)
+ )
+ end
+
+ it 'raises an exception' do
+ expect do
+ service_instance.execute
+ end.to raise_error(Gitlab::SidekiqMiddleware::SizeLimiter::ExceedLimitError)
+ end
+ end
end
end
end
@@ -411,7 +521,7 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state
def expect_to_rate_limit(hook, threshold:, throttled: false)
expect(Gitlab::ApplicationRateLimiter).to receive(:throttled?)
- .with(:web_hook_calls, scope: [hook], threshold: threshold)
+ .with(:web_hook_calls, scope: [hook.parent.root_namespace], threshold: threshold)
.and_return(throttled)
end
@@ -460,13 +570,8 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state
end
end
- context 'when the hook is throttled (via Redis)', :clean_gitlab_redis_rate_limiting do
+ context 'when the hook is throttled (via Redis)', :clean_gitlab_redis_rate_limiting, :freeze_time 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 }
diff --git a/spec/services/web_hooks/destroy_service_spec.rb b/spec/services/web_hooks/destroy_service_spec.rb
index 5269fe08ac0..4d9bb18e540 100644
--- a/spec/services/web_hooks/destroy_service_spec.rb
+++ b/spec/services/web_hooks/destroy_service_spec.rb
@@ -7,50 +7,46 @@ RSpec.describe WebHooks::DestroyService do
subject { described_class.new(user) }
- shared_examples 'batched destroys' do
- it 'destroys all hooks in batches' do
- stub_const("#{described_class}::BATCH_SIZE", 1)
- expect(subject).to receive(:delete_web_hook_logs_in_batches).exactly(4).times.and_call_original
-
- expect do
- status = subject.execute(hook)
- expect(status[:async]).to be false
- end
- .to change { WebHook.count }.from(1).to(0)
- .and change { WebHookLog.count }.from(3).to(0)
- end
-
- it 'returns an error if sync destroy fails' do
- expect(hook).to receive(:destroy).and_return(false)
+ describe '#execute' do
+ %i[system_hook project_hook].each do |factory|
+ context "deleting a #{factory}" do
+ let!(:hook) { create(factory) } # rubocop: disable Rails/SaveBang (false-positive!)
+ let!(:log) { create_list(:web_hook_log, 3, web_hook: hook) }
- result = subject.sync_destroy(hook)
-
- expect(result[:status]).to eq(:error)
- expect(result[:message]).to eq("Unable to destroy #{hook.model_name.human}")
- end
+ it 'is successful' do
+ expect(subject.execute(hook)).to be_success
+ end
- it 'schedules an async delete' do
- stub_const('WebHooks::DestroyService::LOG_COUNT_THRESHOLD', 1)
+ it 'destroys the hook' do
+ expect { subject.execute(hook) }.to change(WebHook, :count).from(1).to(0)
+ end
- expect(WebHooks::DestroyWorker).to receive(:perform_async).with(user.id, hook.id).and_call_original
+ it 'does not destroy logs' do
+ expect { subject.execute(hook) }.not_to change(WebHookLog, :count)
+ end
- status = subject.execute(hook)
+ it 'schedules the destruction of logs' do
+ expect(WebHooks::LogDestroyWorker).to receive(:perform_async).with({ 'hook_id' => hook.id })
+ expect(Gitlab::AppLogger).to receive(:info).with(match(/scheduled a deletion of logs/))
- expect(status[:async]).to be true
- end
- end
+ subject.execute(hook)
+ end
- context 'with system hook' do
- let!(:hook) { create(:system_hook, url: "http://example.com") }
- let!(:log) { create_list(:web_hook_log, 3, web_hook: hook) }
+ context 'when the hook fails to destroy' do
+ before do
+ allow(hook).to receive(:destroy).and_return(false)
+ end
- it_behaves_like 'batched destroys'
- end
+ it 'is not a success' do
+ expect(WebHooks::LogDestroyWorker).not_to receive(:perform_async)
- context 'with project hook' do
- let!(:hook) { create(:project_hook) }
- let!(:log) { create_list(:web_hook_log, 3, web_hook: hook) }
+ r = subject.execute(hook)
- it_behaves_like 'batched destroys'
+ expect(r).to be_error
+ expect(r[:message]).to match %r{Unable to destroy}
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/services/web_hooks/log_destroy_service_spec.rb b/spec/services/web_hooks/log_destroy_service_spec.rb
new file mode 100644
index 00000000000..7634726e5a4
--- /dev/null
+++ b/spec/services/web_hooks/log_destroy_service_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe WebHooks::LogDestroyService do
+ subject(:service) { described_class.new(hook.id) }
+
+ describe '#execute' do
+ shared_examples 'deletes web hook logs for hook' do
+ before do
+ create_list(:web_hook_log, 3, web_hook: hook)
+ hook.destroy! # The LogDestroyService is expected to be called _after_ hook destruction
+ end
+
+ it 'deletes the logs' do
+ expect { service.execute }
+ .to change(WebHookLog, :count).from(3).to(0)
+ end
+
+ context 'when the data-set exceeds the batch size' do
+ before do
+ stub_const("#{described_class}::BATCH_SIZE", 2)
+ end
+
+ it 'deletes the logs' do
+ expect { service.execute }
+ .to change(WebHookLog, :count).from(3).to(0)
+ end
+ end
+
+ context 'when it encounters an error' do
+ before do
+ allow(WebHookLog).to receive(:delete_batch_for).and_raise(StandardError.new('bang'))
+ end
+
+ it 'reports the error' do
+ expect(service.execute)
+ .to be_error
+ .and have_attributes(message: 'bang')
+ end
+ end
+ end
+
+ context 'with system hook' do
+ let!(:hook) { create(:system_hook, url: "http://example.com") }
+
+ it_behaves_like 'deletes web hook logs for hook'
+ end
+
+ context 'with project hook' do
+ let!(:hook) { create(:project_hook) }
+
+ it_behaves_like 'deletes web hook logs for hook'
+ end
+ end
+end
diff --git a/spec/services/work_items/update_service_spec.rb b/spec/services/work_items/update_service_spec.rb
index b2d3f428899..9030326dadb 100644
--- a/spec/services/work_items/update_service_spec.rb
+++ b/spec/services/work_items/update_service_spec.rb
@@ -8,11 +8,12 @@ RSpec.describe WorkItems::UpdateService do
let_it_be_with_reload(:work_item) { create(:work_item, project: project, assignees: [developer]) }
let(:spam_params) { double }
+ let(:widget_params) { {} }
let(:opts) { {} }
let(:current_user) { developer }
describe '#execute' do
- subject(:update_work_item) { described_class.new(project: project, current_user: current_user, params: opts, spam_params: spam_params).execute(work_item) }
+ subject(:update_work_item) { described_class.new(project: project, current_user: current_user, params: opts, spam_params: spam_params, widget_params: widget_params).execute(work_item) }
before do
stub_spam_services
@@ -69,5 +70,17 @@ RSpec.describe WorkItems::UpdateService do
end
end
end
+
+ context 'when updating widgets' do
+ context 'for the description widget' do
+ let(:widget_params) { { description_widget: { description: 'changed' } } }
+
+ it 'updates the description of the work item' do
+ update_work_item
+
+ expect(work_item.description).to eq('changed')
+ end
+ end
+ end
end
end