diff options
author | Francisco Javier López <fjlopez@gitlab.com> | 2018-04-06 18:23:49 +0300 |
---|---|---|
committer | Douwe Maan <douwe@gitlab.com> | 2018-04-06 18:23:49 +0300 |
commit | f20912df033d07c46b0989012244d96d0a12b66d (patch) | |
tree | 6207b8face17f9b7166ba1a5e047032e3927e53e /spec/services | |
parent | 44f4a674e2a87d104f700265d835aba000c589f0 (diff) |
Extend API for importing a project export with overwrite support
Diffstat (limited to 'spec/services')
12 files changed, 852 insertions, 1 deletions
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb index 9bb1cda565c..a66e3c5e995 100644 --- a/spec/services/projects/destroy_service_spec.rb +++ b/spec/services/projects/destroy_service_spec.rb @@ -248,6 +248,28 @@ describe Projects::DestroyService do end end + context '#attempt_restore_repositories' do + let(:path) { project.disk_path + '.git' } + + before do + expect(project.gitlab_shell.exists?(project.repository_storage_path, path)).to be_truthy + expect(project.gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey + + # Dont run sidekiq to check if renamed repository exists + Sidekiq::Testing.fake! { destroy_project(project, user, {}) } + + expect(project.gitlab_shell.exists?(project.repository_storage_path, path)).to be_falsey + expect(project.gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_truthy + end + + it 'restores the repositories' do + Sidekiq::Testing.fake! { described_class.new(project, user).attempt_repositories_rollback } + + expect(project.gitlab_shell.exists?(project.repository_storage_path, path)).to be_truthy + expect(project.gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey + end + end + def destroy_project(project, user, params = {}) if async Projects::DestroyService.new(project, user, params).async_execute diff --git a/spec/services/projects/gitlab_projects_import_service_spec.rb b/spec/services/projects/gitlab_projects_import_service_spec.rb index 880b2aae66a..ee1a886f5d6 100644 --- a/spec/services/projects/gitlab_projects_import_service_spec.rb +++ b/spec/services/projects/gitlab_projects_import_service_spec.rb @@ -4,7 +4,8 @@ describe Projects::GitlabProjectsImportService do set(:namespace) { create(:namespace) } let(:path) { 'test-path' } let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') } - let(:import_params) { { namespace_id: namespace.id, path: path, file: file } } + let(:overwrite) { false } + let(:import_params) { { namespace_id: namespace.id, path: path, file: file, overwrite: overwrite } } subject { described_class.new(namespace.owner, import_params) } describe '#execute' do @@ -37,5 +38,28 @@ describe Projects::GitlabProjectsImportService do expect(project.import_data.data['override_params']['description']).to eq('Hello') end end + + context 'when there is a project with the same path' do + let(:existing_project) { create(:project, namespace: namespace) } + let(:path) { existing_project.path} + + it 'does not create the project' do + project = subject.execute + + expect(project).to be_invalid + expect(project).not_to be_persisted + end + + context 'when overwrite param is set' do + let(:overwrite) { true } + + it 'creates a project in a temporary full_path' do + project = subject.execute + + expect(project).to be_valid + expect(project).to be_persisted + end + end + end end end diff --git a/spec/services/projects/move_access_service_spec.rb b/spec/services/projects/move_access_service_spec.rb new file mode 100644 index 00000000000..a820ebd91f4 --- /dev/null +++ b/spec/services/projects/move_access_service_spec.rb @@ -0,0 +1,114 @@ +require 'spec_helper' + +describe Projects::MoveAccessService do + let(:user) { create(:user) } + let(:group) { create(:group) } + let(:project_with_access) { create(:project, namespace: user.namespace) } + let(:master_user) { create(:user) } + let(:reporter_user) { create(:user) } + let(:developer_user) { create(:user) } + let(:master_group) { create(:group) } + let(:reporter_group) { create(:group) } + let(:developer_group) { create(:group) } + + before do + project_with_access.add_master(master_user) + project_with_access.add_developer(developer_user) + project_with_access.add_reporter(reporter_user) + project_with_access.project_group_links.create(group: master_group, group_access: Gitlab::Access::MASTER) + project_with_access.project_group_links.create(group: developer_group, group_access: Gitlab::Access::DEVELOPER) + project_with_access.project_group_links.create(group: reporter_group, group_access: Gitlab::Access::REPORTER) + end + + subject { described_class.new(target_project, user) } + + describe '#execute' do + shared_examples 'move the accesses' do + it do + expect(project_with_access.project_members.count).to eq 4 + expect(project_with_access.project_group_links.count).to eq 3 + expect(project_with_access.authorized_users.count).to eq 4 + + subject.execute(project_with_access) + + expect(project_with_access.project_members.count).to eq 0 + expect(project_with_access.project_group_links.count).to eq 0 + expect(project_with_access.authorized_users.count).to eq 1 + expect(target_project.project_members.count).to eq 4 + expect(target_project.project_group_links.count).to eq 3 + expect(target_project.authorized_users.count).to eq 4 + end + + it 'rollbacks if an exception is raised' do + allow(subject).to receive(:success).and_raise(StandardError) + + expect { subject.execute(project_with_groups) }.to raise_error(StandardError) + + expect(project_with_access.project_members.count).to eq 4 + expect(project_with_access.project_group_links.count).to eq 3 + expect(project_with_access.authorized_users.count).to eq 4 + end + end + + context 'when both projects are in the same namespace' do + let(:target_project) { create(:project, namespace: user.namespace) } + + it 'does not refresh project owner authorized projects' do + allow(project_with_access).to receive(:namespace).and_return(user.namespace) + expect(project_with_access.namespace).not_to receive(:refresh_project_authorizations) + expect(target_project.namespace).not_to receive(:refresh_project_authorizations) + + subject.execute(project_with_access) + end + + it_behaves_like 'move the accesses' + end + + context 'when projects are in different namespaces' do + let(:target_project) { create(:project, namespace: group) } + + before do + group.add_owner(user) + end + + it 'refreshes both project owner authorized projects' do + allow(project_with_access).to receive(:namespace).and_return(user.namespace) + expect(user.namespace).to receive(:refresh_project_authorizations).once + expect(group).to receive(:refresh_project_authorizations).once + + subject.execute(project_with_access) + end + + it_behaves_like 'move the accesses' + end + + context 'when remove_remaining_elements is false' do + let(:target_project) { create(:project, namespace: user.namespace) } + let(:options) { { remove_remaining_elements: false } } + + it 'does not remove remaining memberships' do + target_project.add_master(master_user) + + subject.execute(project_with_access, options) + + expect(project_with_access.project_members.count).not_to eq 0 + end + + it 'does not remove remaining group links' do + target_project.project_group_links.create(group: master_group, group_access: Gitlab::Access::MASTER) + + subject.execute(project_with_access, options) + + expect(project_with_access.project_group_links.count).not_to eq 0 + end + + it 'does not remove remaining authorizations' do + target_project.add_developer(developer_user) + + subject.execute(project_with_access, options) + + expect(project_with_access.project_authorizations.count).not_to eq 0 + end + end + end +end diff --git a/spec/services/projects/move_deploy_keys_projects_service_spec.rb b/spec/services/projects/move_deploy_keys_projects_service_spec.rb new file mode 100644 index 00000000000..c548edf39a8 --- /dev/null +++ b/spec/services/projects/move_deploy_keys_projects_service_spec.rb @@ -0,0 +1,58 @@ +require 'spec_helper' + +describe Projects::MoveDeployKeysProjectsService do + let!(:user) { create(:user) } + let!(:project_with_deploy_keys) { create(:project, namespace: user.namespace) } + let!(:target_project) { create(:project, namespace: user.namespace) } + + subject { described_class.new(target_project, user) } + + describe '#execute' do + before do + create_list(:deploy_keys_project, 2, project: project_with_deploy_keys) + end + + it 'moves the user\'s deploy keys from one project to another' do + expect(project_with_deploy_keys.deploy_keys_projects.count).to eq 2 + expect(target_project.deploy_keys_projects.count).to eq 0 + + subject.execute(project_with_deploy_keys) + + expect(project_with_deploy_keys.deploy_keys_projects.count).to eq 0 + expect(target_project.deploy_keys_projects.count).to eq 2 + end + + it 'does not link existent deploy_keys in the current project' do + target_project.deploy_keys << project_with_deploy_keys.deploy_keys.first + + expect(project_with_deploy_keys.deploy_keys_projects.count).to eq 2 + expect(target_project.deploy_keys_projects.count).to eq 1 + + subject.execute(project_with_deploy_keys) + + expect(project_with_deploy_keys.deploy_keys_projects.count).to eq 0 + expect(target_project.deploy_keys_projects.count).to eq 2 + end + + it 'rollbacks changes if transaction fails' do + allow(subject).to receive(:success).and_raise(StandardError) + + expect { subject.execute(project_with_deploy_keys) }.to raise_error(StandardError) + + expect(project_with_deploy_keys.deploy_keys_projects.count).to eq 2 + expect(target_project.deploy_keys_projects.count).to eq 0 + end + + context 'when remove_remaining_elements is false' do + let(:options) { { remove_remaining_elements: false } } + + it 'does not remove remaining deploy keys projects' do + target_project.deploy_keys << project_with_deploy_keys.deploy_keys.first + + subject.execute(project_with_deploy_keys, options) + + expect(project_with_deploy_keys.deploy_keys_projects.count).not_to eq 0 + end + end + end +end diff --git a/spec/services/projects/move_forks_service_spec.rb b/spec/services/projects/move_forks_service_spec.rb new file mode 100644 index 00000000000..f4a5a7f9fc2 --- /dev/null +++ b/spec/services/projects/move_forks_service_spec.rb @@ -0,0 +1,96 @@ +require 'spec_helper' + +describe Projects::MoveForksService do + include ProjectForksHelper + + let!(:user) { create(:user) } + let!(:project_with_forks) { create(:project, namespace: user.namespace) } + let!(:target_project) { create(:project, namespace: user.namespace) } + let!(:lvl1_forked_project_1) { fork_project(project_with_forks, user) } + let!(:lvl1_forked_project_2) { fork_project(project_with_forks, user) } + let!(:lvl2_forked_project_1_1) { fork_project(lvl1_forked_project_1, user) } + let!(:lvl2_forked_project_1_2) { fork_project(lvl1_forked_project_1, user) } + + subject { described_class.new(target_project, user) } + + describe '#execute' do + context 'when moving a root forked project' do + it 'moves the descendant forks' do + expect(project_with_forks.forks.count).to eq 2 + expect(target_project.forks.count).to eq 0 + + subject.execute(project_with_forks) + + expect(project_with_forks.forks.count).to eq 0 + expect(target_project.forks.count).to eq 2 + expect(lvl1_forked_project_1.forked_from_project).to eq target_project + expect(lvl1_forked_project_1.fork_network_member.forked_from_project).to eq target_project + expect(lvl1_forked_project_2.forked_from_project).to eq target_project + expect(lvl1_forked_project_2.fork_network_member.forked_from_project).to eq target_project + end + + it 'updates the fork network' do + expect(project_with_forks.fork_network.root_project).to eq project_with_forks + expect(project_with_forks.fork_network.fork_network_members.map(&:project)).to include project_with_forks + + subject.execute(project_with_forks) + + expect(target_project.reload.fork_network.root_project).to eq target_project + expect(target_project.fork_network.fork_network_members.map(&:project)).not_to include project_with_forks + end + end + + context 'when moving a intermediate forked project' do + it 'moves the descendant forks' do + expect(lvl1_forked_project_1.forks.count).to eq 2 + expect(target_project.forks.count).to eq 0 + + subject.execute(lvl1_forked_project_1) + + expect(lvl1_forked_project_1.forks.count).to eq 0 + expect(target_project.forks.count).to eq 2 + expect(lvl2_forked_project_1_1.forked_from_project).to eq target_project + expect(lvl2_forked_project_1_1.fork_network_member.forked_from_project).to eq target_project + expect(lvl2_forked_project_1_2.forked_from_project).to eq target_project + expect(lvl2_forked_project_1_2.fork_network_member.forked_from_project).to eq target_project + end + + it 'moves the ascendant fork' do + subject.execute(lvl1_forked_project_1) + + expect(target_project.forked_from_project).to eq project_with_forks + expect(target_project.fork_network_member.forked_from_project).to eq project_with_forks + end + + it 'does not update fork network' do + subject.execute(lvl1_forked_project_1) + + expect(target_project.reload.fork_network.root_project).to eq project_with_forks + end + end + + context 'when moving a leaf forked project' do + it 'moves the ascendant fork' do + subject.execute(lvl2_forked_project_1_1) + + expect(target_project.forked_from_project).to eq lvl1_forked_project_1 + expect(target_project.fork_network_member.forked_from_project).to eq lvl1_forked_project_1 + end + + it 'does not update fork network' do + subject.execute(lvl2_forked_project_1_1) + + expect(target_project.reload.fork_network.root_project).to eq project_with_forks + end + end + + it 'rollbacks changes if transaction fails' do + allow(subject).to receive(:success).and_raise(StandardError) + + expect { subject.execute(project_with_forks) }.to raise_error(StandardError) + + expect(project_with_forks.forks.count).to eq 2 + expect(target_project.forks.count).to eq 0 + end + end +end diff --git a/spec/services/projects/move_lfs_objects_projects_service_spec.rb b/spec/services/projects/move_lfs_objects_projects_service_spec.rb new file mode 100644 index 00000000000..517a24a982a --- /dev/null +++ b/spec/services/projects/move_lfs_objects_projects_service_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +describe Projects::MoveLfsObjectsProjectsService do + let!(:user) { create(:user) } + let!(:project_with_lfs_objects) { create(:project, namespace: user.namespace) } + let!(:target_project) { create(:project, namespace: user.namespace) } + + subject { described_class.new(target_project, user) } + + before do + create_list(:lfs_objects_project, 3, project: project_with_lfs_objects) + end + + describe '#execute' do + it 'links the lfs objects from existent in source project' do + expect(target_project.lfs_objects.count).to eq 0 + + subject.execute(project_with_lfs_objects) + + expect(project_with_lfs_objects.reload.lfs_objects.count).to eq 0 + expect(target_project.reload.lfs_objects.count).to eq 3 + end + + it 'does not link existent lfs_object in the current project' do + target_project.lfs_objects << project_with_lfs_objects.lfs_objects.first(2) + + expect(target_project.lfs_objects.count).to eq 2 + + subject.execute(project_with_lfs_objects) + + expect(target_project.lfs_objects.count).to eq 3 + end + + it 'rollbacks changes if transaction fails' do + allow(subject).to receive(:success).and_raise(StandardError) + + expect { subject.execute(project_with_lfs_objects) }.to raise_error(StandardError) + + expect(project_with_lfs_objects.lfs_objects.count).to eq 3 + expect(target_project.lfs_objects.count).to eq 0 + end + + context 'when remove_remaining_elements is false' do + let(:options) { { remove_remaining_elements: false } } + + it 'does not remove remaining lfs objects' do + target_project.lfs_objects << project_with_lfs_objects.lfs_objects.first(2) + + subject.execute(project_with_lfs_objects, options) + + expect(project_with_lfs_objects.lfs_objects.count).not_to eq 0 + end + end + end +end diff --git a/spec/services/projects/move_notification_settings_service_spec.rb b/spec/services/projects/move_notification_settings_service_spec.rb new file mode 100644 index 00000000000..24d69eef86a --- /dev/null +++ b/spec/services/projects/move_notification_settings_service_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe Projects::MoveNotificationSettingsService do + let(:user) { create(:user) } + let(:project_with_notifications) { create(:project, namespace: user.namespace) } + let(:target_project) { create(:project, namespace: user.namespace) } + + subject { described_class.new(target_project, user) } + + describe '#execute' do + context 'with notification settings' do + before do + create_list(:notification_setting, 2, source: project_with_notifications) + end + + it 'moves the user\'s notification settings from one project to another' do + expect(project_with_notifications.notification_settings.count).to eq 3 + expect(target_project.notification_settings.count).to eq 1 + + subject.execute(project_with_notifications) + + expect(project_with_notifications.notification_settings.count).to eq 0 + expect(target_project.notification_settings.count).to eq 3 + end + + it 'rollbacks changes if transaction fails' do + allow(subject).to receive(:success).and_raise(StandardError) + + expect { subject.execute(project_with_notifications) }.to raise_error(StandardError) + + expect(project_with_notifications.notification_settings.count).to eq 3 + expect(target_project.notification_settings.count).to eq 1 + end + end + + it 'does not move existent notification settings in the current project' do + expect(project_with_notifications.notification_settings.count).to eq 1 + expect(target_project.notification_settings.count).to eq 1 + expect(user.notification_settings.count).to eq 2 + + subject.execute(project_with_notifications) + + expect(user.notification_settings.count).to eq 1 + end + + context 'when remove_remaining_elements is false' do + let(:options) { { remove_remaining_elements: false } } + + it 'does not remove remaining notification settings' do + subject.execute(project_with_notifications, options) + + expect(project_with_notifications.notification_settings.count).not_to eq 0 + end + end + end +end diff --git a/spec/services/projects/move_project_authorizations_service_spec.rb b/spec/services/projects/move_project_authorizations_service_spec.rb new file mode 100644 index 00000000000..f7262b9b887 --- /dev/null +++ b/spec/services/projects/move_project_authorizations_service_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe Projects::MoveProjectAuthorizationsService do + let!(:user) { create(:user) } + let(:project_with_users) { create(:project, namespace: user.namespace) } + let(:target_project) { create(:project, namespace: user.namespace) } + let(:master_user) { create(:user) } + let(:reporter_user) { create(:user) } + let(:developer_user) { create(:user) } + + subject { described_class.new(target_project, user) } + + describe '#execute' do + before do + project_with_users.add_master(master_user) + project_with_users.add_developer(developer_user) + project_with_users.add_reporter(reporter_user) + end + + it 'moves the authorizations from one project to another' do + expect(project_with_users.authorized_users.count).to eq 4 + expect(target_project.authorized_users.count).to eq 1 + + subject.execute(project_with_users) + + expect(project_with_users.authorized_users.count).to eq 0 + expect(target_project.authorized_users.count).to eq 4 + end + + it 'does not move existent authorizations to the current project' do + target_project.add_master(developer_user) + target_project.add_developer(reporter_user) + + expect(project_with_users.authorized_users.count).to eq 4 + expect(target_project.authorized_users.count).to eq 3 + + subject.execute(project_with_users) + + expect(project_with_users.authorized_users.count).to eq 0 + expect(target_project.authorized_users.count).to eq 4 + end + + context 'when remove_remaining_elements is false' do + let(:options) { { remove_remaining_elements: false } } + + it 'does not remove remaining project authorizations' do + target_project.add_master(developer_user) + target_project.add_developer(reporter_user) + + subject.execute(project_with_users, options) + + expect(project_with_users.project_authorizations.count).not_to eq 0 + end + end + end +end diff --git a/spec/services/projects/move_project_group_links_service_spec.rb b/spec/services/projects/move_project_group_links_service_spec.rb new file mode 100644 index 00000000000..e3d06e6d3d7 --- /dev/null +++ b/spec/services/projects/move_project_group_links_service_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' + +describe Projects::MoveProjectGroupLinksService do + let!(:user) { create(:user) } + let(:project_with_groups) { create(:project, namespace: user.namespace) } + let(:target_project) { create(:project, namespace: user.namespace) } + let(:master_group) { create(:group) } + let(:reporter_group) { create(:group) } + let(:developer_group) { create(:group) } + + subject { described_class.new(target_project, user) } + + describe '#execute' do + before do + project_with_groups.project_group_links.create(group: master_group, group_access: Gitlab::Access::MASTER) + project_with_groups.project_group_links.create(group: developer_group, group_access: Gitlab::Access::DEVELOPER) + project_with_groups.project_group_links.create(group: reporter_group, group_access: Gitlab::Access::REPORTER) + end + + it 'moves the group links from one project to another' do + expect(project_with_groups.project_group_links.count).to eq 3 + expect(target_project.project_group_links.count).to eq 0 + + subject.execute(project_with_groups) + + expect(project_with_groups.project_group_links.count).to eq 0 + expect(target_project.project_group_links.count).to eq 3 + end + + it 'does not move existent group links in the current project' do + target_project.project_group_links.create(group: master_group, group_access: Gitlab::Access::MASTER) + target_project.project_group_links.create(group: developer_group, group_access: Gitlab::Access::DEVELOPER) + + expect(project_with_groups.project_group_links.count).to eq 3 + expect(target_project.project_group_links.count).to eq 2 + + subject.execute(project_with_groups) + + expect(project_with_groups.project_group_links.count).to eq 0 + expect(target_project.project_group_links.count).to eq 3 + end + + it 'rollbacks changes if transaction fails' do + allow(subject).to receive(:success).and_raise(StandardError) + + expect { subject.execute(project_with_groups) }.to raise_error(StandardError) + + expect(project_with_groups.project_group_links.count).to eq 3 + expect(target_project.project_group_links.count).to eq 0 + end + + context 'when remove_remaining_elements is false' do + let(:options) { { remove_remaining_elements: false } } + + it 'does not remove remaining project group links' do + target_project.project_group_links.create(group: master_group, group_access: Gitlab::Access::MASTER) + target_project.project_group_links.create(group: developer_group, group_access: Gitlab::Access::DEVELOPER) + + subject.execute(project_with_groups, options) + + expect(project_with_groups.project_group_links.count).not_to eq 0 + end + end + end +end diff --git a/spec/services/projects/move_project_members_service_spec.rb b/spec/services/projects/move_project_members_service_spec.rb new file mode 100644 index 00000000000..9c9a2d2fde1 --- /dev/null +++ b/spec/services/projects/move_project_members_service_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' + +describe Projects::MoveProjectMembersService do + let!(:user) { create(:user) } + let(:project_with_users) { create(:project, namespace: user.namespace) } + let(:target_project) { create(:project, namespace: user.namespace) } + let(:master_user) { create(:user) } + let(:reporter_user) { create(:user) } + let(:developer_user) { create(:user) } + + subject { described_class.new(target_project, user) } + + describe '#execute' do + before do + project_with_users.add_master(master_user) + project_with_users.add_developer(developer_user) + project_with_users.add_reporter(reporter_user) + end + + it 'moves the members from one project to another' do + expect(project_with_users.project_members.count).to eq 4 + expect(target_project.project_members.count).to eq 1 + + subject.execute(project_with_users) + + expect(project_with_users.project_members.count).to eq 0 + expect(target_project.project_members.count).to eq 4 + end + + it 'does not move existent members to the current project' do + target_project.add_master(developer_user) + target_project.add_developer(reporter_user) + + expect(project_with_users.project_members.count).to eq 4 + expect(target_project.project_members.count).to eq 3 + + subject.execute(project_with_users) + + expect(project_with_users.project_members.count).to eq 0 + expect(target_project.project_members.count).to eq 4 + end + + it 'rollbacks changes if transaction fails' do + allow(subject).to receive(:success).and_raise(StandardError) + + expect { subject.execute(project_with_users) }.to raise_error(StandardError) + + expect(project_with_users.project_members.count).to eq 4 + expect(target_project.project_members.count).to eq 1 + end + + context 'when remove_remaining_elements is false' do + let(:options) { { remove_remaining_elements: false } } + + it 'does not remove remaining project members' do + target_project.add_master(developer_user) + target_project.add_developer(reporter_user) + + subject.execute(project_with_users, options) + + expect(project_with_users.project_members.count).not_to eq 0 + end + end + end +end diff --git a/spec/services/projects/move_users_star_projects_service_spec.rb b/spec/services/projects/move_users_star_projects_service_spec.rb new file mode 100644 index 00000000000..e0545c5a21b --- /dev/null +++ b/spec/services/projects/move_users_star_projects_service_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +describe Projects::MoveUsersStarProjectsService do + let!(:user) { create(:user) } + let!(:project_with_stars) { create(:project, namespace: user.namespace) } + let!(:target_project) { create(:project, namespace: user.namespace) } + + subject { described_class.new(target_project, user) } + + describe '#execute' do + before do + create_list(:users_star_project, 2, project: project_with_stars) + end + + it 'moves the user\'s stars from one project to another' do + expect(project_with_stars.users_star_projects.count).to eq 2 + expect(project_with_stars.star_count).to eq 2 + expect(target_project.users_star_projects.count).to eq 0 + expect(target_project.star_count).to eq 0 + + subject.execute(project_with_stars) + project_with_stars.reload + target_project.reload + + expect(project_with_stars.users_star_projects.count).to eq 0 + expect(project_with_stars.star_count).to eq 0 + expect(target_project.users_star_projects.count).to eq 2 + expect(target_project.star_count).to eq 2 + end + + it 'rollbacks changes if transaction fails' do + allow(subject).to receive(:success).and_raise(StandardError) + + expect { subject.execute(project_with_stars) }.to raise_error(StandardError) + + expect(project_with_stars.users_star_projects.count).to eq 2 + expect(project_with_stars.star_count).to eq 2 + expect(target_project.users_star_projects.count).to eq 0 + expect(target_project.star_count).to eq 0 + end + end +end diff --git a/spec/services/projects/overwrite_project_service_spec.rb b/spec/services/projects/overwrite_project_service_spec.rb new file mode 100644 index 00000000000..252c61f4224 --- /dev/null +++ b/spec/services/projects/overwrite_project_service_spec.rb @@ -0,0 +1,198 @@ +require 'spec_helper' + +describe Projects::OverwriteProjectService do + include ProjectForksHelper + + let(:user) { create(:user) } + let(:project_from) { create(:project, namespace: user.namespace) } + let(:project_to) { create(:project, namespace: user.namespace) } + let!(:lvl1_forked_project_1) { fork_project(project_from, user) } + let!(:lvl1_forked_project_2) { fork_project(project_from, user) } + let!(:lvl2_forked_project_1_1) { fork_project(lvl1_forked_project_1, user) } + let!(:lvl2_forked_project_1_2) { fork_project(lvl1_forked_project_1, user) } + + subject { described_class.new(project_to, user) } + + before do + allow(project_to).to receive(:import_data).and_return(double(data: { 'original_path' => project_from.path })) + end + + describe '#execute' do + shared_examples 'overwrite actions' do + it 'moves deploy keys' do + deploy_keys_count = project_from.deploy_keys_projects.count + + subject.execute(project_from) + + expect(project_to.deploy_keys_projects.count).to eq deploy_keys_count + end + + it 'moves notification settings' do + notification_count = project_from.notification_settings.count + + subject.execute(project_from) + + expect(project_to.notification_settings.count).to eq notification_count + end + + it 'moves users stars' do + stars_count = project_from.users_star_projects.count + + subject.execute(project_from) + project_to.reload + + expect(project_to.users_star_projects.count).to eq stars_count + expect(project_to.star_count).to eq stars_count + end + + it 'moves project group links' do + group_links_count = project_from.project_group_links.count + + subject.execute(project_from) + + expect(project_to.project_group_links.count).to eq group_links_count + end + + it 'moves memberships and authorizations' do + members_count = project_from.project_members.count + project_authorizations = project_from.project_authorizations.count + + subject.execute(project_from) + + expect(project_to.project_members.count).to eq members_count + expect(project_to.project_authorizations.count).to eq project_authorizations + end + + context 'moves lfs objects relationships' do + before do + create_list(:lfs_objects_project, 3, project: project_from) + end + + it do + lfs_objects_count = project_from.lfs_objects.count + + subject.execute(project_from) + + expect(project_to.lfs_objects.count).to eq lfs_objects_count + end + end + + it 'removes the original project' do + subject.execute(project_from) + + expect { Project.find(project_from.id) }.to raise_error(ActiveRecord::RecordNotFound) + end + + it 'renames the project' do + subject.execute(project_from) + + expect(project_to.full_path).to eq project_from.full_path + end + end + + context 'when project does not have any relation' do + it_behaves_like 'overwrite actions' + end + + context 'when project with elements' do + it_behaves_like 'overwrite actions' do + let(:master_user) { create(:user) } + let(:reporter_user) { create(:user) } + let(:developer_user) { create(:user) } + let(:master_group) { create(:group) } + let(:reporter_group) { create(:group) } + let(:developer_group) { create(:group) } + + before do + create_list(:deploy_keys_project, 2, project: project_from) + create_list(:notification_setting, 2, source: project_from) + create_list(:users_star_project, 2, project: project_from) + project_from.project_group_links.create(group: master_group, group_access: Gitlab::Access::MASTER) + project_from.project_group_links.create(group: developer_group, group_access: Gitlab::Access::DEVELOPER) + project_from.project_group_links.create(group: reporter_group, group_access: Gitlab::Access::REPORTER) + project_from.add_master(master_user) + project_from.add_developer(developer_user) + project_from.add_reporter(reporter_user) + end + end + end + + context 'forks' do + context 'when moving a root forked project' do + it 'moves the descendant forks' do + expect(project_from.forks.count).to eq 2 + expect(project_to.forks.count).to eq 0 + + subject.execute(project_from) + + expect(project_from.forks.count).to eq 0 + expect(project_to.forks.count).to eq 2 + expect(lvl1_forked_project_1.forked_from_project).to eq project_to + expect(lvl1_forked_project_1.fork_network_member.forked_from_project).to eq project_to + expect(lvl1_forked_project_2.forked_from_project).to eq project_to + expect(lvl1_forked_project_2.fork_network_member.forked_from_project).to eq project_to + end + + it 'updates the fork network' do + expect(project_from.fork_network.root_project).to eq project_from + expect(project_from.fork_network.fork_network_members.map(&:project)).to include project_from + + subject.execute(project_from) + + expect(project_to.reload.fork_network.root_project).to eq project_to + expect(project_to.fork_network.fork_network_members.map(&:project)).not_to include project_from + end + end + context 'when moving a intermediate forked project' do + let(:project_to) { create(:project, namespace: lvl1_forked_project_1.namespace) } + + it 'moves the descendant forks' do + expect(lvl1_forked_project_1.forks.count).to eq 2 + expect(project_to.forks.count).to eq 0 + + subject.execute(lvl1_forked_project_1) + + expect(lvl1_forked_project_1.forks.count).to eq 0 + expect(project_to.forks.count).to eq 2 + expect(lvl2_forked_project_1_1.forked_from_project).to eq project_to + expect(lvl2_forked_project_1_1.fork_network_member.forked_from_project).to eq project_to + expect(lvl2_forked_project_1_2.forked_from_project).to eq project_to + expect(lvl2_forked_project_1_2.fork_network_member.forked_from_project).to eq project_to + end + + it 'moves the ascendant fork' do + subject.execute(lvl1_forked_project_1) + + expect(project_to.reload.forked_from_project).to eq project_from + expect(project_to.fork_network_member.forked_from_project).to eq project_from + end + + it 'does not update fork network' do + subject.execute(lvl1_forked_project_1) + + expect(project_to.reload.fork_network.root_project).to eq project_from + end + end + end + + context 'if an exception is raised' do + it 'rollbacks changes' do + updated_at = project_from.updated_at + + allow(subject).to receive(:rename_project).and_raise(StandardError) + + expect { subject.execute(project_from) }.to raise_error(StandardError) + expect(Project.find(project_from.id)).not_to be_nil + expect(project_from.reload.updated_at.change(usec: 0)).to eq updated_at.change(usec: 0) + end + + it 'tries to restore the original project repositories' do + allow(subject).to receive(:rename_project).and_raise(StandardError) + + expect(subject).to receive(:attempt_restore_repositories).with(project_from) + + expect { subject.execute(project_from) }.to raise_error(StandardError) + end + end + end +end |