Commit a7a06617 authored by James Fargher's avatar James Fargher

Refactor Backup::Repositories specs

Now that we have extracted the gitaly RPC backup strategy and tested it
separately, we can make these tests more generic. This makes sure we are
querying the database and calling the strategy correctly, but not trying
to actually verify how the backup happens. The goal here is to make the
strategy itself the single source of truth and not make these tests too
fragile.
parent 0e619be6
...@@ -3,31 +3,25 @@ ...@@ -3,31 +3,25 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Backup::Repositories do RSpec.describe Backup::Repositories do
let(:progress) { StringIO.new } let(:progress) { spy(:stdout) }
let(:strategy) { spy(:strategy) }
subject { described_class.new(progress) } subject { described_class.new(progress, strategy: strategy) }
before do
allow(progress).to receive(:puts)
allow(progress).to receive(:print)
allow_next_instance_of(described_class) do |instance|
allow(instance).to receive(:progress).and_return(progress)
end
end
describe '#dump' do describe '#dump' do
context 'hashed storage' do context 'hashed storage' do
let_it_be(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let_it_be(:group) { create(:group, :wiki_repo) } let_it_be(:group) { create(:group, :wiki_repo) }
it 'creates repository bundles', :aggregate_failures do it 'calls enqueue for each repository type', :aggregate_failures do
create(:wiki_page, container: group) create(:wiki_page, container: group)
subject.dump(max_concurrency: 1, max_storage_concurrency: 1) subject.dump(max_concurrency: 1, max_storage_concurrency: 1)
expect(File).to exist(File.join(Gitlab.config.backup.path, 'repositories', project.disk_path + '.bundle')) expect(strategy).to have_received(:start).with(:create)
expect(File).to exist(File.join(Gitlab.config.backup.path, 'repositories', group.wiki.disk_path + '.bundle')) expect(strategy).to have_received(:enqueue).with(project, Gitlab::GlRepository::PROJECT)
expect(strategy).to have_received(:enqueue).with(group, Gitlab::GlRepository::WIKI)
expect(strategy).to have_received(:wait)
end end
end end
...@@ -37,16 +31,18 @@ RSpec.describe Backup::Repositories do ...@@ -37,16 +31,18 @@ RSpec.describe Backup::Repositories do
it 'creates the expected number of threads' do it 'creates the expected number of threads' do
expect(Thread).not_to receive(:new) expect(Thread).not_to receive(:new)
expect(strategy).to receive(:start).with(:create)
groups.each do |group| groups.each do |group|
expect(subject).to receive(:dump_group).with(group).and_call_original expect(strategy).to receive(:enqueue).with(group, Gitlab::GlRepository::WIKI)
end end
expect(strategy).to receive(:wait)
subject.dump(max_concurrency: 1, max_storage_concurrency: 1) subject.dump(max_concurrency: 1, max_storage_concurrency: 1)
end end
describe 'command failure' do describe 'command failure' do
it 'dump_group raises an error' do it 'dump_group raises an error' do
allow(subject).to receive(:dump_group).and_raise(IOError) allow(strategy).to receive(:enqueue).with(anything, Gitlab::GlRepository::WIKI).and_raise(IOError)
expect { subject.dump(max_concurrency: 1, max_storage_concurrency: 1) }.to raise_error(IOError) expect { subject.dump(max_concurrency: 1, max_storage_concurrency: 1) }.to raise_error(IOError)
end end
...@@ -76,24 +72,13 @@ RSpec.describe Backup::Repositories do ...@@ -76,24 +72,13 @@ RSpec.describe Backup::Repositories do
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
let_it_be(:group) { create(:group) } let_it_be(:group) { create(:group) }
let(:next_path_to_bundle) do it 'calls enqueue for each repository type', :aggregate_failures do
[
Rails.root.join('spec/fixtures/lib/backup/wiki_repo.bundle'),
Rails.root.join('spec/fixtures/lib/backup/project_repo.bundle')
].to_enum
end
it 'restores repositories from bundles', :aggregate_failures do
allow_next_instance_of(::Backup::GitalyRpcBackup::BackupRestore) do |backup_restore|
allow(backup_restore).to receive(:path_to_bundle).and_return(next_path_to_bundle.next)
end
subject.restore subject.restore
collect_commit_shas = -> (repo) { repo.commits('master', limit: 10).map(&:sha) } expect(strategy).to have_received(:start).with(:restore)
expect(strategy).to have_received(:enqueue).with(project, Gitlab::GlRepository::PROJECT)
expect(collect_commit_shas.call(project.repository)).to eq(['393a7d860a5a4c3cc736d7eb00604e3472bb95ec']) expect(strategy).to have_received(:enqueue).with(group, Gitlab::GlRepository::WIKI)
expect(collect_commit_shas.call(group.wiki.repository)).to eq(['c74b9948d0088d703ee1fafeddd9ed9add2901ea']) expect(strategy).to have_received(:wait)
end end
end end
end end
...@@ -162,7 +162,6 @@ module Backup ...@@ -162,7 +162,6 @@ module Backup
Snippet.id_in(SnippetRepository.for_repository_storage(storage).select(:snippet_id)) Snippet.id_in(SnippetRepository.for_repository_storage(storage).select(:snippet_id))
end end
def restore_object_pools def restore_object_pools
PoolRepository.includes(:source_project).find_each do |pool| PoolRepository.includes(:source_project).find_each do |pool|
progress.puts " - Object pool #{pool.disk_path}..." progress.puts " - Object pool #{pool.disk_path}..."
......
...@@ -3,37 +3,28 @@ ...@@ -3,37 +3,28 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Backup::Repositories do RSpec.describe Backup::Repositories do
let(:progress) { StringIO.new } let(:progress) { spy(:stdout) }
let(:strategy) { spy(:strategy) }
subject { described_class.new(progress) } subject { described_class.new(progress, strategy: strategy) }
before do
allow(progress).to receive(:puts)
allow(progress).to receive(:print)
allow_next_instance_of(described_class) do |instance|
allow(instance).to receive(:progress).and_return(progress)
end
end
describe '#dump' do describe '#dump' do
let_it_be(:projects) { create_list(:project, 5, :repository) } let_it_be(:projects) { create_list(:project, 5, :repository) }
RSpec.shared_examples 'creates repository bundles' do RSpec.shared_examples 'creates repository bundles' do
specify :aggregate_failures do it 'calls enqueue for each repository type', :aggregate_failures do
# Add data to the wiki, design repositories, and snippets, so they will be included in the dump.
create(:wiki_page, container: project)
create(:design, :with_file, issue: create(:issue, project: project))
project_snippet = create(:project_snippet, :repository, project: project) project_snippet = create(:project_snippet, :repository, project: project)
personal_snippet = create(:personal_snippet, :repository, author: project.owner) personal_snippet = create(:personal_snippet, :repository, author: project.owner)
subject.dump(max_concurrency: 1, max_storage_concurrency: 1) subject.dump(max_concurrency: 1, max_storage_concurrency: 1)
expect(File).to exist(File.join(Gitlab.config.backup.path, 'repositories', project.disk_path + '.bundle')) expect(strategy).to have_received(:start).with(:create)
expect(File).to exist(File.join(Gitlab.config.backup.path, 'repositories', project.disk_path + '.wiki' + '.bundle')) expect(strategy).to have_received(:enqueue).with(project, Gitlab::GlRepository::PROJECT)
expect(File).to exist(File.join(Gitlab.config.backup.path, 'repositories', project.disk_path + '.design' + '.bundle')) expect(strategy).to have_received(:enqueue).with(project, Gitlab::GlRepository::WIKI)
expect(File).to exist(File.join(Gitlab.config.backup.path, 'repositories', personal_snippet.disk_path + '.bundle')) expect(strategy).to have_received(:enqueue).with(project, Gitlab::GlRepository::DESIGN)
expect(File).to exist(File.join(Gitlab.config.backup.path, 'repositories', project_snippet.disk_path + '.bundle')) expect(strategy).to have_received(:enqueue).with(project_snippet, Gitlab::GlRepository::SNIPPET)
expect(strategy).to have_received(:enqueue).with(personal_snippet, Gitlab::GlRepository::SNIPPET)
expect(strategy).to have_received(:wait)
end end
end end
...@@ -53,16 +44,18 @@ RSpec.describe Backup::Repositories do ...@@ -53,16 +44,18 @@ RSpec.describe Backup::Repositories do
it 'creates the expected number of threads' do it 'creates the expected number of threads' do
expect(Thread).not_to receive(:new) expect(Thread).not_to receive(:new)
expect(strategy).to receive(:start).with(:create)
projects.each do |project| projects.each do |project|
expect(subject).to receive(:dump_project).with(project).and_call_original expect(strategy).to receive(:enqueue).with(project, Gitlab::GlRepository::PROJECT)
end end
expect(strategy).to receive(:wait)
subject.dump(max_concurrency: 1, max_storage_concurrency: 1) subject.dump(max_concurrency: 1, max_storage_concurrency: 1)
end end
describe 'command failure' do describe 'command failure' do
it 'dump_project raises an error' do it 'dump_project raises an error' do
allow(subject).to receive(:dump_project).and_raise(IOError) allow(strategy).to receive(:enqueue).with(anything, Gitlab::GlRepository::PROJECT).and_raise(IOError)
expect { subject.dump(max_concurrency: 1, max_storage_concurrency: 1) }.to raise_error(IOError) expect { subject.dump(max_concurrency: 1, max_storage_concurrency: 1) }.to raise_error(IOError)
end end
...@@ -100,9 +93,11 @@ RSpec.describe Backup::Repositories do ...@@ -100,9 +93,11 @@ RSpec.describe Backup::Repositories do
.exactly(storage_keys.length * (max_storage_concurrency + 1)).times .exactly(storage_keys.length * (max_storage_concurrency + 1)).times
.and_call_original .and_call_original
expect(strategy).to receive(:start).with(:create)
projects.each do |project| projects.each do |project|
expect(subject).to receive(:dump_project).with(project).and_call_original expect(strategy).to receive(:enqueue).with(project, Gitlab::GlRepository::PROJECT)
end end
expect(strategy).to receive(:wait)
subject.dump(max_concurrency: 1, max_storage_concurrency: max_storage_concurrency) subject.dump(max_concurrency: 1, max_storage_concurrency: max_storage_concurrency)
end end
...@@ -112,17 +107,18 @@ RSpec.describe Backup::Repositories do ...@@ -112,17 +107,18 @@ RSpec.describe Backup::Repositories do
.exactly(storage_keys.length * (max_storage_concurrency + 1)).times .exactly(storage_keys.length * (max_storage_concurrency + 1)).times
.and_call_original .and_call_original
expect(strategy).to receive(:start).with(:create)
projects.each do |project| projects.each do |project|
expect(subject).to receive(:dump_project).with(project).and_call_original expect(strategy).to receive(:enqueue).with(project, Gitlab::GlRepository::PROJECT)
end end
expect(strategy).to receive(:wait)
subject.dump(max_concurrency: 3, max_storage_concurrency: max_storage_concurrency) subject.dump(max_concurrency: 3, max_storage_concurrency: max_storage_concurrency)
end end
describe 'command failure' do describe 'command failure' do
it 'dump_project raises an error' do it 'dump_project raises an error' do
allow(subject).to receive(:dump_project) allow(strategy).to receive(:enqueue).and_raise(IOError)
.and_raise(IOError)
expect { subject.dump(max_concurrency: 1, max_storage_concurrency: max_storage_concurrency) }.to raise_error(IOError) expect { subject.dump(max_concurrency: 1, max_storage_concurrency: max_storage_concurrency) }.to raise_error(IOError)
end end
...@@ -162,61 +158,16 @@ RSpec.describe Backup::Repositories do ...@@ -162,61 +158,16 @@ RSpec.describe Backup::Repositories do
let_it_be(:personal_snippet) { create(:personal_snippet, author: project.owner) } let_it_be(:personal_snippet) { create(:personal_snippet, author: project.owner) }
let_it_be(:project_snippet) { create(:project_snippet, project: project, author: project.owner) } let_it_be(:project_snippet) { create(:project_snippet, project: project, author: project.owner) }
let(:next_path_to_bundle) do it 'calls enqueue for each repository type', :aggregate_failures do
[
Rails.root.join('spec/fixtures/lib/backup/project_repo.bundle'),
Rails.root.join('spec/fixtures/lib/backup/wiki_repo.bundle'),
Rails.root.join('spec/fixtures/lib/backup/design_repo.bundle'),
Rails.root.join('spec/fixtures/lib/backup/personal_snippet_repo.bundle'),
Rails.root.join('spec/fixtures/lib/backup/project_snippet_repo.bundle')
].to_enum
end
it 'restores repositories from bundles', :aggregate_failures do
allow_next_instance_of(::Backup::GitalyRpcBackup::BackupRestore) do |backup_restore|
allow(backup_restore).to receive(:path_to_bundle).and_return(next_path_to_bundle.next)
end
subject.restore subject.restore
collect_commit_shas = -> (repo) { repo.commits('master', limit: 10).map(&:sha) } expect(strategy).to have_received(:start).with(:restore)
expect(strategy).to have_received(:enqueue).with(project, Gitlab::GlRepository::PROJECT)
expect(collect_commit_shas.call(project.repository)).to eq(['393a7d860a5a4c3cc736d7eb00604e3472bb95ec']) expect(strategy).to have_received(:enqueue).with(project, Gitlab::GlRepository::WIKI)
expect(collect_commit_shas.call(project.wiki.repository)).to eq(['c74b9948d0088d703ee1fafeddd9ed9add2901ea']) expect(strategy).to have_received(:enqueue).with(project, Gitlab::GlRepository::DESIGN)
expect(collect_commit_shas.call(project.design_repository)).to eq(['c3cd4d7bd73a51a0f22045c3a4c871c435dc959d']) expect(strategy).to have_received(:enqueue).with(project_snippet, Gitlab::GlRepository::SNIPPET)
expect(collect_commit_shas.call(personal_snippet.repository)).to eq(['3b3c067a3bc1d1b695b51e2be30c0f8cf698a06e']) expect(strategy).to have_received(:enqueue).with(personal_snippet, Gitlab::GlRepository::SNIPPET)
expect(collect_commit_shas.call(project_snippet.repository)).to eq(['6e44ba56a4748be361a841e759c20e421a1651a1']) expect(strategy).to have_received(:wait)
end
describe 'command failure' do
before do
expect(Project).to receive_message_chain(:includes, :find_each).and_yield(project)
allow_next_instance_of(DesignManagement::Repository) do |repository|
allow(repository).to receive(:create_repository) { raise 'Fail in tests' }
end
allow_next_instance_of(Repository) do |repository|
allow(repository).to receive(:create_repository) { raise 'Fail in tests' }
end
end
context 'hashed storage' do
it 'shows the appropriate error' do
subject.restore
expect(progress).to have_received(:puts).with("[Failed] restoring #{project.full_path} (#{project.disk_path})")
end
end
context 'legacy storage' do
let_it_be(:project) { create(:project, :legacy_storage) }
it 'shows the appropriate error' do
subject.restore
expect(progress).to have_received(:puts).with("[Failed] restoring #{project.full_path} (#{project.disk_path})")
end
end
end end
context 'restoring object pools' do context 'restoring object pools' do
...@@ -242,78 +193,36 @@ RSpec.describe Backup::Repositories do ...@@ -242,78 +193,36 @@ RSpec.describe Backup::Repositories do
end end
end end
it 'cleans existing repositories' do context 'cleanup snippets' do
success_response = ServiceResponse.success(message: "Valid Snippet Repo")
allow(Snippets::RepositoryValidationService).to receive_message_chain(:new, :execute).and_return(success_response)
expect_next_instance_of(DesignManagement::Repository) do |repository|
expect(repository).to receive(:remove)
end
# 4 times = project repo + wiki repo + project_snippet repo + personal_snippet repo
expect(Repository).to receive(:new).exactly(4).times.and_wrap_original do |method, *original_args|
full_path, container, kwargs = original_args
repository = method.call(full_path, container, **kwargs)
expect(repository).to receive(:remove)
repository
end
subject.restore
end
context 'restoring snippets' do
before do before do
create(:snippet_repository, snippet: personal_snippet) create(:snippet_repository, snippet: personal_snippet)
create(:snippet_repository, snippet: project_snippet) create(:snippet_repository, snippet: project_snippet)
allow_next_instance_of(::Backup::GitalyRpcBackup::BackupRestore) do |backup_restore| error_response = ServiceResponse.error(message: "Repository has more than one branch")
allow(backup_restore).to receive(:path_to_bundle).and_return(next_path_to_bundle.next) allow(Snippets::RepositoryValidationService).to receive_message_chain(:new, :execute).and_return(error_response)
end
end end
context 'when the repository is valid' do it 'shows the appropriate error' do
it 'restores the snippet repositories' do subject.restore
subject.restore
expect(personal_snippet.snippet_repository.persisted?).to be true
expect(personal_snippet.repository).to exist
expect(project_snippet.snippet_repository.persisted?).to be true expect(progress).to have_received(:puts).with("Snippet #{personal_snippet.full_path} can't be restored: Repository has more than one branch")
expect(project_snippet.repository).to exist expect(progress).to have_received(:puts).with("Snippet #{project_snippet.full_path} can't be restored: Repository has more than one branch")
end
end end
context 'when repository is invalid' do it 'removes the snippets from the DB' do
before do expect { subject.restore }.to change(PersonalSnippet, :count).by(-1)
error_response = ServiceResponse.error(message: "Repository has more than one branch") .and change(ProjectSnippet, :count).by(-1)
allow(Snippets::RepositoryValidationService).to receive_message_chain(:new, :execute).and_return(error_response) .and change(SnippetRepository, :count).by(-2)
end end
it 'shows the appropriate error' do
subject.restore
expect(progress).to have_received(:puts).with("Snippet #{personal_snippet.full_path} can't be restored: Repository has more than one branch")
expect(progress).to have_received(:puts).with("Snippet #{project_snippet.full_path} can't be restored: Repository has more than one branch")
end
it 'removes the snippets from the DB' do
expect { subject.restore }.to change(PersonalSnippet, :count).by(-1)
.and change(ProjectSnippet, :count).by(-1)
.and change(SnippetRepository, :count).by(-2)
end
it 'removes the repository from disk' do it 'removes the repository from disk' do
gitlab_shell = Gitlab::Shell.new gitlab_shell = Gitlab::Shell.new
shard_name = personal_snippet.repository.shard shard_name = personal_snippet.repository.shard
path = personal_snippet.disk_path + '.git' path = personal_snippet.disk_path + '.git'
subject.restore subject.restore
expect(gitlab_shell.repository_exists?(shard_name, path)).to eq false expect(gitlab_shell.repository_exists?(shard_name, path)).to eq false
end
end end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment