summaryrefslogtreecommitdiff
path: root/spec/services
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-02-28 00:09:08 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-02-28 00:09:08 +0000
commitf54a50aa826d0eedcf2e56f51462613bc132f826 (patch)
tree7194aca23f9af822ea55966a6f477b3d8d68ee47 /spec/services
parentc77fda905a8619b756163c10a75171dc9cfe7084 (diff)
downloadgitlab-ce-f54a50aa826d0eedcf2e56f51462613bc132f826.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/services')
-rw-r--r--spec/services/projects/destroy_service_spec.rb35
-rw-r--r--spec/services/snippets/bulk_destroy_service_spec.rb161
-rw-r--r--spec/services/snippets/create_service_spec.rb2
-rw-r--r--spec/services/snippets/destroy_service_spec.rb81
-rw-r--r--spec/services/snippets/update_service_spec.rb2
-rw-r--r--spec/services/users/destroy_service_spec.rb54
6 files changed, 328 insertions, 7 deletions
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 21a65f361a9..58c40d04fe9 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -124,7 +124,7 @@ describe Projects::DestroyService do
allow(project.repository).to receive(:before_delete).and_raise(::Gitlab::Git::CommandError)
allow(Gitlab::GitLogger).to receive(:warn).with(
class: Repositories::DestroyService.name,
- project_id: project.id,
+ container_id: project.id,
disk_path: project.disk_path,
message: 'Gitlab::Git::CommandError').and_call_original
end
@@ -338,6 +338,39 @@ describe Projects::DestroyService do
end
end
+ context 'snippets' do
+ let!(:snippet1) { create(:project_snippet, project: project, author: user) }
+ let!(:snippet2) { create(:project_snippet, project: project, author: user) }
+
+ it 'does not include snippets when deleting in batches' do
+ expect(project).to receive(:destroy_dependent_associations_in_batches).with({ exclude: [:container_repositories, :snippets] })
+
+ destroy_project(project, user)
+ end
+
+ it 'calls the bulk snippet destroy service' do
+ expect(project.snippets.count).to eq 2
+
+ expect(Snippets::BulkDestroyService).to receive(:new)
+ .with(user, project.snippets).and_call_original
+
+ expect do
+ destroy_project(project, user)
+ end.to change(Snippet, :count).by(-2)
+ end
+
+ context 'when an error is raised deleting snippets' do
+ it 'does not delete project' do
+ allow_next_instance_of(Snippets::BulkDestroyService) do |instance|
+ allow(instance).to receive(:execute).and_return(ServiceResponse.error(message: 'foo'))
+ end
+
+ expect(destroy_project(project, user)).to be_falsey
+ expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_truthy
+ end
+ end
+ end
+
def destroy_project(project, user, params = {})
described_class.new(project, user, params).public_send(async ? :async_execute : :execute)
end
diff --git a/spec/services/snippets/bulk_destroy_service_spec.rb b/spec/services/snippets/bulk_destroy_service_spec.rb
new file mode 100644
index 00000000000..f03d7496f94
--- /dev/null
+++ b/spec/services/snippets/bulk_destroy_service_spec.rb
@@ -0,0 +1,161 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Snippets::BulkDestroyService do
+ let_it_be(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let!(:personal_snippet) { create(:personal_snippet, :repository, author: user) }
+ let!(:project_snippet) { create(:project_snippet, :repository, project: project, author: user) }
+ let(:snippets) { user.snippets }
+ let(:gitlab_shell) { Gitlab::Shell.new }
+ let(:service_user) { user }
+
+ before do
+ project.add_developer(user)
+ end
+
+ subject { described_class.new(service_user, snippets) }
+
+ describe '#execute' 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
+
+ aggregate_failures do
+ expect do
+ response = subject.execute
+ end.to change(Snippet, :count).by(-2)
+
+ expect(response).to be_success
+ expect(repository_exists?(personal_snippet)).to be_falsey
+ expect(repository_exists?(project_snippet)).to be_falsey
+ end
+ end
+
+ context 'when snippets is empty' do
+ let(:snippets) { Snippet.none }
+
+ it 'returns a ServiceResponse success response' do
+ response = subject.execute
+
+ expect(response).to be_success
+ expect(response.message).to eq 'No snippets found.'
+ end
+ end
+
+ shared_examples 'error is raised' do
+ it 'returns error' do
+ response = subject.execute
+
+ aggregate_failures do
+ expect(response).to be_error
+ expect(response.message).to eq error_message
+ end
+ end
+
+ it 'no record is deleted' do
+ expect do
+ subject.execute
+ end.not_to change(Snippet, :count)
+ end
+ end
+
+ context 'when user does not have access to remove the snippet' do
+ let(:service_user) { create(:user) }
+
+ it_behaves_like 'error is raised' do
+ let(:error_message) { "You don't have access to delete these snippets." }
+ end
+ end
+
+ context 'when an error is raised deleting the repository' do
+ before do
+ allow_next_instance_of(Repositories::DestroyService) do |instance|
+ allow(instance).to receive(:execute).and_return({ status: :error })
+ end
+ end
+
+ 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
+ before do
+ allow(snippets).to receive(:destroy_all).and_raise(ActiveRecord::ActiveRecordError)
+ end
+
+ 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
+ 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 do
+ response = subject.execute
+ end.to change(Snippet, :count).by(-3)
+
+ expect(response).to be_success
+ end
+ 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
+end
diff --git a/spec/services/snippets/create_service_spec.rb b/spec/services/snippets/create_service_spec.rb
index a1cbec6748a..37b203c2341 100644
--- a/spec/services/snippets/create_service_spec.rb
+++ b/spec/services/snippets/create_service_spec.rb
@@ -18,7 +18,7 @@ describe Snippets::CreateService do
let(:extra_opts) { {} }
let(:creator) { admin }
- subject { Snippets::CreateService.new(project, creator, opts).execute }
+ subject { described_class.new(project, creator, opts).execute }
let(:snippet) { subject.payload[:snippet] }
diff --git a/spec/services/snippets/destroy_service_spec.rb b/spec/services/snippets/destroy_service_spec.rb
index bb035d275ab..840dc11a740 100644
--- a/spec/services/snippets/destroy_service_spec.rb
+++ b/spec/services/snippets/destroy_service_spec.rb
@@ -8,7 +8,7 @@ describe Snippets::DestroyService do
let_it_be(:other_user) { create(:user) }
describe '#execute' do
- subject { Snippets::DestroyService.new(user, snippet).execute }
+ subject { described_class.new(user, snippet).execute }
context 'when snippet is nil' do
let(:snippet) { nil }
@@ -30,7 +30,7 @@ describe Snippets::DestroyService do
shared_examples 'an unsuccessful destroy' do
it 'does not delete the snippet' do
- expect { subject }.to change { Snippet.count }.by(0)
+ expect { subject }.not_to change { Snippet.count }
end
it 'returns ServiceResponse error' do
@@ -38,8 +38,63 @@ describe Snippets::DestroyService do
end
end
+ 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
+
+ expect(subject).to be_success
+ end
+
+ context 'when the repository deletion service raises an error' do
+ before do
+ allow_next_instance_of(Repositories::DestroyService) do |instance|
+ allow(instance).to receive(:execute).and_return({ status: :error })
+ end
+ 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
+ before do
+ allow(snippet).to receive(:destroy!).and_raise(ActiveRecord::ActiveRecordError)
+ 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
+ it 'does not schedule anything and return success' do
+ allow(snippet).to receive(:repository).and_return(nil)
+
+ expect(GitlabShellWorker).not_to receive(:perform_in)
+ expect_next_instance_of(Repositories::DestroyService) do |instance|
+ expect(instance).to receive(:execute).and_call_original
+ end
+
+ expect(subject).to be_success
+ end
+ end
+ end
+
context 'when ProjectSnippet' do
- let!(:snippet) { create(:project_snippet, project: project, author: author) }
+ let!(:snippet) { create(:project_snippet, :repository, project: project, author: author) }
context 'when user is able to admin_project_snippet' do
let(:author) { user }
@@ -49,6 +104,7 @@ describe Snippets::DestroyService do
end
it_behaves_like 'a successful destroy'
+ it_behaves_like 'deletes the snippet repository'
end
context 'when user is not able to admin_project_snippet' do
@@ -59,12 +115,13 @@ describe Snippets::DestroyService do
end
context 'when PersonalSnippet' do
- let!(:snippet) { create(:personal_snippet, author: author) }
+ let!(:snippet) { create(:personal_snippet, :repository, author: author) }
context 'when user is able to admin_personal_snippet' do
let(:author) { user }
it_behaves_like 'a successful destroy'
+ it_behaves_like 'deletes the snippet repository'
end
context 'when user is not able to admin_personal_snippet' do
@@ -73,5 +130,21 @@ describe Snippets::DestroyService do
it_behaves_like 'an unsuccessful destroy'
end
end
+
+ context 'when the repository does not exists' do
+ let(:snippet) { create(:personal_snippet, author: user) }
+
+ it 'does not schedule anything and return success' do
+ expect(snippet.repository).not_to be_nil
+ expect(snippet.repository.exists?).to be_falsey
+
+ expect(GitlabShellWorker).not_to receive(:perform_in)
+ expect_next_instance_of(Repositories::DestroyService) do |instance|
+ expect(instance).to receive(:execute).and_call_original
+ end
+
+ expect(subject).to be_success
+ end
+ end
end
end
diff --git a/spec/services/snippets/update_service_spec.rb b/spec/services/snippets/update_service_spec.rb
index b8215f9779d..4858a0512ad 100644
--- a/spec/services/snippets/update_service_spec.rb
+++ b/spec/services/snippets/update_service_spec.rb
@@ -18,7 +18,7 @@ describe Snippets::UpdateService do
let(:updater) { user }
subject do
- Snippets::UpdateService.new(
+ described_class.new(
project,
updater,
options
diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb
index 2b658a93b0a..a664719783a 100644
--- a/spec/services/users/destroy_service_spec.rb
+++ b/spec/services/users/destroy_service_spec.rb
@@ -26,6 +26,12 @@ describe Users::DestroyService do
service.execute(user)
end
+ it 'does not include snippets when deleting in batches' do
+ expect(user).to receive(:destroy_dependent_associations_in_batches).with({ exclude: [:snippets] })
+
+ service.execute(user)
+ end
+
it 'will delete the project' do
expect_next_instance_of(Projects::DestroyService) do |destroy_service|
expect(destroy_service).to receive(:execute).once.and_return(true)
@@ -33,6 +39,54 @@ describe Users::DestroyService do
service.execute(user)
end
+
+ it 'calls the bulk snippet destroy service for the user personal snippets' do
+ repo1 = create(:personal_snippet, :repository, author: user).snippet_repository
+ repo2 = create(:project_snippet, :repository, author: user).snippet_repository
+ repo3 = create(:project_snippet, :repository, project: project, author: user).snippet_repository
+
+ aggregate_failures do
+ expect(gitlab_shell.repository_exists?(repo1.shard_name, repo1.disk_path + '.git')).to be_truthy
+ expect(gitlab_shell.repository_exists?(repo2.shard_name, repo2.disk_path + '.git')).to be_truthy
+ expect(gitlab_shell.repository_exists?(repo3.shard_name, repo3.disk_path + '.git')).to be_truthy
+ end
+
+ # Call made when destroying user personal projects
+ expect(Snippets::BulkDestroyService).to receive(:new)
+ .with(admin, project.snippets).and_call_original
+
+ # Call to remove user personal snippets and for
+ # project snippets where projects are not user personal
+ # ones
+ expect(Snippets::BulkDestroyService).to receive(:new)
+ .with(admin, user.snippets).and_call_original
+
+ service.execute(user)
+
+ aggregate_failures do
+ expect(gitlab_shell.repository_exists?(repo1.shard_name, repo1.disk_path + '.git')).to be_falsey
+ expect(gitlab_shell.repository_exists?(repo2.shard_name, repo2.disk_path + '.git')).to be_falsey
+ expect(gitlab_shell.repository_exists?(repo3.shard_name, repo3.disk_path + '.git')).to be_falsey
+ end
+ end
+
+ context 'when an error is raised deleting snippets' do
+ it 'does not delete user' do
+ snippet = create(:personal_snippet, :repository, author: user)
+
+ bulk_service = double
+ allow(Snippets::BulkDestroyService).to receive(:new).and_call_original
+ allow(Snippets::BulkDestroyService).to receive(:new).with(admin, user.snippets).and_return(bulk_service)
+ allow(bulk_service).to receive(:execute).and_return(ServiceResponse.error(message: 'foo'))
+
+ aggregate_failures do
+ expect { service.execute(user) }
+ .to raise_error(Users::DestroyService::DestroyError, 'foo' )
+ expect(snippet.reload).not_to be_nil
+ expect(gitlab_shell.repository_exists?(snippet.repository_storage, snippet.disk_path + '.git')).to be_truthy
+ end
+ end
+ end
end
context 'projects in pending_delete' do