gitlab-org--gitlab-foss/spec/services/members/update_service_spec.rb

365 lines
11 KiB
Ruby

# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Members::UpdateService do
let_it_be(:project) { create(:project, :public) }
let_it_be(:group) { create(:group, :public) }
let_it_be(:current_user) { create(:user) }
let_it_be(:member_user1) { create(:user) }
let_it_be(:member_user2) { create(:user) }
let_it_be(:member_users) { [member_user1, member_user2] }
let_it_be(:permission) { :update }
let_it_be(:access_level) { Gitlab::Access::MAINTAINER }
let(:members) { source.members_and_requesters.where(user_id: member_users).to_a }
let(:update_service) { described_class.new(current_user, params) }
let(:params) { { access_level: access_level } }
let(:updated_members) do
result = subject
Array.wrap(result[:members] || result[:member])
end
before do
member_users.first.tap do |member_user|
expires_at = 10.days.from_now
project.add_member(member_user, Gitlab::Access::DEVELOPER, expires_at: expires_at)
group.add_member(member_user, Gitlab::Access::DEVELOPER, expires_at: expires_at)
end
member_users[1..].each do |member_user|
project.add_developer(member_user)
group.add_developer(member_user)
end
end
shared_examples 'a service raising Gitlab::Access::AccessDeniedError' do
it 'raises Gitlab::Access::AccessDeniedError' do
expect { subject }
.to raise_error(Gitlab::Access::AccessDeniedError)
end
end
shared_examples 'current user cannot update the given members' do
it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
let_it_be(:source) { project }
end
it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
let_it_be(:source) { group }
end
end
shared_examples 'returns error status when params are invalid' do
let_it_be(:params) { { expires_at: 2.days.ago } }
specify do
expect(subject[:status]).to eq(:error)
end
end
shared_examples 'a service updating members' do
it 'updates the members' do
new_access_levels = updated_members.map(&:access_level)
expect(updated_members).not_to be_empty
expect(updated_members).to all(be_valid)
expect(new_access_levels).to all(be access_level)
end
it 'returns success status' do
expect(subject.fetch(:status)).to eq(:success)
end
it 'invokes after_execute with correct args' do
members.each do |member|
expect(update_service).to receive(:after_execute).with(
action: permission,
old_access_level: member.human_access,
old_expiry: member.expires_at,
member: member
)
end
subject
end
it 'authorization update callback is triggered' do
expect(members).to all(receive(:refresh_member_authorized_projects).once)
subject
end
it 'does not enqueues todos for deletion' do
members.each do |member|
expect(TodosDestroyer::EntityLeaveWorker)
.not_to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, source.class.name)
end
subject
end
context 'when members are downgraded to guest' do
shared_examples 'schedules to delete confidential todos' do
it do
members.each do |member|
expect(TodosDestroyer::EntityLeaveWorker)
.to receive(:perform_in)
.with(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, source.class.name).once
end
new_access_levels = updated_members.map(&:access_level)
expect(updated_members).to all(be_valid)
expect(new_access_levels).to all(be Gitlab::Access::GUEST)
end
end
context 'with Gitlab::Access::GUEST level as a string' do
let_it_be(:params) { { access_level: Gitlab::Access::GUEST.to_s } }
it_behaves_like 'schedules to delete confidential todos'
end
context 'with Gitlab::Access::GUEST level as an integer' do
let_it_be(:params) { { access_level: Gitlab::Access::GUEST } }
it_behaves_like 'schedules to delete confidential todos'
end
end
context 'when access_level is invalid' do
let_it_be(:params) { { access_level: 'invalid' } }
it 'raises an error' do
expect { described_class.new(current_user, params) }
.to raise_error(ArgumentError, 'invalid value for Integer(): "invalid"')
end
end
context 'when members update results in no change' do
let(:params) { { access_level: members.first.access_level } }
it 'does not invoke update! and post_update' do
expect(update_service).not_to receive(:save!)
expect(update_service).not_to receive(:post_update)
expect(subject[:status]).to eq(:success)
end
it 'authorization update callback is not triggered' do
members.each { |member| expect(member).not_to receive(:refresh_member_authorized_projects) }
subject
end
end
end
shared_examples 'updating a project' do
let_it_be(:group_project) { create(:project, group: create(:group)) }
let_it_be(:source) { group_project }
before do
member_users.each { |member_user| group_project.add_developer(member_user) }
end
context 'as a project maintainer' do
before do
group_project.add_maintainer(current_user)
end
it_behaves_like 'a service updating members'
context 'when member update results in an error' do
it_behaves_like 'a service returning an error'
end
context 'and updating members to OWNER' do
it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
let_it_be(:access_level) { Gitlab::Access::OWNER }
end
end
context 'and updating themselves to OWNER' do
let(:members) { source.members_and_requesters.find_by!(user_id: current_user.id) }
it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
let_it_be(:access_level) { Gitlab::Access::OWNER }
end
end
context 'and downgrading members from OWNER' do
before do
member_users.each { |member_user| group_project.add_owner(member_user) }
end
it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
let_it_be(:access_level) { Gitlab::Access::MAINTAINER }
end
end
end
context 'when current_user is considered an owner in the project via inheritance' do
before do
group_project.group.add_owner(current_user)
end
context 'and can update members to OWNER' do
before do
member_users.each { |member_user| group_project.add_developer(member_user) }
end
it_behaves_like 'a service updating members' do
let_it_be(:access_level) { Gitlab::Access::OWNER }
end
end
context 'and can downgrade members from OWNER' do
before do
member_users.each { |member_user| group_project.add_owner(member_user) }
end
it_behaves_like 'a service updating members' do
let_it_be(:access_level) { Gitlab::Access::MAINTAINER }
end
end
end
end
shared_examples 'updating a group' do
let_it_be(:source) { group }
before do
group.add_owner(current_user)
end
it_behaves_like 'a service updating members'
context 'when member update results in an error' do
it_behaves_like 'a service returning an error'
end
context 'when group members expiration date is updated' do
let_it_be(:params) { { expires_at: 20.days.from_now } }
let(:notification_service) { instance_double(NotificationService) }
before do
allow(NotificationService).to receive(:new).and_return(notification_service)
end
it 'emails the users that their group membership expiry has changed' do
members.each do |member|
expect(notification_service).to receive(:updated_group_member_expiration).with(member)
end
subject
end
end
end
context 'when :bulk_update_membership_roles feature flag is disabled' do
let(:member) { source.members_and_requesters.find_by!(user_id: member_user1.id) }
let(:members) { [member] }
subject { update_service.execute(member, permission: permission) }
shared_examples 'a service returning an error' do
before do
allow(member).to receive(:save) do
member.errors.add(:user_id)
member.errors.add(:access_level)
end
.and_return(false)
end
it_behaves_like 'returns error status when params are invalid'
it 'returns the error' do
response = subject
expect(response[:status]).to eq(:error)
expect(response[:message]).to eq('User is invalid and Access level is invalid')
end
end
before do
stub_feature_flags(bulk_update_membership_roles: false)
end
it_behaves_like 'current user cannot update the given members'
it_behaves_like 'updating a project'
it_behaves_like 'updating a group'
end
subject { update_service.execute(members, permission: permission) }
shared_examples 'a service returning an error' do
it_behaves_like 'returns error status when params are invalid'
context 'when a member update results in invalid record' do
let(:member2) { members.second }
before do
allow(member2).to receive(:save!) do
member2.errors.add(:user_id)
member2.errors.add(:access_level)
end.and_raise(ActiveRecord::RecordInvalid)
end
it 'returns the error' do
response = subject
expect(response[:status]).to eq(:error)
expect(response[:message]).to eq('User is invalid and Access level is invalid')
end
it 'rollbacks back the entire update' do
old_access_levels = members.pluck(:access_level)
subject
expect(members.each(&:reset).pluck(:access_level)).to eq(old_access_levels)
end
end
end
it_behaves_like 'current user cannot update the given members'
it_behaves_like 'updating a project'
it_behaves_like 'updating a group'
context 'with a single member' do
let(:member) { create(:group_member, group: group) }
let(:members) { member }
before do
group.add_owner(current_user)
end
it 'returns the correct response' do
expect(subject[:member]).to eq(member)
end
end
context 'when current user is an admin', :enable_admin_mode do
let_it_be(:current_user) { create(:admin) }
let_it_be(:source) { group }
context 'when all owners are being downgraded' do
before do
member_users.each { |member_user| group.add_owner(member_user) }
end
it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
end
context 'when all blocked owners are being downgraded' do
before do
member_users.each do |member_user|
group.add_owner(member_user)
member_user.block
end
end
it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
end
end
end