diff options
Diffstat (limited to 'spec/services')
21 files changed, 1085 insertions, 68 deletions
diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index b4efa3e44b6..27a7bf0e605 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -48,10 +48,8 @@ describe Boards::Issues::ListService do context 'when parent is a group' do let(:user) { create(:user) } - let(:group) { create(:group) } let(:project) { create(:project, :empty_repo, namespace: group) } let(:project1) { create(:project, :empty_repo, namespace: group) } - let(:board) { create(:board, group: group) } let(:m1) { create(:milestone, group: group) } let(:m2) { create(:milestone, group: group) } @@ -92,13 +90,30 @@ describe Boards::Issues::ListService do let!(:closed_issue4) { create(:labeled_issue, :closed, project: project1, labels: [p1, p1_project1]) } let!(:closed_issue5) { create(:labeled_issue, :closed, project: project1, labels: [development]) } - let(:parent) { group } - before do group.add_developer(user) end - it_behaves_like 'issues list service' + context 'and group has no parent' do + let(:parent) { group } + let(:group) { create(:group) } + let(:board) { create(:board, group: group) } + + it_behaves_like 'issues list service' + end + + context 'and group is an ancestor', :nested_groups do + let(:parent) { create(:group) } + let(:group) { create(:group, parent: parent) } + let!(:backlog) { create(:backlog_list, board: board) } + let(:board) { create(:board, group: parent) } + + before do + parent.add_developer(user) + end + + it_behaves_like 'issues list service' + end end end end diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb index 97a563c1ce1..aa7cc268dd7 100644 --- a/spec/services/ci/register_job_service_spec.rb +++ b/spec/services/ci/register_job_service_spec.rb @@ -370,10 +370,89 @@ module Ci it_behaves_like 'validation is not active' end end + end - def execute(runner) - described_class.new(runner).execute.build + describe '#register_success' do + let!(:current_time) { Time.new(2018, 4, 5, 14, 0, 0) } + let!(:attempt_counter) { double('Gitlab::Metrics::NullMetric') } + let!(:job_queue_duration_seconds) { double('Gitlab::Metrics::NullMetric') } + + before do + allow(Time).to receive(:now).and_return(current_time) + + # Stub defaults for any metrics other than the ones we're testing + allow(Gitlab::Metrics).to receive(:counter) + .with(any_args) + .and_return(Gitlab::Metrics::NullMetric.instance) + allow(Gitlab::Metrics).to receive(:histogram) + .with(any_args) + .and_return(Gitlab::Metrics::NullMetric.instance) + + # Stub tested metrics + allow(Gitlab::Metrics).to receive(:counter) + .with(:job_register_attempts_total, anything) + .and_return(attempt_counter) + allow(Gitlab::Metrics).to receive(:histogram) + .with(:job_queue_duration_seconds, anything, anything, anything) + .and_return(job_queue_duration_seconds) + + project.update(shared_runners_enabled: true) + pending_job.update(created_at: current_time - 3600, queued_at: current_time - 1800) end + + shared_examples 'metrics collector' do + it 'increments attempt counter' do + allow(job_queue_duration_seconds).to receive(:observe) + expect(attempt_counter).to receive(:increment) + + execute(runner) + end + + it 'counts job queuing time histogram with expected labels' do + allow(attempt_counter).to receive(:increment) + expect(job_queue_duration_seconds).to receive(:observe) + .with({ shared_runner: expected_shared_runner, + jobs_running_for_project: expected_jobs_running_for_project_first_job }, 1800) + + execute(runner) + end + + context 'when project already has running jobs' do + let!(:build2) { create( :ci_build, :running, pipeline: pipeline, runner: shared_runner) } + let!(:build3) { create( :ci_build, :running, pipeline: pipeline, runner: shared_runner) } + + it 'counts job queuing time histogram with expected labels' do + allow(attempt_counter).to receive(:increment) + expect(job_queue_duration_seconds).to receive(:observe) + .with({ shared_runner: expected_shared_runner, + jobs_running_for_project: expected_jobs_running_for_project_third_job }, 1800) + + execute(runner) + end + end + end + + context 'when shared runner is used' do + let(:runner) { shared_runner } + let(:expected_shared_runner) { true } + let(:expected_jobs_running_for_project_first_job) { 0 } + let(:expected_jobs_running_for_project_third_job) { 2 } + + it_behaves_like 'metrics collector' + end + + context 'when specific runner is used' do + let(:runner) { specific_runner } + let(:expected_shared_runner) { false } + let(:expected_jobs_running_for_project_first_job) { '+Inf' } + let(:expected_jobs_running_for_project_third_job) { '+Inf' } + + it_behaves_like 'metrics collector' + end + end + + def execute(runner) + described_class.new(runner).execute.build end end end diff --git a/spec/services/issuable/destroy_service_spec.rb b/spec/services/issuable/destroy_service_spec.rb index 0a3647a814f..8ccbba7fa58 100644 --- a/spec/services/issuable/destroy_service_spec.rb +++ b/spec/services/issuable/destroy_service_spec.rb @@ -8,7 +8,7 @@ describe Issuable::DestroyService do describe '#execute' do context 'when issuable is an issue' do - let!(:issue) { create(:issue, project: project, author: user) } + let!(:issue) { create(:issue, project: project, author: user, assignees: [user]) } it 'destroys the issue' do expect { service.execute(issue) }.to change { project.issues.count }.by(-1) @@ -26,10 +26,15 @@ describe Issuable::DestroyService do expect { service.execute(issue) } .to change { user.todos_pending_count }.from(1).to(0) end + + it 'invalidates the issues count cache for the assignees' do + expect_any_instance_of(User).to receive(:invalidate_cache_counts).once + service.execute(issue) + end end context 'when issuable is a merge request' do - let!(:merge_request) { create(:merge_request, target_project: project, source_project: project, author: user) } + let!(:merge_request) { create(:merge_request, target_project: project, source_project: project, author: user, assignee: user) } it 'destroys the merge request' do expect { service.execute(merge_request) }.to change { project.merge_requests.count }.by(-1) @@ -41,6 +46,11 @@ describe Issuable::DestroyService do service.execute(merge_request) end + it 'invalidates the merge request caches for the MR assignee' do + expect_any_instance_of(User).to receive(:invalidate_cache_counts).once + service.execute(merge_request) + end + it 'updates the todo caches for users with todos on the merge request' do create(:todo, target: merge_request, user: user, author: user, project: project) diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index f95474208f3..23b1134b5a3 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -97,11 +97,13 @@ describe Issues::UpdateService, :mailer do expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position) end - context 'when moving issue between issues from different projects' do + context 'when moving issue between issues from different projects', :nested_groups do let(:group) { create(:group) } + let(:subgroup) { create(:group, parent: group) } + let(:project_1) { create(:project, namespace: group) } let(:project_2) { create(:project, namespace: group) } - let(:project_3) { create(:project, namespace: group) } + let(:project_3) { create(:project, namespace: subgroup) } let(:issue_1) { create(:issue, project: project_1) } let(:issue_2) { create(:issue, project: project_2) } diff --git a/spec/services/merge_requests/conflicts/list_service_spec.rb b/spec/services/merge_requests/conflicts/list_service_spec.rb index 6cadcd438c3..837b8a56d12 100644 --- a/spec/services/merge_requests/conflicts/list_service_spec.rb +++ b/spec/services/merge_requests/conflicts/list_service_spec.rb @@ -77,6 +77,14 @@ describe MergeRequests::Conflicts::ListService do expect(service.can_be_resolved_in_ui?).to be_falsey end + it 'returns a falsey value when the MR has a missing revision after a force push' do + merge_request = create_merge_request('conflict-resolvable') + service = conflicts_service(merge_request) + allow(merge_request).to receive_message_chain(:target_branch_head, :raw, :id).and_return(Gitlab::Git::BLANK_SHA) + + expect(service.can_be_resolved_in_ui?).to be_falsey + end + context 'with gitaly disabled', :skip_gitaly_mock do it 'returns a falsey value when the MR has a missing ref after a force push' do merge_request = create_merge_request('conflict-resolvable') @@ -85,6 +93,14 @@ describe MergeRequests::Conflicts::ListService do expect(service.can_be_resolved_in_ui?).to be_falsey end + + it 'returns a falsey value when the MR has a missing revision after a force push' do + merge_request = create_merge_request('conflict-resolvable') + service = conflicts_service(merge_request) + allow(merge_request).to receive_message_chain(:target_branch_head, :raw, :id).and_return(Gitlab::Git::BLANK_SHA) + + expect(service.can_be_resolved_in_ui?).to be_falsey + end end end end diff --git a/spec/services/notes/post_process_service_spec.rb b/spec/services/notes/post_process_service_spec.rb index 6ef5e93cb20..4e2ab919f0f 100644 --- a/spec/services/notes/post_process_service_spec.rb +++ b/spec/services/notes/post_process_service_spec.rb @@ -23,5 +23,23 @@ describe Notes::PostProcessService do described_class.new(@note).execute end + + context 'with a confidential issue' do + let(:issue) { create(:issue, :confidential, project: project) } + + it "doesn't call note hooks/services" do + expect(project).not_to receive(:execute_hooks).with(anything, :note_hooks) + expect(project).not_to receive(:execute_services).with(anything, :note_hooks) + + described_class.new(@note).execute + end + + it "calls confidential-note hooks/services" do + expect(project).to receive(:execute_hooks).with(anything, :confidential_note_hooks) + expect(project).to receive(:execute_services).with(anything, :confidential_note_hooks) + + described_class.new(@note).execute + end + end end end diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index 2cacb97a293..e35f0f6337a 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -28,6 +28,14 @@ describe Projects::CreateService, '#execute' do end end + describe 'after create actions' do + it 'invalidate personal_projects_count caches' do + expect(user).to receive(:invalidate_personal_projects_count) + + create_project(user, opts) + end + end + context "admin creates project with other user's namespace_id" do it 'sets the correct permissions' do admin = create(:admin) diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb index 0bec2054f50..a66e3c5e995 100644 --- a/spec/services/projects/destroy_service_spec.rb +++ b/spec/services/projects/destroy_service_spec.rb @@ -66,6 +66,12 @@ describe Projects::DestroyService do end it_behaves_like 'deleting the project' + + it 'invalidates personal_project_count cache' do + expect(user).to receive(:invalidate_personal_projects_count) + + destroy_project(project, user) + end end context 'Sidekiq fake' do @@ -242,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 6b8f9619bc4..ee1a886f5d6 100644 --- a/spec/services/projects/gitlab_projects_import_service_spec.rb +++ b/spec/services/projects/gitlab_projects_import_service_spec.rb @@ -2,8 +2,11 @@ require 'spec_helper' 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') } - subject { described_class.new(namespace.owner, { 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 context 'with an invalid path' do @@ -18,8 +21,6 @@ describe Projects::GitlabProjectsImportService do end context 'with a valid path' do - let(:path) { 'test-path' } - it 'creates a project' do project = subject.execute @@ -27,5 +28,38 @@ describe Projects::GitlabProjectsImportService do expect(project).to be_valid end end + + context 'override params' do + it 'stores them as import data when passed' do + project = described_class + .new(namespace.owner, import_params, description: 'Hello') + .execute + + 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 diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 95a6771c59d..ff9b2372a35 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -37,6 +37,12 @@ describe Projects::TransferService do transfer_project(project, user, group) end + it 'invalidates the user\'s personal_project_count cache' do + expect(user).to receive(:invalidate_personal_projects_count) + + transfer_project(project, user, group) + end + it 'executes system hooks' do transfer_project(project, user, group) do |service| expect(service).to receive(:execute_system_hooks) diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index dd31a677dfe..1b6caeab15d 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -21,76 +21,72 @@ describe Projects::UpdatePagesService do end context 'legacy artifacts' do - %w(tar.gz zip).each do |format| - let(:extension) { format } + let(:extension) { 'zip' } - context "for valid #{format}" do + before do + build.update_attributes(legacy_artifacts_file: file) + build.update_attributes(legacy_artifacts_metadata: metadata) + end + + describe 'pages artifacts' do + context 'with expiry date' do before do - build.update_attributes(legacy_artifacts_file: file) - build.update_attributes(legacy_artifacts_metadata: metadata) + build.artifacts_expire_in = "2 days" + build.save! end - describe 'pages artifacts' do - context 'with expiry date' do - before do - build.artifacts_expire_in = "2 days" - build.save! - end - - it "doesn't delete artifacts" do - expect(execute).to eq(:success) - - expect(build.reload.artifacts?).to eq(true) - end - end - - context 'without expiry date' do - it "does delete artifacts" do - expect(execute).to eq(:success) + it "doesn't delete artifacts" do + expect(execute).to eq(:success) - expect(build.reload.artifacts?).to eq(false) - end - end + expect(build.reload.artifacts?).to eq(true) end + end - it 'succeeds' do - expect(project.pages_deployed?).to be_falsey + context 'without expiry date' do + it "does delete artifacts" do expect(execute).to eq(:success) - expect(project.pages_deployed?).to be_truthy - # Check that all expected files are extracted - %w[index.html zero .hidden/file].each do |filename| - expect(File.exist?(File.join(project.public_pages_path, filename))).to be_truthy - end + expect(build.reload.artifacts?).to eq(false) end + end + end - it 'limits pages size' do - stub_application_setting(max_pages_size: 1) - expect(execute).not_to eq(:success) - end + it 'succeeds' do + expect(project.pages_deployed?).to be_falsey + expect(execute).to eq(:success) + expect(project.pages_deployed?).to be_truthy - it 'removes pages after destroy' do - expect(PagesWorker).to receive(:perform_in) - expect(project.pages_deployed?).to be_falsey - expect(execute).to eq(:success) - expect(project.pages_deployed?).to be_truthy - project.destroy - expect(project.pages_deployed?).to be_falsey - end + # Check that all expected files are extracted + %w[index.html zero .hidden/file].each do |filename| + expect(File.exist?(File.join(project.public_pages_path, filename))).to be_truthy + end + end - it 'fails if sha on branch is not latest' do - build.update_attributes(ref: 'feature') + it 'limits pages size' do + stub_application_setting(max_pages_size: 1) + expect(execute).not_to eq(:success) + end - expect(execute).not_to eq(:success) - end + it 'removes pages after destroy' do + expect(PagesWorker).to receive(:perform_in) + expect(project.pages_deployed?).to be_falsey + expect(execute).to eq(:success) + expect(project.pages_deployed?).to be_truthy + project.destroy + expect(project.pages_deployed?).to be_falsey + end - it 'fails for empty file fails' do - build.update_attributes(legacy_artifacts_file: empty_file) + it 'fails if sha on branch is not latest' do + build.update_attributes(ref: 'feature') - expect { execute } - .to raise_error(Projects::UpdatePagesService::FailedToExtractError) - end - end + expect(execute).not_to eq(:success) + end + + it 'fails for empty file fails' do + build.update_attributes(legacy_artifacts_file: empty_file) + + expect { execute } + .to raise_error(Projects::UpdatePagesService::FailedToExtractError) end end |