datatracker/ietf/group/tests_review.py
Sasha Romijn 554a839864 Fix #2336 - Add "select me next for an assignment".
Reviewers can set this flag in their reviewer settings, which triggers
a mail to be sent to the secretary. They are then kept on top of the
recommended assignment order. This flag is automatically reset when any
assignment is made to the reviewer.
 - Legacy-Id: 17048
2019-11-18 12:30:11 +00:00

685 lines
34 KiB
Python

# Copyright The IETF Trust 2016-2019, All Rights Reserved
# -*- coding: utf-8 -*-
from __future__ import absolute_import, print_function, unicode_literals
import datetime
import debug # pyflakes:ignore
import six
from pyquery import PyQuery
from django.urls import reverse as urlreverse
from ietf.review.policies import get_reviewer_queue_policy
from ietf.utils.test_utils import login_testing_unauthorized, TestCase, reload_db_objects
from ietf.doc.models import TelechatDocEvent
from ietf.group.models import Role
from ietf.iesg.models import TelechatDate
from ietf.person.models import Person
from ietf.review.models import ( ReviewerSettings, UnavailablePeriod, ReviewSecretarySettings,
ReviewTeamSettings )
from ietf.review.utils import (
suggested_review_requests_for_team,
review_assignments_needing_reviewer_reminder, email_reviewer_reminder,
review_assignments_needing_secretary_reminder, email_secretary_reminder,
send_unavaibility_period_ending_reminder, send_reminder_all_open_reviews,
send_review_reminder_overdue_assignment, send_reminder_unconfirmed_assignments)
from ietf.name.models import ReviewResultName, ReviewRequestStateName, ReviewAssignmentStateName
import ietf.group.views
from ietf.utils.mail import outbox, empty_outbox
from ietf.dbtemplate.factories import DBTemplateFactory
from ietf.person.factories import PersonFactory, EmailFactory
from ietf.doc.factories import DocumentFactory
from ietf.group.factories import RoleFactory, ReviewTeamFactory
from ietf.review.factories import ReviewRequestFactory, ReviewerSettingsFactory, ReviewAssignmentFactory
class ReviewTests(TestCase):
def test_review_requests(self):
review_req = ReviewRequestFactory(state_id='assigned')
assignment = ReviewAssignmentFactory(review_request=review_req, state_id='assigned', reviewer=EmailFactory(), assigned_on = review_req.time)
group = review_req.team
for url in [urlreverse(ietf.group.views.review_requests, kwargs={ 'acronym': group.acronym }),
urlreverse(ietf.group.views.review_requests, kwargs={ 'acronym': group.acronym , 'group_type': group.type_id})]:
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertContains(r, review_req.doc.name)
self.assertContains(r, str(assignment.reviewer.person))
url = urlreverse(ietf.group.views.review_requests, kwargs={ 'acronym': group.acronym })
# close request, listed under closed
review_req.state = ReviewRequestStateName.objects.get(slug="completed")
review_req.result = ReviewResultName.objects.get(slug="ready")
review_req.save()
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertContains(r, review_req.doc.name)
def test_suggested_review_requests(self):
review_req = ReviewRequestFactory(state_id='assigned')
assignment = ReviewAssignmentFactory(review_request=review_req, state_id='assigned')
doc = review_req.doc
team = review_req.team
# put on telechat
e = TelechatDocEvent.objects.create(
type="scheduled_for_telechat",
by=Person.objects.get(name="(System)"),
doc=doc,
rev=doc.rev,
telechat_date=TelechatDate.objects.all().first().date,
)
doc.rev = "10"
doc.save_with_history([e])
prev_rev = "{:02}".format(int(doc.rev) - 1)
# blocked by existing request
review_req.requested_rev = ""
review_req.save()
self.assertEqual(len(suggested_review_requests_for_team(team)), 0)
# ... but not to previous version
review_req.requested_rev = prev_rev
review_req.save()
suggestions = suggested_review_requests_for_team(team)
self.assertEqual(len(suggestions), 1)
self.assertEqual(suggestions[0].doc, doc)
self.assertEqual(suggestions[0].team, team)
# blocked by non-versioned refusal
review_req.requested_rev = ""
review_req.state = ReviewRequestStateName.objects.get(slug="no-review-document")
review_req.save()
self.assertEqual(list(suggested_review_requests_for_team(team)), [])
# blocked by versioned refusal
review_req.state = ReviewRequestStateName.objects.get(slug="no-review-version")
review_req.save()
self.assertEqual(list(suggested_review_requests_for_team(team)), [])
# blocked by completion
review_req.state = ReviewRequestStateName.objects.get(slug="assigned")
review_req.save()
assignment.state = ReviewAssignmentStateName.objects.get(slug="completed")
assignment.reviewed_rev = review_req.doc.rev
assignment.save()
self.assertEqual(list(suggested_review_requests_for_team(team)), [])
# ... but not to previous version
assignment.reviewed_rev = prev_rev
assignment.save()
self.assertEqual(len(suggested_review_requests_for_team(team)), 1)
def test_reviewer_overview(self):
team = ReviewTeamFactory()
reviewer = RoleFactory(name_id='reviewer',group=team,person__user__username='reviewer').person
ReviewerSettingsFactory(person=reviewer,team=team)
review_req1 = ReviewRequestFactory(state_id='completed',team=team)
ReviewAssignmentFactory(review_request = review_req1, reviewer=reviewer.email())
PersonFactory(user__username='plain')
ReviewAssignmentFactory(
review_request__doc=review_req1.doc,
review_request__team=review_req1.team,
review_request__type_id="early",
review_request__deadline=datetime.date.today() + datetime.timedelta(days=30),
review_request__state_id="assigned",
review_request__requested_by=Person.objects.get(user__username="reviewer"),
state_id = "accepted",
reviewer=reviewer.email_set.first(),
)
UnavailablePeriod.objects.create(
team=review_req1.team,
person=reviewer,
start_date=datetime.date.today() - datetime.timedelta(days=10),
availability="unavailable",
)
settings = ReviewerSettings.objects.get(person=reviewer,team=review_req1.team)
settings.skip_next = 1
settings.save()
group = review_req1.team
# get
for url in [urlreverse(ietf.group.views.reviewer_overview, kwargs={ 'acronym': group.acronym }),
urlreverse(ietf.group.views.reviewer_overview, kwargs={ 'acronym': group.acronym, 'group_type': group.type_id })]:
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertContains(r, str(reviewer))
self.assertContains(r, review_req1.doc.name)
# without a login, reason for being unavailable should not be seen
self.assertNotContains(r, "Availability")
url = urlreverse(ietf.group.views.reviewer_overview, kwargs={ 'acronym': group.acronym })
self.client.login(username="plain", password="plain+password")
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
# not on review team, should not see reason for being unavailable
self.assertNotContains(r, "Availability")
self.client.login(username="reviewer", password="reviewer+password")
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
# review team members can see reason for being unavailable
self.assertContains(r, "Availability")
self.client.login(username="secretary", password="secretary+password")
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
# secretariat can see reason for being unavailable
self.assertContains(r, "Availability")
def test_manage_review_requests(self):
group = ReviewTeamFactory()
RoleFactory(name_id='reviewer',group=group,person__user__username='reviewer').person
marsperson = RoleFactory(name_id='reviewer',group=group,person=PersonFactory(name="Mars Anders Chairman",user__username='marschairman')).person
doc_author = PersonFactory()
review_req1 = ReviewRequestFactory(doc__pages=2,doc__shepherd=marsperson.email(),team=group, doc__authors=[doc_author])
review_req2 = ReviewRequestFactory(team=group)
review_req3 = ReviewRequestFactory(team=group)
RoleFactory(name_id='chair',group=review_req1.doc.group,person=marsperson)
unassigned_url = urlreverse(ietf.group.views.manage_review_requests, kwargs={ 'acronym': group.acronym, 'group_type': group.type_id, "assignment_status": "unassigned" })
login_testing_unauthorized(self, "secretary", unassigned_url)
# 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 exsting one
another_reviewer.role_set.create(name_id='reviewer', email=another_reviewer.email(), group=review_req1.team)
ReviewerSettingsFactory(team=review_req3.team, person = another_reviewer)
yet_another_reviewer = PersonFactory.create(name = "YetAnotherExtra TestReviewer") # needs to be lexically greater than the exsting one
yet_another_reviewer.role_set.create(name_id='reviewer', email=yet_another_reviewer.email(), group=review_req1.team)
ReviewerSettingsFactory(team=review_req3.team, person = yet_another_reviewer)
# get
r = self.client.get(unassigned_url)
self.assertEqual(r.status_code, 200)
self.assertContains(r, review_req1.doc.name)
self.assertContains(r, doc_author.name)
# Test that conflicts are detected
r = self.client.post(unassigned_url, {
"reviewrequest": [str(review_req3.pk)],
"r{}-existing_reviewer".format(review_req3.pk): "",
"r{}-action".format(review_req3.pk): "assign",
"r{}-reviewer".format(review_req3.pk): another_reviewer.email_set.first().pk,
"r{}-add_skip".format(review_req3.pk): 1,
"action": "save",
})
self.assertContains(r, "2 requests opened")
r = self.client.post(unassigned_url, {
"reviewrequest": [str(review_req1.pk),str(review_req2.pk),str(review_req3.pk)],
"r{}-existing_reviewer".format(review_req3.pk): "",
"r{}-action".format(review_req3.pk): "assign",
"r{}-reviewer".format(review_req3.pk): another_reviewer.email_set.first().pk,
"r{}-add_skip".format(review_req3.pk): 1,
"action": "save",
})
self.assertEqual(r.status_code, 302)
review_req3 = reload_db_objects(review_req3)
settings = ReviewerSettings.objects.filter(team=review_req3.team, person=another_reviewer).first()
self.assertEqual(settings.skip_next,1)
self.assertEqual(review_req3.state_id, "assigned")
def test_email_open_review_assignments(self):
review_req1 = ReviewRequestFactory()
review_assignment_completed = ReviewAssignmentFactory(review_request=review_req1,reviewer=EmailFactory(person__user__username='marschairman'), state_id='completed', reviewed_rev=0)
ReviewAssignmentFactory(review_request=review_req1,reviewer=review_assignment_completed.reviewer)
TelechatDocEvent.objects.create(telechat_date=datetime.date.today(), type='scheduled_for_telechat', by=review_assignment_completed.reviewer.person, doc=review_req1.doc, rev=0)
DBTemplateFactory.create(path='/group/defaults/email/open_assignments.txt',
type_id='django',
content = """
{% autoescape off %}Subject: Open review assignments in {{group.acronym}}
The following reviewers have assignments:{% for r in review_assignments %}{% ifchanged r.section %}
{{r.section}}
{% if r.section == 'Early review requests:' %}Reviewer Due Draft{% else %}Reviewer LC end Draft{% endif %}{% endifchanged %}
{{ r.reviewer.person.plain_name|ljust:"22" }} {% if r.section == 'Early review requests:' %}{{ r.review_request.deadline|date:"Y-m-d" }}{% else %}{{ r.lastcall_ends|default:"None " }}{% endif %} {{ r.review_request.doc.name }}-{% if r.review_request.requested_rev %}{{ r.review_request.requested_rev }}{% else %}{{ r.review_request..doc.rev }}{% endif %} {{ r.earlier_reviews }}{% endfor %}
{% if rotation_list %}Next in the reviewer rotation:
{% for p in rotation_list %} {{ p }}
{% endfor %}{% endif %}{% endautoescape %}
""")
group = review_req1.team
url = urlreverse(ietf.group.views.email_open_review_assignments, kwargs={ 'acronym': group.acronym })
login_testing_unauthorized(self, "secretary", url)
url = urlreverse(ietf.group.views.email_open_review_assignments, kwargs={ 'acronym': group.acronym, 'group_type': group.type_id })
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
generated_text = q("[name=body]").text()
# The document should be listed both for the telechat, and in the last call section,
# i.e. the document name is expected twice in the output (#2118)
self.assertEqual(generated_text.count(review_req1.doc.name), 2)
self.assertEqual(generated_text.count('(-0 lc reviewed)'), 2) # previous completed assignment
self.assertTrue(six.text_type(Person.objects.get(user__username="marschairman")) in generated_text)
empty_outbox()
r = self.client.post(url, {
"to": 'toaddr@bogus.test',
"cc": 'ccaddr@bogus.test',
"reply_to": 'replytoaddr@bogus.test',
"frm" : 'fromaddr@bogus.test',
"subject": "Test subject",
"body": "Test body",
"action": "email",
})
self.assertEqual(r.status_code, 302)
self.assertEqual(len(outbox), 1)
self.assertIn('toaddr', outbox[0]["To"])
self.assertIn('ccaddr', outbox[0]["Cc"])
self.assertIn('replytoaddr', outbox[0]["Reply-To"])
self.assertIn('fromaddr', outbox[0]["From"])
self.assertEqual(outbox[0]["subject"], "Test subject")
self.assertIn("Test body", outbox[0].get_payload(decode=True).decode("utf-8"))
def test_change_reviewer_settings(self):
reviewer = ReviewerSettingsFactory(person__user__username='reviewer',expertise='Some expertise').person
review_req = ReviewRequestFactory()
assignment = ReviewAssignmentFactory(review_request=review_req,reviewer=reviewer.email())
RoleFactory(name_id='reviewer',group=review_req.team,person=assignment.reviewer.person)
RoleFactory(name_id='secr',group=review_req.team)
url = urlreverse(ietf.group.views.change_reviewer_settings, kwargs={
"acronym": review_req.team.acronym,
"reviewer_email": assignment.reviewer_id,
})
login_testing_unauthorized(self, reviewer.user.username, url)
url = urlreverse(ietf.group.views.change_reviewer_settings, kwargs={
"group_type": review_req.team.type_id,
"acronym": review_req.team.acronym,
"reviewer_email": assignment.reviewer_id,
})
# get
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.context['period_form']['start_date'].initial, datetime.date.today())
# set settings
empty_outbox()
r = self.client.post(url, {
"action": "change_settings",
"min_interval": "7",
"filter_re": "test-[regexp]",
"remind_days_before_deadline": "6",
"remind_days_open_reviews": "8",
"request_assignment_next": "1",
"expertise": "Some expertise",
})
self.assertEqual(r.status_code, 302)
settings = ReviewerSettings.objects.get(person=reviewer, team=review_req.team)
self.assertEqual(settings.min_interval, 7)
self.assertEqual(settings.filter_re, "test-[regexp]")
self.assertEqual(settings.skip_next, 0)
self.assertEqual(settings.remind_days_before_deadline, 6)
self.assertEqual(settings.remind_days_open_reviews, 8)
self.assertEqual(settings.expertise, "Some expertise")
self.assertEqual(len(outbox), 1)
self.assertTrue("reviewer availability" in outbox[0]["subject"].lower())
msg_content = outbox[0].get_payload(decode=True).decode("utf-8").lower()
self.assertTrue("frequency changed", msg_content)
self.assertTrue("skip next", msg_content)
self.assertTrue("requested to be the next person", msg_content)
# Normal reviewer should not be able to change skip_next
r = self.client.post(url, {
"action": "change_settings",
"min_interval": "7",
"filter_re": "test-[regexp]",
"remind_days_before_deadline": "6",
"skip_next" : "2",
})
self.assertEqual(r.status_code, 302)
settings = ReviewerSettings.objects.get(person=reviewer, team=review_req.team)
self.assertEqual(settings.skip_next, 0)
# add unavailable period
start_date = datetime.date.today() + datetime.timedelta(days=10)
empty_outbox()
r = self.client.post(url, {
"action": "add_period",
'start_date': start_date.isoformat(),
'end_date': "",
'availability': "unavailable",
'reason': "Whimsy",
})
self.assertEqual(r.status_code, 302)
period = UnavailablePeriod.objects.get(person=reviewer, team=review_req.team, start_date=start_date)
self.assertEqual(period.end_date, None)
self.assertEqual(period.availability, "unavailable")
self.assertEqual(len(outbox), 1)
msg_content = outbox[0].get_payload(decode=True).decode("utf-8").lower()
self.assertTrue(start_date.isoformat(), msg_content)
self.assertTrue("indefinite", msg_content)
self.assertEqual(period.reason, "Whimsy")
# end unavailable period
empty_outbox()
end_date = start_date + datetime.timedelta(days=10)
r = self.client.post(url, {
"action": "end_period",
'period_id': period.pk,
'end_date': end_date.isoformat(),
})
self.assertEqual(r.status_code, 302)
period = reload_db_objects(period)
self.assertEqual(period.end_date, end_date)
self.assertEqual(len(outbox), 1)
msg_content = outbox[0].get_payload(decode=True).decode("utf-8").lower()
self.assertTrue(start_date.isoformat(), msg_content)
self.assertTrue("indefinite", msg_content)
# delete unavailable period
empty_outbox()
r = self.client.post(url, {
"action": "delete_period",
'period_id': period.pk,
})
self.assertEqual(r.status_code, 302)
self.assertEqual(UnavailablePeriod.objects.filter(person=reviewer, team=review_req.team, start_date=start_date).count(), 0)
self.assertEqual(len(outbox), 1)
msg_content = outbox[0].get_payload(decode=True).decode("utf-8").lower()
self.assertTrue(start_date.isoformat(), msg_content)
self.assertTrue(end_date.isoformat(), msg_content)
# secretaries and the secretariat should be able to change skip_next
for username in ["secretary","reviewsecretary"]:
skip_next_val = {'secretary':'3','reviewsecretary':'4'}[username]
self.client.login(username=username,password=username+"+password")
r = self.client.post(url, {
"action": "change_settings",
"min_interval": "7",
"filter_re": "test-[regexp]",
"remind_days_before_deadline": "6",
"skip_next" : skip_next_val,
})
self.assertEqual(r.status_code, 302)
settings = ReviewerSettings.objects.get(person=reviewer, team=review_req.team)
self.assertEqual(settings.skip_next, int(skip_next_val))
def test_change_review_secretary_settings(self):
review_req = ReviewRequestFactory()
secretary = RoleFactory(name_id='secr',group=review_req.team,person__user__username='reviewsecretary').person
url = urlreverse(ietf.group.views.change_review_secretary_settings, kwargs={
"acronym": review_req.team.acronym,
})
login_testing_unauthorized(self, secretary.user.username, url)
url = urlreverse(ietf.group.views.change_review_secretary_settings, kwargs={
"group_type": review_req.team.type_id,
"acronym": review_req.team.acronym,
})
# get
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
# set settings
r = self.client.post(url, {
"remind_days_before_deadline": "6"
})
self.assertEqual(r.status_code, 302)
settings = ReviewSecretarySettings.objects.get(person=secretary, team=review_req.team)
self.assertEqual(settings.remind_days_before_deadline, 6)
def test_review_reminders(self):
review_req = ReviewRequestFactory(state_id='assigned')
reviewer = RoleFactory(name_id='reviewer',group=review_req.team,person__user__username='reviewer').person
assignment = ReviewAssignmentFactory(review_request=review_req, state_id='assigned', assigned_on = review_req.time, reviewer=reviewer.email_set.first())
RoleFactory(name_id='secr',group=review_req.team,person__user__username='reviewsecretary')
ReviewerSettingsFactory(team = review_req.team, person = reviewer)
remind_days = 6
reviewer_settings = ReviewerSettings.objects.get(team=review_req.team, person=reviewer)
reviewer_settings.remind_days_before_deadline = remind_days
reviewer_settings.save()
secretary = Person.objects.get(user__username="reviewsecretary")
secretary_role = Role.objects.get(group=review_req.team, name="secr", person=secretary)
secretary_settings = ReviewSecretarySettings(team=review_req.team, person=secretary)
secretary_settings.remind_days_before_deadline = remind_days
secretary_settings.save()
today = datetime.date.today()
review_req.reviewer = reviewer.email_set.first()
review_req.deadline = today + datetime.timedelta(days=remind_days)
review_req.save()
# reviewer
needing_reminders = review_assignments_needing_reviewer_reminder(today - datetime.timedelta(days=1))
self.assertEqual(list(needing_reminders), [])
needing_reminders = review_assignments_needing_reviewer_reminder(today)
self.assertEqual(list(needing_reminders), [assignment])
needing_reminders = review_assignments_needing_reviewer_reminder(today + datetime.timedelta(days=1))
self.assertEqual(list(needing_reminders), [])
# secretary
needing_reminders = review_assignments_needing_secretary_reminder(today - datetime.timedelta(days=1))
self.assertEqual(list(needing_reminders), [])
needing_reminders = review_assignments_needing_secretary_reminder(today)
self.assertEqual(list(needing_reminders), [(assignment, secretary_role)])
needing_reminders = review_assignments_needing_secretary_reminder(today + datetime.timedelta(days=1))
self.assertEqual(list(needing_reminders), [])
# email reviewer
empty_outbox()
email_reviewer_reminder(assignment)
self.assertEqual(len(outbox), 1)
self.assertTrue(review_req.doc.name in outbox[0].get_payload(decode=True).decode("utf-8"))
# email secretary
empty_outbox()
email_secretary_reminder(assignment, secretary_role)
self.assertEqual(len(outbox), 1)
self.assertTrue(review_req.doc.name in outbox[0].get_payload(decode=True).decode("utf-8"))
def test_send_unavaibility_period_ending_reminder(self):
review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review",
list_email="reviewteam@ietf.org")
reviewer = RoleFactory(group=review_team, person__user__username='reviewer',
person__user__email='reviewer@example.com',
person__name='Some Reviewer', name_id='reviewer')
secretary = RoleFactory(group=review_team, person__user__username='reviewsecretary',
person__user__email='reviewsecretary@example.com', name_id='secr')
empty_outbox()
today = datetime.date.today()
UnavailablePeriod.objects.create(
team=review_team,
person=reviewer.person,
start_date=today - datetime.timedelta(days=40),
end_date=today + datetime.timedelta(days=3),
availability="unavailable",
)
UnavailablePeriod.objects.create(
team=review_team,
person=reviewer.person,
# This object should be ignored, length is too short
start_date=today - datetime.timedelta(days=20),
end_date=today + datetime.timedelta(days=3),
availability="unavailable",
)
UnavailablePeriod.objects.create(
team=review_team,
person=reviewer.person,
start_date=today - datetime.timedelta(days=40),
# This object should be ignored, end date is too far away
end_date=today + datetime.timedelta(days=4),
availability="unavailable",
)
UnavailablePeriod.objects.create(
team=review_team,
person=reviewer.person,
# This object should be ignored, end date is too close
start_date=today - datetime.timedelta(days=40),
end_date=today + datetime.timedelta(days=2),
availability="unavailable",
)
log = send_unavaibility_period_ending_reminder(today)
self.assertEqual(len(outbox), 1)
self.assertTrue(reviewer.person.email_address() in outbox[0]["To"])
self.assertTrue(secretary.person.email_address() in outbox[0]["To"])
message = outbox[0].get_payload(decode=True).decode("utf-8")
self.assertTrue(reviewer.person.name in message)
self.assertTrue(review_team.acronym in message)
self.assertEqual(len(log), 1)
self.assertTrue(reviewer.person.name in log[0])
self.assertTrue(review_team.acronym in log[0])
def test_send_review_reminder_overdue_assignment(self):
today = datetime.date.today()
# An assignment that's exactly on the date at which the grace period expires
review_req = ReviewRequestFactory(state_id='assigned', deadline=today - datetime.timedelta(5))
reviewer = RoleFactory(name_id='reviewer', group=review_req.team,person__user__username='reviewer').person
ReviewAssignmentFactory(review_request=review_req, state_id='assigned', assigned_on=review_req.time, reviewer=reviewer.email_set.first())
secretary = RoleFactory(name_id='secr', group=review_req.team, person__user__username='reviewsecretary')
# A assignment that is not yet overdue
not_overdue = today + datetime.timedelta(days=1)
ReviewAssignmentFactory(review_request__team=review_req.team, review_request__state_id='assigned', review_request__deadline=not_overdue, state_id='assigned', assigned_on=not_overdue, reviewer=reviewer.email_set.first())
# An assignment that is overdue but is not past the grace period
in_grace_period = today - datetime.timedelta(days=1)
ReviewAssignmentFactory(review_request__team=review_req.team, review_request__state_id='assigned', review_request__deadline=in_grace_period, state_id='assigned', assigned_on=in_grace_period, reviewer=reviewer.email_set.first())
empty_outbox()
log = send_review_reminder_overdue_assignment(today)
self.assertEqual(len(log), 1)
self.assertEqual(len(outbox), 1)
self.assertTrue(secretary.person.email_address() in outbox[0]["To"])
self.assertEqual(outbox[0]["Subject"], "1 Overdue review for team {}".format(review_req.team.acronym))
message = outbox[0].get_payload(decode=True).decode("utf-8")
self.assertIn(review_req.team.acronym + ' has 1 accepted or assigned review overdue by at least 5 days.', message)
self.assertIn('Review of {} by {}'.format(review_req.doc.name, reviewer.plain_name()), message)
self.assertEqual(len(log), 1)
self.assertIn(secretary.person.email_address(), log[0])
self.assertIn('1 overdue review', log[0])
def test_send_reminder_all_open_reviews(self):
review_req = ReviewRequestFactory(state_id='assigned')
reviewer = RoleFactory(name_id='reviewer', group=review_req.team,person__user__username='reviewer').person
ReviewAssignmentFactory(review_request=review_req, state_id='assigned', assigned_on=review_req.time, reviewer=reviewer.email_set.first())
RoleFactory(name_id='secr', group=review_req.team, person__user__username='reviewsecretary')
ReviewerSettingsFactory(team=review_req.team, person=reviewer, remind_days_open_reviews=1)
empty_outbox()
today = datetime.date.today()
log = send_reminder_all_open_reviews(today)
self.assertEqual(len(outbox), 1)
self.assertTrue(reviewer.email_address() in outbox[0]["To"])
self.assertEqual(outbox[0]["Subject"], "Reminder: you have 1 open review assignment")
message = outbox[0].get_payload(decode=True).decode("utf-8")
self.assertTrue(review_req.team.acronym in message)
self.assertTrue('you have 1 open review' in message)
self.assertTrue(review_req.doc.name in message)
self.assertTrue(review_req.deadline.strftime('%Y-%m-%d') in message)
self.assertEqual(len(log), 1)
self.assertTrue(reviewer.email_address() in log[0])
self.assertTrue('1 open review' in log[0])
def test_send_reminder_unconfirmed_assignments(self):
review_req = ReviewRequestFactory(state_id='assigned')
reviewer = RoleFactory(name_id='reviewer', group=review_req.team, person__user__username='reviewer').person
ReviewAssignmentFactory(review_request=review_req, state_id='assigned', assigned_on=review_req.time, reviewer=reviewer.email_set.first())
RoleFactory(name_id='secr', group=review_req.team, person__user__username='reviewsecretary')
today = datetime.date.today()
# By default, these reminders are disabled for all teams.
empty_outbox()
log = send_reminder_unconfirmed_assignments(today)
self.assertEqual(len(outbox), 0)
self.assertFalse(log)
ReviewTeamSettings.objects.update(remind_days_unconfirmed_assignments=1)
empty_outbox()
log = send_reminder_unconfirmed_assignments(today)
self.assertEqual(len(outbox), 1)
self.assertIn(reviewer.email_address(), outbox[0]["To"])
self.assertEqual(outbox[0]["Subject"], "Reminder: you have not responded to a review assignment")
message = outbox[0].get_payload(decode=True).decode("utf-8")
self.assertIn(review_req.team.acronym, message)
self.assertIn('accept or reject the assignment on', message)
self.assertIn(review_req.doc.name, message)
self.assertEqual(len(log), 1)
self.assertIn(reviewer.email_address(), log[0])
self.assertIn('not accepted/rejected review assignment', log[0])
class BulkAssignmentTests(TestCase):
def test_rotation_queue_update(self):
group = ReviewTeamFactory.create()
empty_outbox()
reviewers = [RoleFactory.create(group=group,name_id='reviewer') for i in range(6)] # pyflakes:ignore
secretary = RoleFactory.create(group=group,name_id='secr')
docs = [DocumentFactory.create(type_id='draft',group=None) for i in range(4)]
requests = [ReviewRequestFactory(team=group,doc=docs[i]) for i in range(4)]
policy = get_reviewer_queue_policy(group)
rot_list = policy.default_reviewer_rotation_list()
expected_ending_head_of_rotation = rot_list[3]
unassigned_url = urlreverse(ietf.group.views.manage_review_requests, kwargs={ 'acronym': group.acronym, 'group_type': group.type_id, "assignment_status": "unassigned" })
postdict = {}
postdict['reviewrequest'] = [r.id for r in requests]
# assignments that affect the first 3 reviewers in queue
for i in range(3):
postdict['r{}-existing_reviewer'.format(requests[i].pk)] = ''
postdict['r{}-action'.format(requests[i].pk)] = 'assign'
postdict['r{}-reviewer'.format(requests[i].pk)] = rot_list[i].email_address()
# and one out of order assignment
postdict['r{}-existing_reviewer'.format(requests[3].pk)] = ''
postdict['r{}-action'.format(requests[3].pk)] = 'assign'
postdict['r{}-reviewer'.format(requests[3].pk)] = rot_list[5].email_address()
postdict['action'] = 'save'
self.client.login(username=secretary.person.user.username,password=secretary.person.user.username+'+password')
r = self.client.post(unassigned_url, postdict)
self.assertEqual(r.status_code,302)
self.assertEqual(expected_ending_head_of_rotation, policy.default_reviewer_rotation_list()[0])
self.assertMailboxContains(outbox, subject='Last Call assignment', text='Requested by', count=4)