diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-28 00:09:08 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-28 00:09:08 +0000 |
commit | f54a50aa826d0eedcf2e56f51462613bc132f826 (patch) | |
tree | 7194aca23f9af822ea55966a6f477b3d8d68ee47 /spec/services | |
parent | c77fda905a8619b756163c10a75171dc9cfe7084 (diff) | |
download | gitlab-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.rb | 35 | ||||
-rw-r--r-- | spec/services/snippets/bulk_destroy_service_spec.rb | 161 | ||||
-rw-r--r-- | spec/services/snippets/create_service_spec.rb | 2 | ||||
-rw-r--r-- | spec/services/snippets/destroy_service_spec.rb | 81 | ||||
-rw-r--r-- | spec/services/snippets/update_service_spec.rb | 2 | ||||
-rw-r--r-- | spec/services/users/destroy_service_spec.rb | 54 |
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 |