From e90fe1f99b54557ecc5588bedf13c6ecab5ec1af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C4=8Cavoj?= <mcavoj@gitlab.com> Date: Sat, 22 Apr 2023 08:45:05 +0000 Subject: [PATCH] Set approval rule as invalid if there are not enough approvers A rule is already marked as invalid if there are no approvers. This change also marks it as invalid if there are some approvers, but less than the number of required approvals. Changelog: changed EE: true --- .../project/merge_requests/approvals/index.md | 5 +- .../merge_requests/approval_state_type.rb | 1 + ee/app/models/approval_state.rb | 4 +- ee/app/models/approval_wrapped_rule.rb | 2 +- ee/spec/models/approval_state_spec.rb | 29 +++++++++-- ee/spec/models/approval_wrapped_rule_spec.rb | 52 ++++++++++++++++++- .../api/merge_request_approvals_spec.rb | 1 + 7 files changed, 83 insertions(+), 11 deletions(-) diff --git a/doc/user/project/merge_requests/approvals/index.md b/doc/user/project/merge_requests/approvals/index.md index 9988cdcddd061..3a39ff4f5f79e 100644 --- a/doc/user/project/merge_requests/approvals/index.md +++ b/doc/user/project/merge_requests/approvals/index.md @@ -119,12 +119,13 @@ Whenever an approval rule cannot be satisfied, the rule is displayed as `(!) Aut - The only eligible approver is the author of the merge request. - No eligible approvers (either groups or users) have been assigned to the approval rule. +- The number of required approvals is more than the number of eligible approvers. -These rules will be automatically approved (fail-open state) to unblock their respective merge requests, +These rules are automatically approved to unblock their respective merge requests, unless they were created through a security policy. Invalid approval rules created through a security policy are presented with `(!) Action Required` -and are not automatically approved (fail-closed state), blocking their respective merge requests. +and are not automatically approved, blocking their respective merge requests. ## Related topics diff --git a/ee/app/graphql/types/merge_requests/approval_state_type.rb b/ee/app/graphql/types/merge_requests/approval_state_type.rb index c76dda8073b13..c5dc8d9cfe99f 100644 --- a/ee/app/graphql/types/merge_requests/approval_state_type.rb +++ b/ee/app/graphql/types/merge_requests/approval_state_type.rb @@ -18,6 +18,7 @@ class ApprovalStateType < BaseObject field :invalid_approvers_rules, [::Types::ApprovalRuleType], null: true, + calls_gitaly: true, description: 'List of approval rules that are associated with the merge request, but invalid.' field :suggested_approvers, Types::UserType.connection_type, diff --git a/ee/app/models/approval_state.rb b/ee/app/models/approval_state.rb index 9136b9c2f2d05..172afff51d82d 100644 --- a/ee/app/models/approval_state.rb +++ b/ee/app/models/approval_state.rb @@ -198,12 +198,12 @@ def invalid_approvers_rules strong_memoize(:invalid_approvers_rules) do wrapped_approval_rules.select do |rule| next if rule.any_approver? - next unless rule.approvers.empty? + next if rule.approvers.any? && rule.approvers.size >= rule.approvals_required if rule.code_owner? rule.branch_requires_code_owner_approval? else - rule.approvals_required > 0 + rule.approvals_required > rule.approvers.size end end end diff --git a/ee/app/models/approval_wrapped_rule.rb b/ee/app/models/approval_wrapped_rule.rb index b553f8440ae68..01ca90195af42 100644 --- a/ee/app/models/approval_wrapped_rule.rb +++ b/ee/app/models/approval_wrapped_rule.rb @@ -91,7 +91,7 @@ def approved? end def invalid_rule? - !any_approver? && approvals_required > 0 && unactioned_approvers.size <= 0 + !any_approver? && approvals_required > approvers.size end def allow_merge_when_invalid? diff --git a/ee/spec/models/approval_state_spec.rb b/ee/spec/models/approval_state_spec.rb index 2e9a60c62f255..d24ff78d4c50e 100644 --- a/ee/spec/models/approval_state_spec.rb +++ b/ee/spec/models/approval_state_spec.rb @@ -1600,8 +1600,18 @@ def create_rules end end - context 'when invalid' do - let!(:rule) { invalid_rule } + context 'when invalid due to no approvers' do + let!(:rule) { invalid_no_approvers_rule } + + it 'returns the rule' do + approval_rules = subject.map(&:approval_rule) + + expect(approval_rules).to include(rule) + end + end + + context 'when invalid due to not enough approvers' do + let!(:rule) { invalid_not_enough_approvers_rule } it 'returns the rule' do approval_rules = subject.map(&:approval_rule) @@ -1613,7 +1623,10 @@ def create_rules context 'when the rule is code owner' do let(:valid_rule) { create(:code_owner_rule, merge_request: merge_request, users: create_list(:user, 1)) } - let(:invalid_rule) { create(:code_owner_rule, merge_request: merge_request) } + let(:invalid_no_approvers_rule) { create(:code_owner_rule, merge_request: merge_request) } + let(:invalid_not_enough_approvers_rule) do + create(:code_owner_rule, merge_request: merge_request, users: create_list(:user, 1), approvals_required: 2) + end before do stub_licensed_features(code_owner_approval_required: true) @@ -1659,7 +1672,10 @@ def create_rules context 'when the rule is approval_merge_request_rule' do let(:valid_rule) { create(:approval_merge_request_rule, merge_request: merge_request, users: create_list(:user, 1)) } - let(:invalid_rule) { create(:approval_merge_request_rule, merge_request: merge_request, approvals_required: 1) } + let(:invalid_no_approvers_rule) { create(:approval_merge_request_rule, merge_request: merge_request, approvals_required: 1) } + let(:invalid_not_enough_approvers_rule) do + create(:approval_merge_request_rule, merge_request: merge_request, users: create_list(:user, 1), approvals_required: 2) + end context 'when approvals required is 0' do let!(:rule) { create(:approval_merge_request_rule, merge_request: merge_request) } @@ -1676,7 +1692,10 @@ def create_rules context 'when the rule is report_approver' do let(:valid_rule) { create(:report_approver_rule, merge_request: merge_request, users: create_list(:user, 1)) } - let(:invalid_rule) { create(:report_approver_rule, merge_request: merge_request, approvals_required: 1) } + let(:invalid_no_approvers_rule) { create(:report_approver_rule, merge_request: merge_request, approvals_required: 1) } + let(:invalid_not_enough_approvers_rule) do + create(:report_approver_rule, merge_request: merge_request, users: create_list(:user, 1), approvals_required: 2) + end context 'when approvals required is 0' do let!(:rule) { create(:report_approver_rule, merge_request: merge_request) } diff --git a/ee/spec/models/approval_wrapped_rule_spec.rb b/ee/spec/models/approval_wrapped_rule_spec.rb index 3e469ea5ccb3b..2dc5d363a22db 100644 --- a/ee/spec/models/approval_wrapped_rule_spec.rb +++ b/ee/spec/models/approval_wrapped_rule_spec.rb @@ -64,7 +64,7 @@ end context 'when approvals left is not zero, but there is still unactioned approvers' do - let(:approvals_required) { 99 } + let(:approvals_required) { 2 } before do rule.users << approver2 @@ -82,6 +82,18 @@ expect(subject.approved?).to eq(true) end end + + context 'when approvals left is not zero, but there is not enough unactioned approvers' do + let(:approvals_required) { 99 } + + before do + rule.users << approver2 + end + + it 'returns true' do + expect(subject.approved?).to eq(true) + end + end end describe '#invalid_rule?' do @@ -101,6 +113,18 @@ end end + context 'when more approvals are required than the number of approvers' do + let(:approvals_required) { 2 } + + before do + rule.users << approver1 + end + + it 'returns true' do + expect(subject.invalid_rule?).to eq(true) + end + end + context 'when there are unactioned approvers and approvals are required' do let(:approvals_required) { 1 } @@ -113,6 +137,32 @@ end end + context 'when there are no unactioned approvers because all required approvals are given' do + let(:approvals_required) { 1 } + + before do + create(:approval, merge_request: merge_request, user: approver1) + rule.users << approver1 + end + + it 'returns false' do + expect(subject.invalid_rule?).to eq(false) + end + end + + context 'when there are more approvers than required approvals' do + let(:approvals_required) { 1 } + + before do + rule.users << approver1 + rule.users << approver2 + end + + it 'returns false' do + expect(subject.invalid_rule?).to eq(false) + end + end + context 'when no approvals are required' do let(:approvals_required) { 0 } diff --git a/ee/spec/requests/api/merge_request_approvals_spec.rb b/ee/spec/requests/api/merge_request_approvals_spec.rb index 25b98f0700aef..dbd19c57d8dac 100644 --- a/ee/spec/requests/api/merge_request_approvals_spec.rb +++ b/ee/spec/requests/api/merge_request_approvals_spec.rb @@ -84,6 +84,7 @@ project.add_developer(approver) project.add_developer(create(:user)) rule.users << approver + rule.users << create(:user) rule.groups << group get api(path, approver) -- GitLab