debian-mirror-gitlab/spec/services/members/destroy_service_spec.rb

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

585 lines
20 KiB
Ruby
Raw Normal View History

2019-07-31 22:56:46 +05:30
# frozen_string_literal: true
2016-08-24 12:49:21 +05:30
require 'spec_helper'
2023-03-17 16:20:25 +05:30
RSpec.describe Members::DestroyService, feature_category: :subgroups do
2018-03-27 19:54:05 +05:30
let(:current_user) { create(:user) }
2016-11-03 12:29:30 +05:30
let(:member_user) { create(:user) }
let(:group) { create(:group, :public) }
2018-03-27 19:54:05 +05:30
let(:group_project) { create(:project, :public, group: group) }
let(:opts) { {} }
2016-08-24 12:49:21 +05:30
2016-11-03 12:29:30 +05:30
shared_examples 'a service raising ActiveRecord::RecordNotFound' do
it 'raises ActiveRecord::RecordNotFound' do
2018-03-27 19:54:05 +05:30
expect { described_class.new(current_user).execute(member) }.to raise_error(ActiveRecord::RecordNotFound)
2016-08-24 12:49:21 +05:30
end
2016-11-03 12:29:30 +05:30
end
2016-08-24 12:49:21 +05:30
2016-11-03 12:29:30 +05:30
shared_examples 'a service raising Gitlab::Access::AccessDeniedError' do
it 'raises Gitlab::Access::AccessDeniedError' do
2018-03-27 19:54:05 +05:30
expect { described_class.new(current_user).execute(member) }.to raise_error(Gitlab::Access::AccessDeniedError)
2016-08-24 12:49:21 +05:30
end
end
2016-11-03 12:29:30 +05:30
shared_examples 'a service destroying a member' do
2018-11-18 11:00:15 +05:30
before do
type = member.is_a?(GroupMember) ? 'Group' : 'Project'
2019-01-03 12:48:30 +05:30
expect(TodosDestroyer::EntityLeaveWorker).to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, type)
2020-07-28 23:09:34 +05:30
expect(MembersDestroyer::UnassignIssuablesWorker).to receive(:perform_async).with(member.user_id, member.source_id, type) if opts[:unassign_issuables]
2018-11-18 11:00:15 +05:30
end
2016-11-03 12:29:30 +05:30
it 'destroys the member' do
2021-01-03 14:25:43 +05:30
expect { described_class.new(current_user).execute(member, **opts) }.to change { member.source.members_and_requesters.count }.by(-1)
2016-11-03 12:29:30 +05:30
end
2018-03-27 19:54:05 +05:30
it 'destroys member notification_settings' do
if member_user.notification_settings.any?
2021-01-03 14:25:43 +05:30
expect { described_class.new(current_user).execute(member, **opts) }
2018-03-27 19:54:05 +05:30
.to change { member_user.notification_settings.count }.by(-1)
else
2021-01-03 14:25:43 +05:30
expect { described_class.new(current_user).execute(member, **opts) }
2018-03-27 19:54:05 +05:30
.not_to change { member_user.notification_settings.count }
2016-11-03 12:29:30 +05:30
end
2018-03-27 19:54:05 +05:30
end
end
2016-11-03 12:29:30 +05:30
2018-03-27 19:54:05 +05:30
shared_examples 'a service destroying a member with access' do
it_behaves_like 'a service destroying a member'
2016-11-03 12:29:30 +05:30
2019-12-26 22:10:19 +05:30
it 'invalidates cached counts for assigned issues and merge requests', :aggregate_failures, :sidekiq_might_not_need_inline do
2018-03-27 19:54:05 +05:30
create(:issue, project: group_project, assignees: [member_user])
2019-07-31 22:56:46 +05:30
create(:merge_request, source_project: group_project, assignees: [member_user])
2018-03-27 19:54:05 +05:30
create(:todo, :pending, project: group_project, user: member_user)
create(:todo, :done, project: group_project, user: member_user)
2016-11-03 12:29:30 +05:30
2018-03-27 19:54:05 +05:30
expect(member_user.assigned_open_merge_requests_count).to be(1)
expect(member_user.assigned_open_issues_count).to be(1)
expect(member_user.todos_pending_count).to be(1)
expect(member_user.todos_done_count).to be(1)
2016-11-03 12:29:30 +05:30
2020-07-28 23:09:34 +05:30
service = described_class.new(current_user)
if opts[:unassign_issuables]
expect(service).to receive(:enqueue_unassign_issuables).with(member)
end
2021-01-03 14:25:43 +05:30
service.execute(member, **opts)
2016-11-03 12:29:30 +05:30
2018-03-27 19:54:05 +05:30
expect(member_user.assigned_open_merge_requests_count).to be(0)
expect(member_user.assigned_open_issues_count).to be(0)
expect(member_user.todos_pending_count).to be(0)
expect(member_user.todos_done_count).to be(0)
2020-07-28 23:09:34 +05:30
unless opts[:unassign_issuables]
expect(member_user.assigned_merge_requests.opened.count).to be(1)
expect(member_user.assigned_issues.opened.count).to be(1)
end
2018-03-27 19:54:05 +05:30
end
end
2016-11-03 12:29:30 +05:30
2023-01-13 00:05:48 +05:30
shared_examples 'a service destroying an access request of another user' do
2018-03-27 19:54:05 +05:30
it_behaves_like 'a service destroying a member'
it 'calls Member#after_decline_request' do
expect_any_instance_of(NotificationService).to receive(:decline_access_request).with(member)
2021-01-03 14:25:43 +05:30
described_class.new(current_user).execute(member, **opts)
2018-03-27 19:54:05 +05:30
end
2023-01-13 00:05:48 +05:30
end
shared_examples 'a service destroying an access request of self' do
it_behaves_like 'a service destroying a member'
2018-03-27 19:54:05 +05:30
context 'when current user is the member' do
it 'does not call Member#after_decline_request' do
expect_any_instance_of(NotificationService).not_to receive(:decline_access_request).with(member)
2023-01-13 00:05:48 +05:30
described_class.new(current_user).execute(member, **opts)
2016-11-03 12:29:30 +05:30
end
2016-08-24 12:49:21 +05:30
end
2016-11-03 12:29:30 +05:30
end
2022-11-25 23:54:43 +05:30
context 'With ExclusiveLeaseHelpers' do
2023-03-17 16:20:25 +05:30
include ExclusiveLeaseHelpers
let(:lock_key) do
"delete_members:#{member_to_delete.source.class}:#{member_to_delete.source.id}"
end
let(:timeout) { 1.minute }
2022-11-25 23:54:43 +05:30
let(:service_object) { described_class.new(current_user) }
2023-03-17 16:20:25 +05:30
subject(:destroy_member) { service_object.execute(member_to_delete, **opts) }
2022-11-25 23:54:43 +05:30
2023-03-17 16:20:25 +05:30
shared_examples_for 'deletes the member without using a lock' do
it 'does not try to perform the delete within a lock' do
# `UpdateHighestRole` concern also uses locks to peform work
# whenever a Member is committed, so that needs to be accounted for.
lock_key_for_update_highest_role = "update_highest_role:#{member_to_delete.user_id}"
expect(Gitlab::ExclusiveLease)
.to receive(:new).with(lock_key_for_update_highest_role, timeout: 10.minutes.to_i).and_call_original
# We do not use any locks for member deletion process.
expect(Gitlab::ExclusiveLease)
.not_to receive(:new).with(lock_key, timeout: timeout)
destroy_member
end
2022-11-25 23:54:43 +05:30
2023-03-17 16:20:25 +05:30
it 'destroys the membership' do
expect { destroy_member }.to change { entity.members.count }.by(-1)
2022-11-25 23:54:43 +05:30
end
end
2023-03-17 16:20:25 +05:30
context 'for group members' do
before do
group.add_owner(current_user)
end
context 'deleting group owners' do
let!(:member_to_delete) { group.add_owner(member_user) }
2022-11-25 23:54:43 +05:30
2023-03-17 16:20:25 +05:30
context 'locking to avoid race conditions' do
it 'tries to perform the delete within a lock' do
expect_to_obtain_exclusive_lease(lock_key, timeout: timeout)
destroy_member
end
context 'based on status of the lock' do
context 'when lock is obtained' do
it 'destroys the membership' do
expect_to_obtain_exclusive_lease(lock_key, timeout: timeout)
expect { destroy_member }.to change { group.members.count }.by(-1)
end
end
context 'when the lock cannot be obtained' do
before do
stub_exclusive_lease_taken(lock_key, timeout: timeout)
end
it 'raises error' do
expect { destroy_member }.to raise_error(Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError)
end
end
end
end
end
context 'deleting group members that are not owners' do
let!(:member_to_delete) { group.add_developer(member_user) }
it_behaves_like 'deletes the member without using a lock' do
let(:entity) { group }
end
2022-11-25 23:54:43 +05:30
end
end
2023-03-17 16:20:25 +05:30
context 'for project members' do
before do
group_project.add_owner(current_user)
end
context 'deleting project owners' do
context 'deleting project owners' do
let!(:member_to_delete) { entity.add_owner(member_user) }
it_behaves_like 'deletes the member without using a lock' do
let(:entity) { group_project }
end
end
end
context 'deleting project memebrs that are not owners' do
let!(:member_to_delete) { group_project.add_developer(member_user) }
2022-11-25 23:54:43 +05:30
2023-03-17 16:20:25 +05:30
it_behaves_like 'deletes the member without using a lock' do
let(:entity) { group_project }
end
2022-11-25 23:54:43 +05:30
end
end
end
2018-03-27 19:54:05 +05:30
context 'with a member with access' do
before do
group_project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
group.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
end
context 'when current user cannot destroy the given member' do
context 'with a project member' do
let(:member) { group_project.members.find_by(user_id: member_user.id) }
2022-07-23 23:45:48 +05:30
context 'when current user does not have any membership management permissions' do
before do
group_project.add_developer(member_user)
end
it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
context 'when skipping authorisation' do
it_behaves_like 'a service destroying a member with access' do
let(:opts) { { skip_authorization: true, unassign_issuables: true } }
end
end
2018-03-27 19:54:05 +05:30
end
2022-07-23 23:45:48 +05:30
context 'when a project maintainer tries to destroy a project owner' do
before do
group_project.add_owner(member_user)
end
2016-08-24 12:49:21 +05:30
2022-07-23 23:45:48 +05:30
it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
context 'when skipping authorisation' do
it_behaves_like 'a service destroying a member with access' do
let(:opts) { { skip_authorization: true, unassign_issuables: true } }
end
end
2018-03-27 19:54:05 +05:30
end
end
2022-07-23 23:45:48 +05:30
end
2018-03-27 19:54:05 +05:30
2022-07-23 23:45:48 +05:30
context 'with a group member' do
let(:member) { group.members.find_by(user_id: member_user.id) }
2018-03-27 19:54:05 +05:30
2022-07-23 23:45:48 +05:30
before do
group.add_developer(member_user)
end
2018-03-27 19:54:05 +05:30
2022-07-23 23:45:48 +05:30
it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
2018-03-27 19:54:05 +05:30
2022-07-23 23:45:48 +05:30
context 'when skipping authorisation' do
2018-03-27 19:54:05 +05:30
it_behaves_like 'a service destroying a member with access' do
2020-07-28 23:09:34 +05:30
let(:opts) { { skip_authorization: true, unassign_issuables: true } }
2018-03-27 19:54:05 +05:30
end
end
2016-11-03 12:29:30 +05:30
end
2018-03-27 19:54:05 +05:30
context 'when current user can destroy the given member' do
before do
2018-11-18 11:00:15 +05:30
group_project.add_maintainer(current_user)
2018-03-27 19:54:05 +05:30
group.add_owner(current_user)
end
context 'with a project member' do
let(:member) { group_project.members.find_by(user_id: member_user.id) }
before do
group_project.add_developer(member_user)
end
it_behaves_like 'a service destroying a member with access'
2020-07-28 23:09:34 +05:30
context 'unassign issuables' do
it_behaves_like 'a service destroying a member with access' do
let(:opts) { { unassign_issuables: true } }
end
end
end
context 'with a project bot member' do
let(:member) { group_project.members.find_by(user_id: member_user.id) }
let(:member_user) { create(:user, :project_bot) }
before do
group_project.add_maintainer(member_user)
end
context 'when the destroy_bot flag is true' do
it_behaves_like 'a service destroying a member with access' do
let(:opts) { { destroy_bot: true } }
end
end
context 'when the destroy_bot flag is not specified' do
it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
end
2018-03-27 19:54:05 +05:30
end
context 'with a group member' do
let(:member) { group.members.find_by(user_id: member_user.id) }
before do
group.add_developer(member_user)
end
it_behaves_like 'a service destroying a member with access'
2020-07-28 23:09:34 +05:30
context 'unassign issuables' do
it_behaves_like 'a service destroying a member with access' do
let(:opts) { { unassign_issuables: true } }
end
end
2018-03-27 19:54:05 +05:30
end
2016-08-24 12:49:21 +05:30
end
end
2018-03-27 19:54:05 +05:30
context 'with an access requester' do
2016-08-24 12:49:21 +05:30
before do
2020-11-24 15:15:51 +05:30
group_project.update!(request_access_enabled: true)
group.update!(request_access_enabled: true)
2018-03-27 19:54:05 +05:30
group_project.request_access(member_user)
group.request_access(member_user)
2016-08-24 12:49:21 +05:30
end
2018-03-27 19:54:05 +05:30
context 'when current user cannot destroy the given access requester' do
2016-11-03 12:29:30 +05:30
it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
2018-03-27 19:54:05 +05:30
let(:member) { group_project.requesters.find_by(user_id: member_user.id) }
end
it_behaves_like 'a service destroying a member' do
2019-01-03 12:48:30 +05:30
let(:opts) { { skip_authorization: true, skip_subresources: true } }
2018-03-27 19:54:05 +05:30
let(:member) { group_project.requesters.find_by(user_id: member_user.id) }
2016-11-03 12:29:30 +05:30
end
2016-08-24 12:49:21 +05:30
2016-11-03 12:29:30 +05:30
it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
2018-03-27 19:54:05 +05:30
let(:member) { group.requesters.find_by(user_id: member_user.id) }
end
it_behaves_like 'a service destroying a member' do
2019-03-02 22:35:43 +05:30
let(:opts) { { skip_authorization: true, skip_subresources: true } }
2018-03-27 19:54:05 +05:30
let(:member) { group.requesters.find_by(user_id: member_user.id) }
2016-11-03 12:29:30 +05:30
end
2016-08-24 12:49:21 +05:30
end
2018-03-27 19:54:05 +05:30
context 'when current user can destroy the given access requester' do
2019-01-03 12:48:30 +05:30
let(:opts) { { skip_subresources: true } }
2016-08-24 12:49:21 +05:30
before do
2018-11-18 11:00:15 +05:30
group_project.add_maintainer(current_user)
2018-03-27 19:54:05 +05:30
group.add_owner(current_user)
end
2023-01-13 00:05:48 +05:30
it_behaves_like 'a service destroying an access request of another user' do
let(:member) { group_project.requesters.find_by(user_id: member_user.id) }
end
it_behaves_like 'a service destroying an access request of another user' do
let(:member) { group.requesters.find_by(user_id: member_user.id) }
end
end
context 'on withdrawing their own access request' do
let(:opts) { { skip_subresources: true } }
let(:current_user) { member_user }
it_behaves_like 'a service destroying an access request of self' do
2018-03-27 19:54:05 +05:30
let(:member) { group_project.requesters.find_by(user_id: member_user.id) }
end
2023-01-13 00:05:48 +05:30
it_behaves_like 'a service destroying an access request of self' do
2018-03-27 19:54:05 +05:30
let(:member) { group.requesters.find_by(user_id: member_user.id) }
end
end
end
context 'with an invited user' do
let(:project_invited_member) { create(:project_member, :invited, project: group_project) }
let(:group_invited_member) { create(:group_member, :invited, group: group) }
context 'when current user cannot destroy the given invited user' do
it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
let(:member) { project_invited_member }
2016-08-24 12:49:21 +05:30
end
2016-11-03 12:29:30 +05:30
it_behaves_like 'a service destroying a member' do
2018-03-27 19:54:05 +05:30
let(:opts) { { skip_authorization: true } }
let(:member) { project_invited_member }
end
it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
let(:member) { group_invited_member }
2016-08-24 12:49:21 +05:30
end
2016-11-03 12:29:30 +05:30
it_behaves_like 'a service destroying a member' do
2018-03-27 19:54:05 +05:30
let(:opts) { { skip_authorization: true } }
let(:member) { group_invited_member }
2016-08-24 12:49:21 +05:30
end
2018-03-27 19:54:05 +05:30
end
2016-08-24 12:49:21 +05:30
2018-03-27 19:54:05 +05:30
context 'when current user can destroy the given invited user' do
before do
2018-11-18 11:00:15 +05:30
group_project.add_maintainer(current_user)
2018-03-27 19:54:05 +05:30
group.add_owner(current_user)
end
2016-08-24 12:49:21 +05:30
2019-12-04 20:38:33 +05:30
# Regression spec for issue: https://gitlab.com/gitlab-org/gitlab-foss/issues/32504
2018-03-27 19:54:05 +05:30
it_behaves_like 'a service destroying a member' do
let(:member) { project_invited_member }
end
it_behaves_like 'a service destroying a member' do
let(:member) { group_invited_member }
2016-08-24 12:49:21 +05:30
end
end
end
2019-01-03 12:48:30 +05:30
context 'subresources' do
let(:user) { create(:user) }
let(:member_user) { create(:user) }
let(:group) { create(:group, :public) }
let(:subgroup) { create(:group, parent: group) }
let(:subsubgroup) { create(:group, parent: subgroup) }
let(:subsubproject) { create(:project, group: subsubgroup) }
let(:group_project) { create(:project, :public, group: group) }
let(:control_project) { create(:project, group: subsubgroup) }
2021-04-29 21:17:54 +05:30
context 'with memberships' do
before do
subgroup.add_developer(member_user)
subsubgroup.add_developer(member_user)
subsubproject.add_developer(member_user)
group_project.add_developer(member_user)
control_project.add_maintainer(user)
group.add_owner(user)
@group_member = create(:group_member, :developer, group: group, user: member_user)
end
2019-01-03 12:48:30 +05:30
2021-04-29 21:17:54 +05:30
context 'with skipping of subresources' do
before do
described_class.new(user).execute(@group_member, skip_subresources: true)
end
2019-01-03 12:48:30 +05:30
2021-04-29 21:17:54 +05:30
it 'removes the group membership' do
expect(group.members.map(&:user)).not_to include(member_user)
end
2019-01-03 12:48:30 +05:30
2021-04-29 21:17:54 +05:30
it 'does not remove the project membership' do
expect(group_project.members.map(&:user)).to include(member_user)
end
2019-01-03 12:48:30 +05:30
2021-04-29 21:17:54 +05:30
it 'does not remove the subgroup membership' do
expect(subgroup.members.map(&:user)).to include(member_user)
end
2019-01-03 12:48:30 +05:30
2021-04-29 21:17:54 +05:30
it 'does not remove the subsubgroup membership' do
expect(subsubgroup.members.map(&:user)).to include(member_user)
end
2019-01-03 12:48:30 +05:30
2021-04-29 21:17:54 +05:30
it 'does not remove the subsubproject membership' do
expect(subsubproject.members.map(&:user)).to include(member_user)
end
2019-01-03 12:48:30 +05:30
2021-04-29 21:17:54 +05:30
it 'does not remove the user from the control project' do
expect(control_project.members.map(&:user)).to include(user)
end
end
2019-01-03 12:48:30 +05:30
2021-04-29 21:17:54 +05:30
context 'without skipping of subresources' do
before do
described_class.new(user).execute(@group_member, skip_subresources: false)
end
2019-01-03 12:48:30 +05:30
2021-04-29 21:17:54 +05:30
it 'removes the project membership' do
expect(group_project.members.map(&:user)).not_to include(member_user)
end
2020-09-03 11:15:55 +05:30
2021-04-29 21:17:54 +05:30
it 'removes the group membership' do
expect(group.members.map(&:user)).not_to include(member_user)
end
2020-09-03 11:15:55 +05:30
2021-04-29 21:17:54 +05:30
it 'removes the subgroup membership' do
expect(subgroup.members.map(&:user)).not_to include(member_user)
end
it 'removes the subsubgroup membership' do
expect(subsubgroup.members.map(&:user)).not_to include(member_user)
end
it 'removes the subsubproject membership' do
expect(subsubproject.members.map(&:user)).not_to include(member_user)
end
2020-09-03 11:15:55 +05:30
2021-04-29 21:17:54 +05:30
it 'does not remove the user from the control project' do
expect(control_project.members.map(&:user)).to include(user)
end
end
2020-09-03 11:15:55 +05:30
end
2021-04-29 21:17:54 +05:30
context 'with invites' do
before do
create(:group_member, :developer, group: subsubgroup, user: member_user)
create(:project_member, :invited, project: group_project, created_by: member_user)
create(:group_member, :invited, group: group, created_by: member_user)
create(:project_member, :invited, project: subsubproject, created_by: member_user)
create(:group_member, :invited, group: subgroup, created_by: member_user)
subsubproject.add_developer(member_user)
control_project.add_maintainer(user)
group.add_owner(user)
@group_member = create(:group_member, :developer, group: group, user: member_user)
end
context 'with skipping of subresources' do
before do
described_class.new(user).execute(@group_member, skip_subresources: true)
end
it 'does not remove group members invited by deleted user' do
expect(group.members.not_accepted_invitations_by_user(member_user)).not_to be_empty
end
it 'does not remove project members invited by deleted user' do
expect(group_project.members.not_accepted_invitations_by_user(member_user)).not_to be_empty
end
it 'does not remove subgroup members invited by deleted user' do
expect(subgroup.members.not_accepted_invitations_by_user(member_user)).not_to be_empty
end
it 'does not remove subproject members invited by deleted user' do
expect(subsubproject.members.not_accepted_invitations_by_user(member_user)).not_to be_empty
end
end
context 'without skipping of subresources' do
before do
described_class.new(user).execute(@group_member, skip_subresources: false)
end
it 'removes group members invited by deleted user' do
expect(group.members.not_accepted_invitations_by_user(member_user)).to be_empty
end
it 'removes project members invited by deleted user' do
expect(group_project.members.not_accepted_invitations_by_user(member_user)).to be_empty
end
it 'removes subgroup members invited by deleted user' do
expect(subgroup.members.not_accepted_invitations_by_user(member_user)).to be_empty
end
it 'removes subproject members invited by deleted user' do
expect(subsubproject.members.not_accepted_invitations_by_user(member_user)).to be_empty
end
end
2020-09-03 11:15:55 +05:30
end
end
context 'deletion of invitations created by deleted project member' do
2022-03-02 08:16:31 +05:30
let(:user) { project.first_owner }
2020-09-03 11:15:55 +05:30
let(:member_user) { create(:user) }
let(:project) { create(:project) }
before do
create(:project_member, :invited, project: project, created_by: member_user)
project_member = create(:project_member, :maintainer, user: member_user, project: project)
2021-01-03 14:25:43 +05:30
described_class.new(user).execute(project_member)
2020-09-03 11:15:55 +05:30
end
it 'removes project members invited by deleted user' do
expect(project.members.not_accepted_invitations_by_user(member_user)).to be_empty
end
2019-01-03 12:48:30 +05:30
end
2016-08-24 12:49:21 +05:30
end