approval_state_spec.rb 38 KB
Newer Older
1 2 3 4 5 6
# frozen_string_literal: true

require 'spec_helper'

describe ApprovalState do
  def create_rule(additional_params = {})
7 8 9 10
    params = additional_params.merge(merge_request: merge_request)
    factory = params.delete(:code_owner) ? :code_owner_rule : :approval_merge_request_rule

    create(factory, params)
11 12
  end

13 14 15 16 17 18 19
  def approve_rules(rules)
    rules_to_approve = rules.select { |rule| rule.approvals_required > 0 }
    rules_to_approve.each do |rule|
      create(:approval, merge_request: merge_request, user: rule.users.first)
    end
  end

20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
  let(:merge_request) { create(:merge_request) }
  let(:project) { merge_request.target_project }
  let(:approver1) { create(:user) }
  let(:approver2) { create(:user) }
  let(:approver3) { create(:user) }

  let(:group_approver1) { create(:user) }
  let(:group1) do
    group = create(:group)
    group.add_developer(group_approver1)
    group
  end

  subject { merge_request.approval_state }

  shared_examples 'filtering author' do
    before do
37 38
      allow(merge_request).to receive(:authors).and_return([merge_request.author, create(:user, username: 'commiter')])

39
      project.update(merge_requests_author_approval: merge_requests_author_approval)
40
      create_rule(users: merge_request.authors)
41 42 43 44 45
    end

    context 'when self approval is disabled' do
      let(:merge_requests_author_approval) { false }

46 47
      it 'excludes authors' do
        expect(results).not_to include(*merge_request.authors)
48 49 50 51 52 53 54
      end
    end

    context 'when self approval is enabled' do
      let(:merge_requests_author_approval) { true }

      it 'includes author' do
55
        expect(results).to include(*merge_request.authors)
56 57 58 59
      end
    end
  end

60 61 62 63 64
  context '#approval_rules_overwritten?' do
    context 'when approval rule on the merge request does not exist' do
      it 'returns false' do
        expect(subject.approval_rules_overwritten?).to eq(false)
      end
65 66
    end

67
    context 'when approval rule on the merge request exists' do
68
      before do
69
        create(:approval_merge_request_rule, merge_request: merge_request)
70 71
      end

72 73
      it 'returns true' do
        expect(subject.approval_rules_overwritten?).to eq(true)
74 75 76
      end
    end

77 78 79 80 81 82 83
    context 'when `approvals_before_merge` is set on a merge request' do
      before do
        merge_request.update!(approvals_before_merge: 7)
      end

      it 'returns true' do
        expect(subject.approval_rules_overwritten?).to eq(true)
84 85
      end

86
      context 'when overriding approvals is not allowed' do
87
        before do
88
          project.update!(disable_overriding_approvers_per_merge_request: true)
89 90
        end
        it 'returns true' do
91
          expect(subject.approval_rules_overwritten?).to eq(false)
92 93 94
        end
      end
    end
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
  end

  context 'when multiple rules are allowed' do
    before do
      stub_licensed_features(multiple_approval_rules: true)
    end

    describe '#wrapped_approval_rules' do
      before do
        2.times { create_rule }
      end

      it 'returns all rules in wrapper' do
        expect(subject.wrapped_approval_rules).to all(be_an(ApprovalWrappedRule))
        expect(subject.wrapped_approval_rules.size).to eq(2)
      end
    end
112 113 114 115 116 117 118 119 120 121

    describe '#approval_needed?' do
      context 'when feature not available' do
        it 'returns false' do
          allow(subject.project).to receive(:feature_available?).with(:merge_request_approvers).and_return(false)

          expect(subject.approval_needed?).to eq(false)
        end
      end

122 123 124 125
      context 'when overall approvals required is not zero' do
        before do
          project.update(approvals_before_merge: 1)
        end
126

127 128
        it 'returns true' do
          expect(subject.approval_needed?).to eq(true)
129
        end
130
      end
131

132 133 134
      context "when any rule's approvals required is not zero" do
        it 'returns false' do
          create_rule(approvals_required: 1)
135

136
          expect(subject.approval_needed?).to eq(true)
137
        end
138 139
      end

140 141 142
      context "when overall approvals required and all rule's approvals_required are zero" do
        it 'returns false' do
          create_rule(approvals_required: 0)
143

144
          expect(subject.approval_needed?).to eq(false)
145 146 147
        end
      end

148 149 150 151
      context "when overall approvals required is zero, and there is no rule" do
        it 'returns false' do
          expect(subject.approval_needed?).to eq(false)
        end
152
      end
153
    end
154

155
    describe '#approved?' do
156 157 158
      shared_examples_for 'when rules are present' do
        context 'when all rules are approved' do
          before do
159
            approve_rules(subject.wrapped_approval_rules)
160
          end
161

162 163
          it 'returns true' do
            expect(subject.approved?).to eq(true)
164 165 166
          end
        end

167 168 169 170
        context 'when some rules are not approved' do
          before do
            allow(subject.wrapped_approval_rules.first).to receive(:approved?).and_return(false)
          end
171

172 173
          it 'returns false' do
            expect(subject.approved?).to eq(false)
174
          end
175 176 177
        end
      end

178
      shared_examples_for 'checking fallback_approvals_required' do
179
        before do
180
          project.update(approvals_before_merge: 1)
181 182
        end

183 184 185
        context 'when it is not met' do
          it 'returns false' do
            expect(subject.approved?).to eq(false)
186
          end
187
        end
188

189
        context 'when it is met' do
190
          it 'returns true' do
191 192
            create(:approval, merge_request: merge_request)

193 194
            expect(subject.approved?).to eq(true)
          end
195 196
        end
      end
197

198 199 200
      context 'when no rules' do
        it_behaves_like 'checking fallback_approvals_required'
      end
201

202 203 204
      context 'when only code owner rules present' do
        before do
          2.times { create_rule(users: [create(:user)], code_owner: true) }
205 206
        end

207 208 209
        it_behaves_like 'when rules are present'
        it_behaves_like 'checking fallback_approvals_required'
      end
210

211 212 213 214
      context 'when regular rules present' do
        before do
          project.update(approvals_before_merge: 999)
          2.times { create_rule(users: [create(:user)]) }
215
        end
216 217

        it_behaves_like 'when rules are present'
218 219 220
      end
    end

221
    describe '#any_approver_allowed?' do
222
      context 'when no rules' do
223 224 225 226 227
        it 'returns true' do
          expect(subject.any_approver_allowed?).to eq(true)
        end
      end

228
      context 'when with rules' do
229
        before do
230
          create_rule(approvals_required: 1, users: [approver1])
231 232
        end

233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
        context 'when approved' do
          before do
            allow(subject).to receive(:approved?).and_return(true)
          end

          it 'returns true' do
            expect(subject.any_approver_allowed?).to eq(true)
          end
        end

        context 'when not approved' do
          before do
            allow(subject).to receive(:approved?).and_return(false)
          end

          it 'returns false' do
            expect(subject.approved?).to eq(false)
          end
251 252 253 254
        end
      end
    end

255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644
    describe '#approvals_left' do
      before do
        create_rule(approvals_required: 5)
        create_rule(approvals_required: 7)
      end

      it 'sums approvals_left from rules' do
        expect(subject.approvals_left).to eq(12)
      end
    end

    describe '#approval_rules_left' do
      def create_unapproved_rule
        create_rule(approvals_required: 1, users: [create(:user)])
      end

      it 'counts approval_rules left' do
        create_unapproved_rule
        create_unapproved_rule

        expect(subject.approval_rules_left.size).to eq(2)
      end
    end

    describe '#approvers' do
      it 'includes all approvers, including code owner and group members' do
        create_rule(users: [approver1])
        create_rule(users: [approver1], groups: [group1])

        expect(subject.approvers).to contain_exactly(approver1, group_approver1)
      end

      it_behaves_like 'filtering author' do
        let(:results) { subject.approvers }
      end
    end

    describe '#filtered_approvers' do
      describe 'only direct users, without code owners' do
        it 'includes only rule user members' do
          create_rule(users: [approver1])
          create_rule(users: [approver1], groups: [group1])
          create_rule(users: [approver2], code_owner: true)

          expect(subject.filtered_approvers(code_owner: false, target: :users)).to contain_exactly(approver1)
        end
      end

      describe 'only code owners' do
        it 'includes only code owners' do
          create_rule(users: [approver1])
          create_rule(users: [approver1], groups: [group1])
          create_rule(users: [approver2], code_owner: true)

          expect(subject.filtered_approvers(regular: false)).to contain_exactly(approver2)
        end
      end

      describe 'only unactioned' do
        it 'excludes approved approvers' do
          create_rule(users: [approver1])
          create_rule(users: [approver1], groups: [group1])
          create_rule(users: [approver2], code_owner: true)

          create(:approval, merge_request: merge_request, user: approver1)

          expect(subject.filtered_approvers(unactioned: true)).to contain_exactly(approver2, group_approver1)
        end
      end

      it_behaves_like 'filtering author' do
        let(:results) { subject.filtered_approvers }
      end
    end

    describe '#unactioned_approvers' do
      it 'sums approvals_left from rules' do
        create_rule(users: [approver1, approver2])
        create_rule(users: [approver1])

        merge_request.approvals.create(user: approver2)

        expect(subject.unactioned_approvers).to contain_exactly(approver1)
      end

      it_behaves_like 'filtering author' do
        let(:results) { subject.unactioned_approvers }
      end
    end

    describe '#can_approve?' do
      shared_examples_for 'authors self-approval authorization' do
        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 '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 authors are not authorized to approve their own MRs' do
          it 'does not allow the author to approve the MR' do
            expect(subject.can_approve?(author)).to be_falsey
          end
        end
      end

      def create_project_member(role)
        user = create(:user)
        project.add_user(user, role)
        user
      end

      let(:project) { create(:project, :repository) }
      let(:merge_request) { create(:merge_request, source_project: project, author: author) }
      let(:author) { create_project_member(:developer) }
      let(:approver) { create_project_member(:developer) }
      let(:approver2) { create_project_member(:developer) }
      let(:developer) { create_project_member(:developer) }
      let(:other_developer) { create_project_member(:developer) }
      let(:reporter) { create_project_member(:reporter) }
      let(:stranger) { create(:user) }

      context 'when there is one approver required' do
        let!(:rule) { create_rule(approvals_required: 1) }

        context 'when that approver is the MR author' do
          before do
            rule.users << author
          end

          it_behaves_like 'authors self-approval authorization'

          it 'requires one approval' do
            expect(subject.approvals_left).to eq(1)
          end

          it 'allows any other project member with write access to approve the MR' do
            expect(subject.can_approve?(developer)).to be_truthy

            expect(subject.can_approve?(reporter)).to be_falsey
            expect(subject.can_approve?(stranger)).to be_falsey
          end

          it 'does not allow a logged-out user to approve the MR' do
            expect(subject.can_approve?(nil)).to be_falsey
          end
        end

        context 'when that approver is not the MR author' do
          before do
            rule.users << approver
          end

          it 'requires one approval' do
            expect(subject.approvals_left).to eq(1)
          end

          it 'only allows the approver to approve the MR' do
            expect(subject.can_approve?(approver)).to be_truthy

            expect(subject.can_approve?(author)).to be_falsey
            expect(subject.can_approve?(developer)).to be_falsey
            expect(subject.can_approve?(reporter)).to be_falsey
            expect(subject.can_approve?(stranger)).to be_falsey
            expect(subject.can_approve?(nil)).to be_falsey
          end
        end
      end

      context 'when there are multiple approvers required' do
        let!(:rule) { create_rule(approvals_required: 3) }

        context 'when one of those approvers is the MR author' do
          before do
            rule.users = [author, approver, approver2]
          end

          it_behaves_like 'authors self-approval authorization'

          it 'requires the original number of approvals' do
            expect(subject.approvals_left).to eq(3)
          end

          it 'allows any other other approver to approve the MR' do
            expect(subject.can_approve?(approver)).to be_truthy
          end

          it 'does not allow a logged-out user to approve the MR' do
            expect(subject.can_approve?(nil)).to be_falsey
          end

          context 'when self-approval is disabled and all of the valid approvers have approved the MR' do
            before do
              create(:approval, user: approver, merge_request: merge_request)
              create(:approval, user: approver2, merge_request: merge_request)
            end

            it 'requires the original number of approvals' do
              expect(subject.approvals_left).to eq(1)
            end

            it 'does not allow the author to approve the MR' do
              expect(subject.can_approve?(author)).to be_falsey
            end

            it 'does not allow the approvers to approve the MR again' do
              expect(subject.can_approve?(approver)).to be_falsey
              expect(subject.can_approve?(approver2)).to be_falsey
            end

            it 'allows any other project member with write access to approve the MR' do
              expect(subject.can_approve?(developer)).to be_truthy

              expect(subject.can_approve?(reporter)).to be_falsey
              expect(subject.can_approve?(stranger)).to be_falsey
              expect(subject.can_approve?(nil)).to be_falsey
            end
          end

          context 'when self-approval is enabled and all of the valid approvers have approved the MR' do
            before do
              project.update!(merge_requests_author_approval: true)
              create(:approval, user: author, merge_request: merge_request)
              create(:approval, user: approver2, merge_request: merge_request)
            end

            it 'requires the original number of approvals' do
              expect(subject.approvals_left).to eq(1)
            end

            it 'does not allow the approvers to approve the MR again' do
              expect(subject.can_approve?(author)).to be_falsey
              expect(subject.can_approve?(approver2)).to be_falsey
            end

            it 'allows any other project member with write access to approve the MR' do
              expect(subject.can_approve?(reporter)).to be_falsey
              expect(subject.can_approve?(stranger)).to be_falsey
              expect(subject.can_approve?(nil)).to be_falsey
            end
          end

          context 'when all approvers have approved the MR' do
            before do
              create(:approval, user: approver, merge_request: merge_request)
              create(:approval, user: approver2, merge_request: merge_request)
              create(:approval, user: developer, merge_request: merge_request)
            end

            it 'is approved' do
              expect(subject).to be_approved
            end

            it "returns sum of each rule's approvals_left" do
              expect(subject.approvals_left).to eq(1)
            end
          end
        end

        context 'when the approvers do not contain the MR author' do
          before do
            rule.users = [developer, approver, approver2]
          end

          it 'requires the original number of approvals' do
            expect(subject.approvals_left).to eq(3)
          end

          it 'only allows the approvers to approve the MR' do
            expect(subject.can_approve?(developer)).to be_truthy
            expect(subject.can_approve?(approver)).to be_truthy
            expect(subject.can_approve?(approver2)).to be_truthy

            expect(subject.can_approve?(author)).to be_falsey
            expect(subject.can_approve?(reporter)).to be_falsey
            expect(subject.can_approve?(stranger)).to be_falsey
            expect(subject.can_approve?(nil)).to be_falsey
          end

          context 'when only 1 approval approved' do
            it 'only allows the approvers to approve the MR' do
              create(:approval, user: approver, merge_request: merge_request)

              expect(subject.can_approve?(developer)).to be_truthy
              expect(subject.can_approve?(approver)).to be_falsey
              expect(subject.can_approve?(approver2)).to be_truthy

              expect(subject.can_approve?(author)).to be_falsey
              expect(subject.can_approve?(reporter)).to be_falsey
              expect(subject.can_approve?(other_developer)).to be_falsey
              expect(subject.can_approve?(stranger)).to be_falsey
              expect(subject.can_approve?(nil)).to be_falsey
            end
          end

          context 'when all approvals received' do
            it 'allows anyone with write access except for author to approve the MR' do
              create(:approval, user: approver, merge_request: merge_request)
              create(:approval, user: approver2, merge_request: merge_request)
              create(:approval, user: developer, merge_request: merge_request)

              expect(subject.can_approve?(author)).to be_falsey
              expect(subject.can_approve?(reporter)).to be_falsey
              expect(subject.can_approve?(other_developer)).to be_truthy
              expect(subject.can_approve?(stranger)).to be_falsey
              expect(subject.can_approve?(nil)).to be_falsey
            end
          end
        end
      end
    end

    describe '#has_approved?' do
      it 'returns false if user is nil' do
        expect(subject.has_approved?(nil)).to eq(false)
      end

      it 'returns true if user has approved' do
        create(:approval, merge_request: merge_request, user: approver1)

        expect(subject.has_approved?(approver1)).to eq(true)
        expect(subject.has_approved?(approver2)).to eq(false)
      end
    end

    describe '#authors_can_approve?' do
      context 'when project allows author approval' do
        before do
          project.update(merge_requests_author_approval: true)
        end

        it 'returns true' do
          expect(subject.authors_can_approve?).to eq(true)
        end
      end

      context 'when project disallows author approval' do
        before do
          project.update(merge_requests_author_approval: false)
        end

        it 'returns true' do
          expect(subject.authors_can_approve?).to eq(false)
        end
      end
    end
  end

  context 'when only a single rule is allowed' do
    def create_unapproved_rule(additional_params = {})
      create_rule(
        additional_params.reverse_merge(approvals_required: 1, users: [create(:user)])
      )
    end

    def create_rules
      rule1
      rule2
      code_owner_rule
    end

    let(:rule1) { create_unapproved_rule }
    let(:rule2) { create_unapproved_rule }
    let(:code_owner_rule) { create_unapproved_rule(code_owner: true, approvals_required: 0) }

    before do
      stub_licensed_features multiple_approval_rules: false
    end

    describe '#wrapped_approval_rules' do
      it 'returns one regular rule in wrapper' do
        create_rules

        subject.wrapped_approval_rules.each do |rule|
          expect(rule.is_a?(ApprovalWrappedRule)).to eq(true)
        end

        expect(subject.wrapped_approval_rules.size).to eq(2)
      end
    end

645 646 647 648 649
    describe '#has_non_fallback_rules?' do
      it 'returns true when there are rules' do
        create_rules

        expect(subject.has_non_fallback_rules?).to be(true)
650 651
      end

652 653 654
      it 'returns false if there are no rules' do
        expect(subject.has_non_fallback_rules?).to be(false)
      end
655

656 657 658 659
      it 'returns false if there are only fallback rules' do
        project.update!(approvals_before_merge: 1)

        expect(subject.has_non_fallback_rules?).to be(false)
660 661 662 663 664 665 666 667 668 669 670 671
      end
    end

    describe '#approval_needed?' do
      context 'when feature not available' do
        it 'returns false' do
          allow(subject.project).to receive(:feature_available?).with(:merge_request_approvers).and_return(false)

          expect(subject.approval_needed?).to eq(false)
        end
      end

672 673 674 675
      context 'when overall approvals required is not zero' do
        before do
          project.update(approvals_before_merge: 1)
        end
676

677 678
        it 'returns true' do
          expect(subject.approval_needed?).to eq(true)
679 680 681
        end
      end

682 683 684
      context "when any rule's approvals required is not zero" do
        it 'returns false' do
          create_rule(approvals_required: 1)
685 686 687 688 689

          expect(subject.approval_needed?).to eq(true)
        end
      end

690 691 692 693 694 695
      context "when overall approvals required and all rule's approvals_required are zero" do
        it 'returns false' do
          create_rule(approvals_required: 0)

          expect(subject.approval_needed?).to eq(false)
        end
696 697
      end

698 699 700
      context "when overall approvals required is zero, and there is no rule" do
        it 'returns false' do
          expect(subject.approval_needed?).to eq(false)
701
        end
702 703
      end
    end
704

705
    describe '#approved?' do
706 707 708
      shared_examples_for 'when rules are present' do
        context 'when all rules are approved' do
          before do
709
            approve_rules(subject.wrapped_approval_rules)
710
          end
711

712 713
          it 'returns true' do
            expect(subject.approved?).to eq(true)
714 715
          end
        end
716

717 718 719 720
        context 'when some rules are not approved' do
          before do
            allow(subject.wrapped_approval_rules.first).to receive(:approved?).and_return(false)
          end
721

722 723
          it 'returns false' do
            expect(subject.approved?).to eq(false)
724 725
          end
        end
726 727
      end

728
      shared_examples_for 'checking fallback_approvals_required' do
729
        before do
730
          project.update(approvals_before_merge: 1)
731 732
        end

733 734 735
        context 'when it is not met' do
          it 'returns false' do
            expect(subject.approved?).to eq(false)
736
          end
737
        end
738

739
        context 'when it is met' do
740
          it 'returns true' do
741 742
            create(:approval, merge_request: merge_request)

743 744
            expect(subject.approved?).to eq(true)
          end
745 746
        end
      end
747

748 749 750
      context 'when no rules' do
        it_behaves_like 'checking fallback_approvals_required'
      end
751

752 753
      context 'when only code owner rules present' do
        before do
754 755
          # setting approvals required to 0 since we don't want to block on them now
          2.times { create_rule(users: [create(:user)], code_owner: true, approvals_required: 0) }
756 757
        end

758 759 760
        it_behaves_like 'when rules are present'
        it_behaves_like 'checking fallback_approvals_required'
      end
761

762 763 764 765
      context 'when regular rules present' do
        before do
          project.update(approvals_before_merge: 999)
          2.times { create_rule(users: [create(:user)]) }
766
        end
767 768

        it_behaves_like 'when rules are present'
769
      end
770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803

      context 'when a single project rule is present' do
        before do
          create(:approval_project_rule, users: [create(:user)], project: project)
        end

        it_behaves_like 'when rules are present'

        context 'when the project rule is overridden by a fallback but the project does not allow overriding' do
          before do
            merge_request.update!(approvals_before_merge: 1)
            project.update!(disable_overriding_approvers_per_merge_request: true)
          end

          it_behaves_like 'when rules are present'
        end

        context 'when the project rule is overridden by a fallback' do
          before do
            merge_request.update!(approvals_before_merge: 1)
          end

          it_behaves_like 'checking fallback_approvals_required'
        end
      end

      context 'when a single project rule is present that is overridden in the merge request' do
        before do
          create(:approval_project_rule, users: [create(:user)], project: project)
          merge_request.update!(approvals_before_merge: 1)
        end

        it_behaves_like 'checking fallback_approvals_required'
      end
804 805
    end

806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827
    describe '#any_approver_allowed?' do
      context 'when approved' do
        before do
          allow(subject).to receive(:approved?).and_return(true)
        end

        it 'returns true' do
          expect(subject.any_approver_allowed?).to eq(true)
        end
      end

      context 'when not approved' do
        before do
          allow(subject).to receive(:approved?).and_return(false)
        end

        it 'returns false' do
          expect(subject.approved?).to eq(false)
        end
      end
    end

828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181
    describe '#approvals_left' do
      let(:rule1) { create_unapproved_rule(approvals_required: 5) }
      let(:rule2) { create_unapproved_rule(approvals_required: 7) }

      it 'sums approvals_left from rules' do
        create_rules

        expect(subject.approvals_left).to eq(5)
      end
    end

    describe '#approval_rules_left' do
      it 'counts approval_rules left' do
        create_rules

        expect(subject.approval_rules_left.size).to eq(1)
      end
    end

    describe '#approvers' do
      let(:code_owner_rule) { create_rule(code_owner: true, groups: [group1]) }

      it 'includes approvers from first rule and code owner rule' do
        create_rules
        approvers = rule1.users + [group_approver1]

        expect(subject.approvers).to contain_exactly(*approvers)
      end

      it_behaves_like 'filtering author' do
        let(:results) { subject.approvers }
      end
    end

    describe '#filtered_approvers' do
      describe 'only direct users, without code owners' do
        it 'includes only rule user members' do
          create_rule(users: [approver1])
          create_rule(users: [approver1], groups: [group1])
          create_rule(users: [approver2], code_owner: true)

          expect(subject.filtered_approvers(code_owner: false, target: :users)).to contain_exactly(approver1)
        end
      end

      describe 'excludes regular rule' do
        it 'includes only code owners' do
          create_rule(users: [approver1])
          create_rule(users: [approver1], groups: [group1])
          create_rule(users: [approver2], code_owner: true)

          expect(subject.filtered_approvers(regular: false)).to contain_exactly(approver2)
        end
      end

      describe 'only unactioned' do
        it 'excludes approved approvers' do
          create_rule(users: [approver1])
          create_rule(users: [approver1], groups: [group1])
          create_rule(users: [approver2], code_owner: true)

          create(:approval, merge_request: merge_request, user: approver1)

          expect(subject.filtered_approvers(unactioned: true)).to contain_exactly(approver2)
        end
      end

      it_behaves_like 'filtering author' do
        let(:results) { subject.filtered_approvers }
      end
    end

    describe '#unactioned_approvers' do
      it 'sums approvals_left from rules' do
        create_rule(users: [approver1, approver2])
        create_rule(users: [approver1])

        merge_request.approvals.create(user: approver2)

        expect(subject.unactioned_approvers).to contain_exactly(approver1)
      end

      it_behaves_like 'filtering author' do
        let(:results) { subject.unactioned_approvers }
      end
    end

    describe '#can_approve?' do
      shared_examples_for 'authors self-approval authorization' do
        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 '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 authors are not authorized to approve their own MRs' do
          it 'does not allow the author to approve the MR' do
            expect(subject.can_approve?(author)).to be_falsey
          end
        end
      end

      def create_project_member(role)
        user = create(:user)
        project.add_user(user, role)
        user
      end

      let(:project) { create(:project, :repository) }
      let(:merge_request) { create(:merge_request, source_project: project, author: author) }
      let(:author) { create_project_member(:developer) }
      let(:approver) { create_project_member(:developer) }
      let(:approver2) { create_project_member(:developer) }
      let(:developer) { create_project_member(:developer) }
      let(:other_developer) { create_project_member(:developer) }
      let(:reporter) { create_project_member(:reporter) }
      let(:stranger) { create(:user) }

      context 'when there is one approver required' do
        let!(:rule) { create_rule(approvals_required: 1) }

        context 'when that approver is the MR author' do
          before do
            rule.users << author
          end

          it_behaves_like 'authors self-approval authorization'

          it 'requires one approval' do
            expect(subject.approvals_left).to eq(1)
          end

          it 'allows any other project member with write access to approve the MR' do
            expect(subject.can_approve?(developer)).to be_truthy

            expect(subject.can_approve?(reporter)).to be_falsey
            expect(subject.can_approve?(stranger)).to be_falsey
          end

          it 'does not allow a logged-out user to approve the MR' do
            expect(subject.can_approve?(nil)).to be_falsey
          end
        end

        context 'when that approver is not the MR author' do
          before do
            rule.users << approver
          end

          it 'requires one approval' do
            expect(subject.approvals_left).to eq(1)
          end

          it 'only allows the approver to approve the MR' do
            expect(subject.can_approve?(approver)).to be_truthy

            expect(subject.can_approve?(author)).to be_falsey
            expect(subject.can_approve?(developer)).to be_falsey
            expect(subject.can_approve?(reporter)).to be_falsey
            expect(subject.can_approve?(stranger)).to be_falsey
            expect(subject.can_approve?(nil)).to be_falsey
          end
        end
      end

      context 'when there are multiple approvers required' do
        let!(:rule) { create_rule(approvals_required: 3) }

        context 'when one of those approvers is the MR author' do
          before do
            rule.users = [author, approver, approver2]
          end

          it_behaves_like 'authors self-approval authorization'

          it 'requires the original number of approvals' do
            expect(subject.approvals_left).to eq(3)
          end

          it 'allows any other other approver to approve the MR' do
            expect(subject.can_approve?(approver)).to be_truthy
          end

          it 'does not allow a logged-out user to approve the MR' do
            expect(subject.can_approve?(nil)).to be_falsey
          end

          context 'when self-approval is disabled and all of the valid approvers have approved the MR' do
            before do
              create(:approval, user: approver, merge_request: merge_request)
              create(:approval, user: approver2, merge_request: merge_request)
            end

            it 'requires the original number of approvals' do
              expect(subject.approvals_left).to eq(1)
            end

            it 'does not allow the author to approve the MR' do
              expect(subject.can_approve?(author)).to be_falsey
            end

            it 'does not allow the approvers to approve the MR again' do
              expect(subject.can_approve?(approver)).to be_falsey
              expect(subject.can_approve?(approver2)).to be_falsey
            end

            it 'allows any other project member with write access to approve the MR' do
              expect(subject.can_approve?(developer)).to be_truthy

              expect(subject.can_approve?(reporter)).to be_falsey
              expect(subject.can_approve?(stranger)).to be_falsey
              expect(subject.can_approve?(nil)).to be_falsey
            end
          end

          context 'when self-approval is enabled and all of the valid approvers have approved the MR' do
            before do
              project.update!(merge_requests_author_approval: true)
              create(:approval, user: author, merge_request: merge_request)
              create(:approval, user: approver2, merge_request: merge_request)
            end

            it 'requires the original number of approvals' do
              expect(subject.approvals_left).to eq(1)
            end

            it 'does not allow the approvers to approve the MR again' do
              expect(subject.can_approve?(author)).to be_falsey
              expect(subject.can_approve?(approver2)).to be_falsey
            end

            it 'allows any other project member with write access to approve the MR' do
              expect(subject.can_approve?(reporter)).to be_falsey
              expect(subject.can_approve?(stranger)).to be_falsey
              expect(subject.can_approve?(nil)).to be_falsey
            end
          end

          context 'when all approvers have approved the MR' do
            before do
              create(:approval, user: approver, merge_request: merge_request)
              create(:approval, user: approver2, merge_request: merge_request)
              create(:approval, user: developer, merge_request: merge_request)
            end

            it 'is approved' do
              expect(subject).to be_approved
            end

            it "returns sum of each rule's approvals_left" do
              expect(subject.approvals_left).to eq(1)
            end
          end
        end

        context 'when the approvers do not contain the MR author' do
          before do
            rule.users = [developer, approver, approver2]
          end

          it 'requires the original number of approvals' do
            expect(subject.approvals_left).to eq(3)
          end

          it 'only allows the approvers to approve the MR' do
            expect(subject.can_approve?(developer)).to be_truthy
            expect(subject.can_approve?(approver)).to be_truthy
            expect(subject.can_approve?(approver2)).to be_truthy

            expect(subject.can_approve?(author)).to be_falsey
            expect(subject.can_approve?(reporter)).to be_falsey
            expect(subject.can_approve?(stranger)).to be_falsey
            expect(subject.can_approve?(nil)).to be_falsey
          end

          context 'when only 1 approval approved' do
            it 'only allows the approvers to approve the MR' do
              create(:approval, user: approver, merge_request: merge_request)

              expect(subject.can_approve?(developer)).to be_truthy
              expect(subject.can_approve?(approver)).to be_falsey
              expect(subject.can_approve?(approver2)).to be_truthy

              expect(subject.can_approve?(author)).to be_falsey
              expect(subject.can_approve?(reporter)).to be_falsey
              expect(subject.can_approve?(other_developer)).to be_falsey
              expect(subject.can_approve?(stranger)).to be_falsey
              expect(subject.can_approve?(nil)).to be_falsey
            end
          end

          context 'when all approvals received' do
            it 'allows anyone with write access except for author to approve the MR' do
              create(:approval, user: approver, merge_request: merge_request)
              create(:approval, user: approver2, merge_request: merge_request)
              create(:approval, user: developer, merge_request: merge_request)

              expect(subject.can_approve?(author)).to be_falsey
              expect(subject.can_approve?(reporter)).to be_falsey
              expect(subject.can_approve?(other_developer)).to be_truthy
              expect(subject.can_approve?(stranger)).to be_falsey
              expect(subject.can_approve?(nil)).to be_falsey
            end
          end
        end
      end
    end

    describe '#has_approved?' do
      it 'returns false if user is nil' do
        expect(subject.has_approved?(nil)).to eq(false)
      end

      it 'returns true if user has approved' do
        create(:approval, merge_request: merge_request, user: approver1)

        expect(subject.has_approved?(approver1)).to eq(true)
        expect(subject.has_approved?(approver2)).to eq(false)
      end
    end

    describe '#authors_can_approve?' do
      context 'when project allows author approval' do
        before do
          project.update(merge_requests_author_approval: true)
        end

        it 'returns true' do
          expect(subject.authors_can_approve?).to eq(true)
        end
      end

      context 'when project disallows author approval' do
        before do
          project.update(merge_requests_author_approval: false)
        end

        it 'returns true' do
          expect(subject.authors_can_approve?).to eq(false)
        end
      end
    end
  end
end