Commit 72856753 authored by Luke Duncalfe's avatar Luke Duncalfe

Allow self-approvals in fallback approval rules

Allow authors of a MR to approve a MR if fallback approval rules are
in play.

Issue https://gitlab.com/gitlab-org/gitlab-ee/issues/7532
parent 62362ba9
...@@ -133,15 +133,17 @@ class ApprovalState ...@@ -133,15 +133,17 @@ class ApprovalState
def can_approve?(user) def can_approve?(user)
return false unless user return false unless user
# The check below considers authors being able to approve the MR.
# That is, they're included/excluded from that list accordingly.
return true if unactioned_approvers.include?(user) return true if unactioned_approvers.include?(user)
# We can safely unauthorize author and committers if it reaches this guard clause. return false unless any_approver_allowed?
return false if merge_request.author == user
return false if merge_request.committers.include?(user)
return false unless user.can?(:update_merge_request, merge_request) return false unless user.can?(:update_merge_request, merge_request)
# Users can only approve once.
return false if approvals.where(user: user).any?
# At this point, follow self-approval rules. Otherwise authors must
# have been in the list of unactioned_approvers to have been approved.
return committers_can_approve? if merge_request.committers.include?(user)
return authors_can_approve? if merge_request.author == user
any_approver_allowed? && merge_request.approvals.where(user: user).empty? true
end end
def has_approved?(user) def has_approved?(user)
...@@ -154,6 +156,10 @@ class ApprovalState ...@@ -154,6 +156,10 @@ class ApprovalState
project.merge_requests_author_approval? project.merge_requests_author_approval?
end end
def committers_can_approve?
!project.merge_requests_disable_committers_approval?
end
# TODO: remove after #1979 is closed # TODO: remove after #1979 is closed
# This is a temporary method for backward compatibility # This is a temporary method for backward compatibility
# before introduction of approval rules. # before introduction of approval rules.
......
...@@ -43,26 +43,36 @@ describe ApprovableForRule do ...@@ -43,26 +43,36 @@ describe ApprovableForRule do
end end
context 'when the user is the author' do context 'when the user is the author' do
context 'and user is an approver' do context 'and author is an approver' do
before do before do
create(:approver, target: merge_request, user: author) create(:approver, target: merge_request, user: author)
end end
it 'return true when authors can approve' do it 'returns true when authors can approve' do
project.update(merge_requests_author_approval: true) project.update(merge_requests_author_approval: true)
expect(merge_request.can_approve?(author)).to be true expect(merge_request.can_approve?(author)).to be true
end end
it 'return false when authors cannot approve' do it 'returns false when authors cannot approve' do
project.update(merge_requests_author_approval: false) project.update(merge_requests_author_approval: false)
expect(merge_request.can_approve?(author)).to be false expect(merge_request.can_approve?(author)).to be false
end end
end end
it 'returns false when user is not an approver' do context 'and author is not an approver' do
expect(merge_request.can_approve?(author)).to be false it 'returns true when authors can approve' do
project.update(merge_requests_author_approval: true)
expect(merge_request.can_approve?(author)).to be true
end
it 'returns false when authors cannot approve' do
project.update(merge_requests_author_approval: false)
expect(merge_request.can_approve?(author)).to be false
end
end end
end end
...@@ -73,7 +83,7 @@ describe ApprovableForRule do ...@@ -73,7 +83,7 @@ describe ApprovableForRule do
project.add_developer(user) project.add_developer(user)
end end
context 'and user is an approver' do context 'and committer is an approver' do
before do before do
create(:approver, target: merge_request, user: user) create(:approver, target: merge_request, user: user)
end end
...@@ -91,8 +101,18 @@ describe ApprovableForRule do ...@@ -91,8 +101,18 @@ describe ApprovableForRule do
end end
end end
it 'returns false when user is not an approver' do context 'and committer is not an approver' do
expect(merge_request.can_approve?(user)).to be false it 'return true when committers can approve' do
project.update(merge_requests_disable_committers_approval: false)
expect(merge_request.can_approve?(user)).to be true
end
it 'return false when committers cannot approve' do
project.update(merge_requests_disable_committers_approval: true)
expect(merge_request.can_approve?(user)).to be false
end
end end
end end
......
...@@ -442,8 +442,8 @@ describe ApprovalState do ...@@ -442,8 +442,8 @@ describe ApprovalState do
end end
end end
def create_project_member(role) def create_project_member(role, user_attrs = {})
user = create(:user) user = create(:user, user_attrs)
project.add_user(user, role) project.add_user(user, role)
user user
end end
...@@ -453,11 +453,168 @@ describe ApprovalState do ...@@ -453,11 +453,168 @@ describe ApprovalState do
let(:author) { create_project_member(:developer) } let(:author) { create_project_member(:developer) }
let(:approver) { create_project_member(:developer) } let(:approver) { create_project_member(:developer) }
let(:approver2) { create_project_member(:developer) } let(:approver2) { create_project_member(:developer) }
let(:committer) { create_project_member(:developer, email: merge_request.commits.without_merge_commits.first.committer_email) }
let(:developer) { create_project_member(:developer) } let(:developer) { create_project_member(:developer) }
let(:other_developer) { create_project_member(:developer) } let(:other_developer) { create_project_member(:developer) }
let(:reporter) { create_project_member(:reporter) } let(:reporter) { create_project_member(:reporter) }
let(:stranger) { create(:user) } let(:stranger) { create(:user) }
context 'when there are no approval rules' do
before do
project.update!(approvals_before_merge: 1)
end
it_behaves_like 'a MR that all members with write access can approve'
it 'has fallback rules apply' do
expect(subject.use_fallback?).to be_truthy
end
it 'requires one approval' do
expect(subject.approvals_left).to eq(1)
end
context 'when authors are authorized to approve their own MRs' do
before do
project.update!(merge_requests_author_approval: true)
end
it 'allows the author to approve the MR if within the approvers list' do
expect(subject.can_approve?(author)).to be_truthy
end
it 'allows the author to approve the MR if not within the approvers list' do
allow(subject).to receive(:approvers).and_return([])
expect(subject.can_approve?(author)).to be_truthy
end
context 'when the author has approved the MR already' do
before do
create(:approval, user: author, merge_request: merge_request)
end
it 'does not allow the author to approve the MR again' do
expect(subject.can_approve?(author)).to be_falsey
end
end
end
context 'when authors are not authorized to approve their own MRs' do
before do
project.update!(merge_requests_author_approval: false)
end
it 'allows the author to approve the MR if within the approvers list' do
allow(subject).to receive(:approvers).and_return([author])
expect(subject.can_approve?(author)).to be_truthy
end
it 'does not allow the author to approve the MR if not within the approvers list' do
allow(subject).to receive(:approvers).and_return([])
expect(subject.can_approve?(author)).to be_falsey
end
end
context 'when committers are authorized to approve their own MRs' do
before do
project.update!(merge_requests_disable_committers_approval: false)
end
it 'allows the committer to approve the MR if within the approvers list' do
allow(subject).to receive(:approvers).and_return([committer])
expect(subject.can_approve?(committer)).to be_truthy
end
it 'allows the committer to approve the MR if not within the approvers list' do
allow(subject).to receive(:approvers).and_return([])
expect(subject.can_approve?(committer)).to be_truthy
end
context 'when the committer has approved the MR already' do
before do
create(:approval, user: committer, merge_request: merge_request)
end
it 'does not allow the committer to approve the MR again' do
expect(subject.can_approve?(committer)).to be_falsey
end
end
end
context 'when committers are not authorized to approve their own MRs' do
before do
project.update!(merge_requests_disable_committers_approval: true)
end
it 'allows the committer to approve the MR if within the approvers list' do
allow(subject).to receive(:approvers).and_return([committer])
expect(subject.can_approve?(committer)).to be_truthy
end
it 'does not allow the committer to approve the MR if not within the approvers list' do
allow(subject).to receive(:approvers).and_return([])
expect(subject.can_approve?(committer)).to be_falsey
end
end
context 'when the user is both an author and a committer' do
let(:user) { committer }
before do
merge_request.update!(author: committer)
end
context 'when authors are authorized to approve their own MRs, but not committers' do
before do
project.update!(
merge_requests_author_approval: true,
merge_requests_disable_committers_approval: true
)
end
it 'allows the user to approve the MR if within the approvers list' do
allow(subject).to receive(:approvers).and_return([user])
expect(subject.can_approve?(user)).to be_truthy
end
it 'does not allow the user to approve the MR if not within the approvers list' do
allow(subject).to receive(:approvers).and_return([])
expect(subject.can_approve?(user)).to be_falsey
end
end
context 'when committers are authorized to approve their own MRs, but not authors' do
before do
project.update!(
merge_requests_author_approval: false,
merge_requests_disable_committers_approval: false
)
end
it 'allows the user to approve the MR if within the approvers list' do
allow(subject).to receive(:approvers).and_return([user])
expect(subject.can_approve?(user)).to be_truthy
end
it 'allows the user to approve the MR if not within the approvers list' do
allow(subject).to receive(:approvers).and_return([])
expect(subject.can_approve?(user)).to be_truthy
end
end
end
end
context 'when there is one approver required' do context 'when there is one approver required' do
let!(:rule) { create_rule(approvals_required: 1) } let!(:rule) { create_rule(approvals_required: 1) }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment