From 93d101bec1dfc966defad9eca3842e3e695d2a5c Mon Sep 17 00:00:00 2001
From: Todd Dembrey <todd.dembrey@torchbox.com>
Date: Wed, 22 Aug 2018 18:08:18 +0100
Subject: [PATCH] make sure staff cant mess with external reviewers by accident

---
 opentech/apply/funds/forms.py            |  14 ++-
 opentech/apply/funds/tests/test_views.py | 121 ++++++++++++++++++++++-
 2 files changed, 130 insertions(+), 5 deletions(-)

diff --git a/opentech/apply/funds/forms.py b/opentech/apply/funds/forms.py
index c5efce9d4..dd435d559 100644
--- a/opentech/apply/funds/forms.py
+++ b/opentech/apply/funds/forms.py
@@ -52,8 +52,11 @@ class UpdateReviewersForm(forms.ModelForm):
         model = ApplicationSubmission
         fields: list = []
 
+    def can_alter_reviewers(self, user):
+        return self.instance.stage.has_external_review and user == self.instance.lead
+
     def __init__(self, *args, **kwargs):
-        user = kwargs.pop('user')
+        self.user = kwargs.pop('user')
         super().__init__(*args, **kwargs)
         reviewers = self.instance.reviewers.all()
         self.submitted_reviewers = User.objects.filter(id__in=self.instance.reviews.values('author'))
@@ -62,7 +65,7 @@ class UpdateReviewersForm(forms.ModelForm):
         staff_field.queryset = staff_field.queryset.exclude(id__in=self.submitted_reviewers)
         staff_field.initial = reviewers
 
-        if self.instance.stage.has_external_review and user == self.instance.lead:
+        if self.can_alter_reviewers(self.user):
             review_field = self.fields['reviewer_reviewers']
             review_field.queryset = review_field.queryset.exclude(id__in=self.submitted_reviewers)
             review_field.initial = reviewers
@@ -71,9 +74,14 @@ class UpdateReviewersForm(forms.ModelForm):
 
     def save(self, *args, **kwargs):
         instance = super().save(*args, **kwargs)
+        if self.can_alter_reviewers(self.user):
+            reviewers = self.cleaned_data.get('reviewer_reviewers')
+        else:
+            reviewers = instance.reviewers_not_reviewed
+
         instance.reviewers.set(
             self.cleaned_data['staff_reviewers'] |
-            self.cleaned_data.get('reviewer_reviewers', User.objects.none()) |
+            reviewers |
             self.submitted_reviewers
         )
         return instance
diff --git a/opentech/apply/funds/tests/test_views.py b/opentech/apply/funds/tests/test_views.py
index 16e33d86a..0e68c7d06 100644
--- a/opentech/apply/funds/tests/test_views.py
+++ b/opentech/apply/funds/tests/test_views.py
@@ -11,7 +11,12 @@ from opentech.apply.funds.tests.factories import (
     SealedSubmissionFactory,
 )
 from opentech.apply.stream_forms.testing.factories import flatten_for_form
-from opentech.apply.users.tests.factories import UserFactory, StaffFactory, SuperUserFactory
+from opentech.apply.users.tests.factories import (
+    ReviewerFactory,
+    StaffFactory,
+    SuperUserFactory,
+    UserFactory,
+)
 from opentech.apply.utils.testing.tests import BaseViewTestCase
 
 from ..models import ApplicationRevision
@@ -122,7 +127,7 @@ class TestStaffSubmissionView(BaseSubmissionViewTestCase):
         data = prepare_form_data(self.submission, title=new_title)
         response = self.post_page(self.submission, {'submit': True, **data}, 'edit')
 
-        url = self.url_from_pattern('funds:submissions:detail', kwargs={'pk': self.submission.id})
+        url = self.url(self.submission)
 
         self.assertRedirects(response, url)
         submission = self.refresh(self.submission)
@@ -132,6 +137,118 @@ class TestStaffSubmissionView(BaseSubmissionViewTestCase):
         self.assertEqual(new_title, submission.title)
 
 
+class TestReviewersUpdateView(BaseSubmissionViewTestCase):
+    user_factory = StaffFactory
+
+    def post_form(self, submission, staff=list(), reviewers=list()):
+        return self.post_page(submission, {
+            'form-submitted-reviewer_form': '',
+            'staff_reviewers': [s.id for s in staff],
+            'reviewer_reviewers': [r.id for r in reviewers]
+        })
+
+    def test_lead_can_add_staff_single(self):
+        submission = ApplicationSubmissionFactory(lead=self.user)
+        staff = StaffFactory.create_batch(4)
+
+        self.post_form(submission, staff)
+
+        self.assertCountEqual(submission.reviewers.all(), staff)
+
+    def test_lead_can_remove_staff_single(self):
+        staff = StaffFactory.create_batch(4)
+        submission = ApplicationSubmissionFactory(lead=self.user, reviewers=staff)
+        self.assertCountEqual(submission.reviewers.all(), staff)
+
+        self.post_form(submission, [])
+
+        self.assertCountEqual(submission.reviewers.all(), [])
+
+    def test_lead_can_remove_some_staff(self):
+        staff = StaffFactory.create_batch(4)
+        submission = ApplicationSubmissionFactory(lead=self.user, reviewers=staff)
+        self.assertCountEqual(submission.reviewers.all(), staff)
+
+        self.post_form(submission, staff[0:2])
+
+        self.assertCountEqual(submission.reviewers.all(), staff[0:2])
+
+    def test_lead_cant_add_reviewers_single(self):
+        submission = ApplicationSubmissionFactory(lead=self.user)
+        reviewers = ReviewerFactory.create_batch(4)
+
+        self.post_form(submission, reviewers=reviewers)
+
+        self.assertCountEqual(submission.reviewers.all(), [])
+
+    def test_lead_can_add_staff_and_reviewers_for_proposal(self):
+        submission = InvitedToProposalFactory(lead=self.user)
+
+        staff = StaffFactory.create_batch(4)
+        reviewers = ReviewerFactory.create_batch(4)
+
+        self.post_form(submission, staff, reviewers)
+
+        self.assertCountEqual(submission.reviewers.all(), staff + reviewers)
+
+    def test_lead_can_remove_staff_and_reviewers_for_proposal(self):
+        staff = StaffFactory.create_batch(4)
+        reviewers = ReviewerFactory.create_batch(4)
+
+        submission = InvitedToProposalFactory(lead=self.user, reviewers=staff + reviewers)
+        self.assertCountEqual(submission.reviewers.all(), staff + reviewers)
+
+        self.post_form(submission)
+
+        self.assertCountEqual(submission.reviewers.all(), [])
+
+    def test_lead_can_remove_some_staff_and_reviewers_for_proposal(self):
+        staff = StaffFactory.create_batch(4)
+        reviewers = ReviewerFactory.create_batch(4)
+
+        submission = InvitedToProposalFactory(lead=self.user, reviewers=staff + reviewers)
+        self.assertCountEqual(submission.reviewers.all(), staff + reviewers)
+
+        self.post_form(submission, staff[0:2], reviewers[0:2])
+
+        self.assertCountEqual(submission.reviewers.all(), staff[0:2] + reviewers[0:2])
+
+    def test_staff_can_add_staff_single(self):
+        submission = ApplicationSubmissionFactory()
+        staff = StaffFactory.create_batch(4)
+
+        self.post_form(submission, staff)
+
+        self.assertCountEqual(submission.reviewers.all(), staff)
+
+    def test_staff_can_remove_staff_single(self):
+        staff = StaffFactory.create_batch(4)
+        submission = ApplicationSubmissionFactory(reviewers=staff)
+        self.assertCountEqual(submission.reviewers.all(), staff)
+
+        self.post_form(submission, [])
+
+        self.assertCountEqual(submission.reviewers.all(), [])
+
+    def test_staff_cant_add_reviewers_proposal(self):
+        submission = ApplicationSubmissionFactory()
+        reviewers = ReviewerFactory.create_batch(4)
+
+        self.post_form(submission, reviewers=reviewers)
+
+        self.assertCountEqual(submission.reviewers.all(), [])
+
+    def test_staff_cant_remove_reviewers_proposal(self):
+        reviewers = ReviewerFactory.create_batch(4)
+
+        submission = ApplicationSubmissionFactory(reviewers=reviewers)
+        self.assertCountEqual(submission.reviewers.all(), reviewers)
+
+        self.post_form(submission, reviewers=[])
+
+        self.assertCountEqual(submission.reviewers.all(), reviewers)
+
+
 class TestApplicantSubmissionView(BaseSubmissionViewTestCase):
     user_factory = UserFactory
 
-- 
GitLab