# Copyright The IETF Trust 2016-2020, All Rights Reserved # -*- coding: utf-8 -*- import datetime, os, shutil import io import tarfile, tempfile, mailbox import email.mime.multipart, email.mime.text, email.utils from mock import patch from requests import Response from django.apps import apps from django.urls import reverse as urlreverse from django.conf import settings from django.utils import timezone from pyquery import PyQuery import debug # pyflakes:ignore import ietf.review.mailarch from ietf.doc.factories import ( NewRevisionDocEventFactory, IndividualDraftFactory, WgDraftFactory, WgRfcFactory, ReviewFactory, DocumentFactory) from ietf.doc.models import ( Document, DocumentAuthor, RelatedDocument, DocEvent, ReviewRequestDocEvent, ReviewAssignmentDocEvent, ) from ietf.group.factories import RoleFactory, ReviewTeamFactory from ietf.group.models import Group from ietf.message.models import Message from ietf.name.models import ReviewResultName, ReviewRequestStateName, ReviewAssignmentStateName, ReviewTypeName from ietf.person.factories import PersonFactory from ietf.person.models import Email, Person from ietf.review.factories import ReviewRequestFactory, ReviewAssignmentFactory from ietf.review.models import ReviewRequest, ReviewerSettings, ReviewWish, NextReviewerInTeam from ietf.review.policies import get_reviewer_queue_policy from ietf.utils.mail import outbox, empty_outbox, parseaddr, on_behalf_of, get_payload_text from ietf.utils.test_utils import login_testing_unauthorized, reload_db_objects from ietf.utils.test_utils import TestCase from ietf.utils.text import strip_prefix, xslugify from ietf.utils.timezone import DEADLINE_TZINFO from django.utils.html import escape class ReviewTests(TestCase): def setUp(self): super().setUp() self.review_dir = self.tempdir('review') self.old_document_path_pattern = settings.DOCUMENT_PATH_PATTERN settings.DOCUMENT_PATH_PATTERN = self.review_dir + "/{doc.type_id}/" self.review_subdir = os.path.join(self.review_dir, "review") if not os.path.exists(self.review_subdir): os.mkdir(self.review_subdir) def tearDown(self): shutil.rmtree(self.review_dir) settings.DOCUMENT_PATH_PATTERN = self.old_document_path_pattern super().tearDown() def test_request_review(self): doc = WgDraftFactory(group__acronym='mars',rev='01') NewRevisionDocEventFactory(doc=doc,rev='01') RoleFactory(name_id='chair',person__user__username='marschairman',group=doc.group) review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) review_team3 = ReviewTeamFactory(acronym="reviewteam3", name="Review Team3", type_id="review", list_email="reviewteam3@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') RoleFactory(group=review_team3,person=rev_role.person,name_id='reviewer') RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr') RoleFactory(group=review_team3,person__user__username='reviewsecretary3',person__user__email='reviewsecretary3@example.com',name_id='secr') req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20)) ReviewAssignmentFactory(review_request = req, reviewer = rev_role.person.email_set.first(), state_id='accepted') url = urlreverse('ietf.doc.views_review.request_review', kwargs={ "name": doc.name }) login_testing_unauthorized(self, "ad", url) # get r = self.client.get(url) self.assertEqual(r.status_code, 200) deadline = datetime.date.today() + datetime.timedelta(days=10) empty_outbox() # post request r = self.client.post(url, { "type": "early", "team": [review_team.pk,review_team3.pk], "deadline": deadline.isoformat(), "requested_rev": "01", "requested_by": Person.objects.get(user__username="ad").pk, "comment": "gZT2iiYqYLKiQHvsgWCcVLdH" }) self.assertEqual(r.status_code, 302) qs = ReviewRequest.objects.filter(doc=doc, state="requested") self.assertEqual(qs.count(),2) self.assertEqual(set(qs.values_list('team__acronym',flat=True)),set(['reviewteam','reviewteam3'])) for req in qs: self.assertEqual(req.deadline, deadline) self.assertEqual(req.requested_rev, "01") self.assertEqual(doc.latest_event().type, "requested_review") self.assertEqual(req.comment, "gZT2iiYqYLKiQHvsgWCcVLdH") self.assertEqual(len(outbox),2) self.assertTrue('reviewteam Early' in outbox[0]['Subject']) self.assertTrue('reviewsecretary@' in outbox[0]['To']) self.assertTrue('reviewteam3 Early' in outbox[1]['Subject']) if not 'reviewsecretary3@' in outbox[1]['To']: print(outbox[1].as_string()) self.assertTrue('reviewsecretary3@' in outbox[1]['To']) # set the reviewteamsetting for the secretary email alias, then do the post again m = apps.get_model('review', 'ReviewTeamSettings') for row in m.objects.all(): if row.group.upcase_acronym == review_team3.upcase_acronym: row.secr_mail_alias = 'reviewsecretary3-alias@example.com' row.save(update_fields=['secr_mail_alias']) r = self.client.post(url, { "type": "early", "team": [review_team.pk,review_team3.pk], "deadline": deadline.isoformat(), "requested_rev": "01", "requested_by": Person.objects.get(user__username="ad").pk, "comment": "gZT2iiYqYLKiQHvsgWCcVLdH" }) self.assertEqual(r.status_code, 302) self.assertEqual(len(outbox),4) self.assertTrue('reviewsecretary@' in outbox[2]['To']) self.assertTrue('reviewsecretary3-alias@' in outbox[3]['To']) def test_request_review_of_rfc(self): doc = WgRfcFactory() url = urlreverse('ietf.doc.views_review.request_review', kwargs={ "name": doc.name }) login_testing_unauthorized(self, "ad", url) # get should fail r = self.client.get(url) self.assertEqual(r.status_code, 403) def test_doc_page(self): doc = WgDraftFactory(group__acronym='mars',rev='01') review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20)) ReviewAssignmentFactory(review_request=review_req, reviewer=rev_role.person.email_set.first(), state_id='accepted') # move the review request to a doubly-replaced document to # check we can fish it out old_doc = WgDraftFactory(name="draft-foo-mars-test") older_doc = WgDraftFactory(name="draft-older") RelatedDocument.objects.create(source=old_doc, target=older_doc.docalias.first(), relationship_id='replaces') RelatedDocument.objects.create(source=doc, target=old_doc.docalias.first(), relationship_id='replaces') review_req.doc = older_doc review_req.save() url = urlreverse('ietf.doc.views_doc.document_main', kwargs={ "name": doc.name }) r = self.client.get(url) self.assertContains(r, "{} Review".format(review_req.type.name)) def test_review_request(self): author = PersonFactory() doc = WgDraftFactory(group__acronym='mars',rev='01', authors=[author]) review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20)) ReviewAssignmentFactory(review_request = review_req, reviewer = rev_role.person.email_set.first(), state_id='accepted') url = urlreverse('ietf.doc.views_review.review_request', kwargs={ "name": doc.name, "request_id": review_req.pk }) r = self.client.get(url) self.assertContains(r, review_req.team.acronym) self.assertContains(r, review_req.team.name) try: # FIXME-LARS self.assertContains(r, escape(author.name)) except: print(r.content) self.assertContains(r, author.name) url = urlreverse('ietf.doc.views_review.review_request_forced_login', kwargs={ "name": doc.name, "request_id": review_req.pk }) r = self.client.get(url) self.assertEqual(r.status_code, 302) self.client.login(username='reviewer', password="reviewer+password") r = self.client.get(url,follow=True) self.assertEqual(r.status_code, 200) def test_close_request(self): doc = WgDraftFactory(group__acronym='mars',rev='01') review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr') RoleFactory(group=review_team,person__user__username='reviewsecretary2',person__user__email='reviewsecretary2@example.com',name_id='secr') review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20)) ReviewAssignmentFactory(review_request=review_req, state_id='accepted', reviewer=rev_role.person.email_set.first()) close_url = urlreverse('ietf.doc.views_review.close_request', kwargs={ "name": doc.name, "request_id": review_req.pk }) # follow link req_url = urlreverse('ietf.doc.views_review.review_request', kwargs={ "name": doc.name, "request_id": review_req.pk }) self.client.login(username="reviewsecretary", password="reviewsecretary+password") r = self.client.get(req_url) self.assertEqual(r.status_code, 200) self.assertContains(r, close_url) self.client.logout() # get close page login_testing_unauthorized(self, "reviewsecretary", close_url) r = self.client.get(close_url) self.assertEqual(r.status_code, 200) # close empty_outbox() r = self.client.post(close_url, {"close_reason": "withdrawn", "close_comment": "review_request_close_comment"}) self.assertEqual(r.status_code, 302) review_req = reload_db_objects(review_req) self.assertEqual(review_req.state_id, "withdrawn") e = doc.latest_event(ReviewRequestDocEvent) self.assertEqual(e.type, "closed_review_request") self.assertIn("closed", e.desc.lower()) self.assertIn("review_request_close_comment", e.desc.lower()) e = doc.latest_event(ReviewAssignmentDocEvent) self.assertEqual(e.type, "closed_review_assignment") self.assertIn("closed", e.desc.lower()) self.assertNotIn("review_request_close_comment", e.desc.lower()) self.assertEqual(len(outbox), 1) self.assertIn('', outbox[0]["To"]) self.assertNotIn("", outbox[0]["To"]) self.assertIn("reviewsecretary2@example.com", outbox[0]["CC"]) mail_content = get_payload_text(outbox[0]) self.assertIn("closed", mail_content) self.assertIn("review_request_close_comment", mail_content) def test_assign_reviewer(self): doc = WgDraftFactory(pages=2) review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',person__name='Some Reviewer',name_id='reviewer') RoleFactory(group=review_team,person__user__username='marschairman',person__name='WG Cháir Man',name_id='reviewer') secretary = RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr') ReviewerSettings.objects.create(team=review_team, person=rev_role.person, min_interval=14, skip_next=0) queue_policy = get_reviewer_queue_policy(review_team) # review to assign to review_req = ReviewRequestFactory(team=review_team,doc=doc,state_id='requested') # set up some reviewer-suitability factors reviewer_email = Email.objects.get(person__user__username="reviewer") DocumentAuthor.objects.create(person=reviewer_email.person, email=reviewer_email, document=doc) doc.rev = "10" doc.save_with_history([DocEvent.objects.create(doc=doc, rev=doc.rev, type="changed_document", by=Person.objects.get(user__username="secretary"), desc="Test")]) # previous review req = ReviewRequestFactory( time=timezone.now() - datetime.timedelta(days=100), requested_by=Person.objects.get(name="(System)"), doc=doc, type_id='early', team=review_req.team, state_id='assigned', requested_rev="01", deadline=datetime.date.today() - datetime.timedelta(days=80), ) ReviewAssignmentFactory( review_request = req, state_id='completed', result_id='serious-issues', reviewer=reviewer_email, reviewed_rev="01", review = ReviewFactory(), assigned_on=req.time, completed_on=req.time + datetime.timedelta(days=10), ) reviewer_settings = ReviewerSettings.objects.get(person__email=reviewer_email, team=review_req.team) reviewer_settings.filter_re = doc.name reviewer_settings.skip_next = 1 reviewer_settings.save() # Need one more person in review team one so we can test incrementing skip_count without immediately decrementing it another_reviewer = PersonFactory.create(name = "Extra TestReviewer") # needs to be lexically greater than the existing one another_reviewer.role_set.create(name_id='reviewer', email=another_reviewer.email(), group=review_req.team) ReviewWish.objects.create(person=reviewer_email.person, team=review_req.team, doc=doc) NextReviewerInTeam.objects.filter(team=review_req.team).delete() NextReviewerInTeam.objects.create( team=review_req.team, next_reviewer=Person.objects.exclude(pk=reviewer_email.person_id).first(), ) assign_url = urlreverse('ietf.doc.views_review.assign_reviewer', kwargs={ "name": doc.name, "request_id": review_req.pk }) # follow link req_url = urlreverse('ietf.doc.views_review.review_request', kwargs={ "name": doc.name, "request_id": review_req.pk }) self.client.login(username="reviewsecretary", password="reviewsecretary+password") r = self.client.get(req_url) self.assertEqual(r.status_code, 200) self.assertContains(r, assign_url) self.client.logout() # get assign page login_testing_unauthorized(self, "reviewsecretary", assign_url) r = self.client.get(assign_url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) reviewer_label = q("option[value=\"{}\"]".format(reviewer_email.address)).text().lower() self.assertIn("reviewed document before", reviewer_label) self.assertIn("wishes to review", reviewer_label) self.assertIn("is author", reviewer_label) self.assertIn("regexp matches", reviewer_label) self.assertIn("skip next 1", reviewer_label) self.assertIn("#1", reviewer_label) self.assertIn("1 fully completed", reviewer_label) # assign empty_outbox() rotation_list = queue_policy.default_reviewer_rotation_list() reviewer = Email.objects.filter(role__name="reviewer", role__group=review_req.team, person=rotation_list[0]).first() r = self.client.post(assign_url, { "action": "assign", "reviewer": reviewer.pk }) self.assertEqual(r.status_code, 302) review_req = reload_db_objects(review_req) self.assertEqual(review_req.state_id, "assigned") self.assertEqual(review_req.reviewassignment_set.count(),1) assignment = review_req.reviewassignment_set.first() self.assertEqual(assignment.reviewer, reviewer) self.assertEqual(assignment.state_id, "assigned") self.assertEqual(len(outbox), 1) self.assertEqual('"Some Reviewer" ', outbox[0]["To"]) message = get_payload_text(outbox[0]) self.assertIn("Pages: {}".format(doc.pages), message ) self.assertIn("{} has assigned {}".format(secretary.person.ascii, reviewer.person.ascii), message) self.assertIn("This team has completed other reviews", message) self.assertIn("{} -01 Serious Issues".format(reviewer_email.person.ascii), message) def test_previously_reviewed_replaced_doc(self): review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',person__name='Some Reviewer',name_id='reviewer') RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr') ind_doc = IndividualDraftFactory() old_wg_doc = WgDraftFactory(relations=[('replaces',ind_doc)]) middle_wg_doc = WgDraftFactory(relations=[('replaces',old_wg_doc)]) new_wg_doc = WgDraftFactory(relations=[('replaces',middle_wg_doc)]) ReviewAssignmentFactory(review_request__team=review_team, review_request__doc=old_wg_doc, reviewer=rev_role.email, state_id='completed') review_req=ReviewRequestFactory(team=review_team, doc=new_wg_doc) assign_url = urlreverse('ietf.doc.views_review.assign_reviewer', kwargs={ "name": new_wg_doc.name, "request_id": review_req.pk }) login_testing_unauthorized(self, "reviewsecretary", assign_url) r = self.client.get(assign_url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) reviewer_label = q("option[value=\"{}\"]".format(rev_role.email.address)).text().lower() self.assertIn("reviewed document before", reviewer_label) def test_accept_reviewer_assignment(self): doc = WgDraftFactory(group__acronym='mars',rev='01') review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20)) assignment = ReviewAssignmentFactory(review_request=review_req, state_id='assigned', reviewer=rev_role.person.email_set.first()) url = urlreverse('ietf.doc.views_review.review_request', kwargs={ "name": doc.name, "request_id": review_req.pk }) username = assignment.reviewer.person.user.username self.client.login(username=username, password=username + "+password") r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertTrue(q("[name=action][value=accept]")) # accept r = self.client.post(url, { "action": "accept" }) self.assertEqual(r.status_code, 302) assignment = reload_db_objects(assignment) self.assertEqual(assignment.state_id, "accepted") def test_reject_reviewer_assignment(self): doc = WgDraftFactory(group__acronym='mars',rev='01') review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr') review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='assigned',requested_by=rev_role.person,deadline=timezone.now()+datetime.timedelta(days=20)) assignment = ReviewAssignmentFactory(review_request = review_req, reviewer=rev_role.person.email_set.first(), state_id='accepted') reject_url = urlreverse('ietf.doc.views_review.reject_reviewer_assignment', kwargs={ "name": doc.name, "assignment_id": assignment.pk }) # follow link req_url = urlreverse('ietf.doc.views_review.review_request', kwargs={ "name": doc.name, "request_id": assignment.review_request.pk }) self.client.login(username="reviewsecretary", password="reviewsecretary+password") r = self.client.get(req_url) self.assertEqual(r.status_code, 200) self.assertContains(r, reject_url) self.client.logout() # get reject page login_testing_unauthorized(self, "reviewsecretary", reject_url) r = self.client.get(reject_url) self.assertEqual(r.status_code, 200) self.assertContains(r, assignment.reviewer.person.name) self.assertNotContains(r, 'can not be rejected') self.assertContains(r, '