Skip to content

Commit 36c43c8

Browse files
chore: add task tests; move message task to message app (#6964)
* test: Test send_review_reminders_task * refactor: Move send_scheduled_mail_task to message app * chore: Remove unused import * test: Add Message/SendQueue factories * test: Test send_scheduled_mail_task * test: Reset mocks before reuse * test: Cover error conditions * test: Return non-empty change set * test: Test SMTPException handling * test: Test fetch_attendance_from_meetings() * test: Test RuntimeError handling * test: RFC index sync should populate authors
1 parent 7438a4e commit 36c43c8

File tree

7 files changed

+195
-10
lines changed

7 files changed

+195
-10
lines changed

ietf/message/factories.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Copyright The IETF Trust 2024, All Rights Reserved
2+
import factory
3+
4+
from ietf.person.models import Person
5+
from .models import Message, SendQueue
6+
7+
8+
class MessageFactory(factory.django.DjangoModelFactory):
9+
class Meta:
10+
model = Message
11+
12+
by = factory.LazyFunction(lambda: Person.objects.get(name="(System)"))
13+
subject = factory.Faker("sentence")
14+
to = factory.Faker("email")
15+
frm = factory.Faker("email")
16+
cc = factory.Faker("email")
17+
bcc = factory.Faker("email")
18+
body = factory.Faker("paragraph")
19+
content_type = "text/plain"
20+
21+
22+
class SendQueueFactory(factory.django.DjangoModelFactory):
23+
class Meta:
24+
model = SendQueue
25+
26+
by = factory.LazyFunction(lambda: Person.objects.get(name="(System)"))
27+
message = factory.SubFactory(MessageFactory)
File renamed without changes.

ietf/message/tests.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
# Copyright The IETF Trust 2013-2020, All Rights Reserved
22
# -*- coding: utf-8 -*-
3-
4-
53
import datetime
4+
import mock
5+
6+
from smtplib import SMTPException
67

78
from django.urls import reverse as urlreverse
89
from django.utils import timezone
910

1011
import debug # pyflakes:ignore
1112

1213
from ietf.group.factories import GroupFactory
14+
from ietf.message.factories import SendQueueFactory
1315
from ietf.message.models import Message, SendQueue
16+
from ietf.message.tasks import send_scheduled_mail_task
1417
from ietf.message.utils import send_scheduled_message_from_send_queue
1518
from ietf.person.models import Person
1619
from ietf.utils.mail import outbox, send_mail_text, send_mail_message, get_payload_text
@@ -128,3 +131,22 @@ def test_send_mime_announcement(self):
128131
self.assertTrue("This is a test" in outbox[-1]["Subject"])
129132
self.assertTrue("--NextPart" in outbox[-1].as_string())
130133
self.assertTrue(SendQueue.objects.get(id=q.id).sent_at)
134+
135+
136+
class TaskTests(TestCase):
137+
@mock.patch("ietf.message.tasks.log_smtp_exception")
138+
@mock.patch("ietf.message.tasks.send_scheduled_message_from_send_queue")
139+
def test_send_scheduled_mail_task(self, mock_send_message, mock_log_smtp_exception):
140+
not_yet_sent = SendQueueFactory()
141+
SendQueueFactory(sent_at=timezone.now()) # already sent
142+
send_scheduled_mail_task()
143+
self.assertEqual(mock_send_message.call_count, 1)
144+
self.assertEqual(mock_send_message.call_args[0], (not_yet_sent,))
145+
self.assertFalse(mock_log_smtp_exception.called)
146+
147+
mock_send_message.reset_mock()
148+
mock_send_message.side_effect = SMTPException
149+
send_scheduled_mail_task()
150+
self.assertEqual(mock_send_message.call_count, 1)
151+
self.assertEqual(mock_send_message.call_args[0], (not_yet_sent,))
152+
self.assertTrue(mock_log_smtp_exception.called)

ietf/review/tests.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# Copyright The IETF Trust 2019-2020, All Rights Reserved
22
# -*- coding: utf-8 -*-
33
import datetime
4+
import mock
45
import debug # pyflakes:ignore
56

67
from pyquery import PyQuery
8+
79
from ietf.group.factories import RoleFactory
810
from ietf.doc.factories import WgDraftFactory
911
from ietf.utils.mail import empty_outbox, get_payload_text, outbox
@@ -13,6 +15,7 @@
1315
from .factories import ReviewAssignmentFactory, ReviewRequestFactory, ReviewerSettingsFactory
1416
from .mailarch import hash_list_message_id
1517
from .models import ReviewerSettings, ReviewSecretarySettings, ReviewTeamSettings, UnavailablePeriod
18+
from .tasks import send_review_reminders_task
1619
from .utils import (email_secretary_reminder, review_assignments_needing_secretary_reminder,
1720
email_reviewer_reminder, review_assignments_needing_reviewer_reminder,
1821
send_reminder_unconfirmed_assignments, send_review_reminder_overdue_assignment,
@@ -550,3 +553,66 @@ def test_review_add_comment(self):
550553
# But can't have the comment we are goint to add.
551554
self.assertContains(r, 'This is a test.')
552555

556+
557+
class TaskTests(TestCase):
558+
# hyaaa it's mockzilla
559+
@mock.patch("ietf.review.tasks.date_today")
560+
@mock.patch("ietf.review.tasks.review_assignments_needing_reviewer_reminder")
561+
@mock.patch("ietf.review.tasks.email_reviewer_reminder")
562+
@mock.patch("ietf.review.tasks.review_assignments_needing_secretary_reminder")
563+
@mock.patch("ietf.review.tasks.email_secretary_reminder")
564+
@mock.patch("ietf.review.tasks.send_unavailability_period_ending_reminder")
565+
@mock.patch("ietf.review.tasks.send_reminder_all_open_reviews")
566+
@mock.patch("ietf.review.tasks.send_review_reminder_overdue_assignment")
567+
@mock.patch("ietf.review.tasks.send_reminder_unconfirmed_assignments")
568+
def test_send_review_reminders_task(
569+
self,
570+
mock_send_reminder_unconfirmed_assignments,
571+
mock_send_review_reminder_overdue_assignment,
572+
mock_send_reminder_all_open_reviews,
573+
mock_send_unavailability_period_ending_reminder,
574+
mock_email_secretary_reminder,
575+
mock_review_assignments_needing_secretary_reminder,
576+
mock_email_reviewer_reminder,
577+
mock_review_assignments_needing_reviewer_reminder,
578+
mock_date_today,
579+
):
580+
"""Test that send_review_reminders calls functions correctly
581+
582+
Does not test individual methods, just that they are called as expected.
583+
"""
584+
mock_today = object()
585+
assignment = ReviewAssignmentFactory()
586+
secretary_role = RoleFactory(name_id="secr")
587+
588+
mock_date_today.return_value = mock_today
589+
mock_review_assignments_needing_reviewer_reminder.return_value = [assignment]
590+
mock_review_assignments_needing_secretary_reminder.return_value = [[assignment, secretary_role]]
591+
mock_send_unavailability_period_ending_reminder.return_value = ["pretending I sent a period end reminder"]
592+
mock_send_review_reminder_overdue_assignment.return_value = ["pretending I sent an overdue reminder"]
593+
mock_send_reminder_all_open_reviews.return_value = ["pretending I sent an open review reminder"]
594+
mock_send_reminder_unconfirmed_assignments.return_value = ["pretending I sent an unconfirmed reminder"]
595+
596+
send_review_reminders_task()
597+
598+
self.assertEqual(mock_review_assignments_needing_reviewer_reminder.call_count, 1)
599+
self.assertEqual(mock_review_assignments_needing_reviewer_reminder.call_args[0], (mock_today,))
600+
self.assertEqual(mock_email_reviewer_reminder.call_count, 1)
601+
self.assertEqual(mock_email_reviewer_reminder.call_args[0], (assignment,))
602+
603+
self.assertEqual(mock_review_assignments_needing_secretary_reminder.call_count, 1)
604+
self.assertEqual(mock_review_assignments_needing_secretary_reminder.call_args[0], (mock_today,))
605+
self.assertEqual(mock_email_secretary_reminder.call_count, 1)
606+
self.assertEqual(mock_email_secretary_reminder.call_args[0], (assignment, secretary_role))
607+
608+
self.assertEqual(mock_send_unavailability_period_ending_reminder.call_count, 1)
609+
self.assertEqual(mock_send_unavailability_period_ending_reminder.call_args[0], (mock_today,))
610+
611+
self.assertEqual(mock_send_review_reminder_overdue_assignment.call_count, 1)
612+
self.assertEqual(mock_send_review_reminder_overdue_assignment.call_args[0], (mock_today,))
613+
614+
self.assertEqual(mock_send_reminder_all_open_reviews.call_count, 1)
615+
self.assertEqual(mock_send_reminder_all_open_reviews.call_args[0], (mock_today,))
616+
617+
self.assertEqual(mock_send_reminder_unconfirmed_assignments.call_count, 1)
618+
self.assertEqual(mock_send_reminder_unconfirmed_assignments.call_args[0], (mock_today,))

ietf/stats/tests.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from ietf.stats.models import MeetingRegistration, CountryAlias
3131
from ietf.stats.factories import MeetingRegistrationFactory
3232
from ietf.stats.tasks import fetch_meeting_attendance_task
33-
from ietf.stats.utils import get_meeting_registration_data, FetchStats
33+
from ietf.stats.utils import get_meeting_registration_data, FetchStats, fetch_attendance_from_meetings
3434
from ietf.utils.timezone import date_today
3535

3636

@@ -302,6 +302,28 @@ def test_get_meeting_registration_data_duplicates(self, mock_get):
302302
query = MeetingRegistration.objects.all()
303303
self.assertEqual(query.count(), 2)
304304

305+
@patch("ietf.stats.utils.get_meeting_registration_data")
306+
def test_fetch_attendance_from_meetings(self, mock_get_mtg_reg_data):
307+
mock_meetings = [object(), object(), object()]
308+
mock_get_mtg_reg_data.side_effect = (
309+
(1, 2, 3),
310+
(4, 5, 6),
311+
(7, 8, 9),
312+
)
313+
stats = fetch_attendance_from_meetings(mock_meetings)
314+
self.assertEqual(
315+
[mock_get_mtg_reg_data.call_args_list[n][0][0] for n in range(3)],
316+
mock_meetings,
317+
)
318+
self.assertEqual(
319+
stats,
320+
[
321+
FetchStats(1, 2, 3),
322+
FetchStats(4, 5, 6),
323+
FetchStats(7, 8, 9),
324+
]
325+
)
326+
305327

306328
class TaskTests(TestCase):
307329
@patch("ietf.stats.tasks.fetch_attendance_from_meetings")
@@ -315,6 +337,12 @@ def test_fetch_meeting_attendance_task(self, mock_fetch_attendance):
315337
mock_fetch_attendance.return_value = [FetchStats(1,2,3), FetchStats(1,2,3)]
316338

317339
fetch_meeting_attendance_task()
318-
319340
self.assertEqual(mock_fetch_attendance.call_count, 1)
320341
self.assertCountEqual(mock_fetch_attendance.call_args[0][0], meetings[0:2])
342+
343+
# test handling of RuntimeError
344+
mock_fetch_attendance.reset_mock()
345+
mock_fetch_attendance.side_effect = RuntimeError
346+
fetch_meeting_attendance_task()
347+
self.assertTrue(mock_fetch_attendance.called)
348+
# Good enough that we got here without raising an exception

ietf/sync/tests.py

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import datetime
99
import mock
1010
import quopri
11+
import requests
1112

1213
from dataclasses import dataclass
1314

@@ -18,7 +19,7 @@
1819

1920
import debug # pyflakes:ignore
2021

21-
from ietf.doc.factories import WgDraftFactory, RfcFactory
22+
from ietf.doc.factories import WgDraftFactory, RfcFactory, DocumentAuthorFactory
2223
from ietf.doc.models import Document, DocEvent, DeletedEvent, DocTagName, RelatedDocument, State, StateDocEvent
2324
from ietf.doc.utils import add_state_change_event
2425
from ietf.group.factories import GroupFactory
@@ -238,6 +239,7 @@ def test_rfc_index(self):
238239
external_url="http://my-external-url.example.com",
239240
note="this is a note",
240241
)
242+
DocumentAuthorFactory.create_batch(2, document=draft_doc)
241243
draft_doc.action_holders.add(draft_doc.ad) # not normally set, but add to be sure it's cleared
242244

243245
RfcFactory(rfc_number=123)
@@ -381,6 +383,7 @@ def test_rfc_index(self):
381383

382384
rfc_doc = Document.objects.filter(rfc_number=1234, type_id="rfc").first()
383385
self.assertIsNotNone(rfc_doc, "RFC document should have been created")
386+
self.assertEqual(rfc_doc.authors(), draft_doc.authors())
384387
rfc_events = rfc_doc.docevent_set.all()
385388
self.assertEqual(len(rfc_events), 8)
386389
expected_events = [
@@ -715,11 +718,14 @@ def json(self):
715718
errata_response = MockResponse(
716719
text="these are the errata", json_length=rfceditor.MIN_ERRATA_RESULTS
717720
)
718-
721+
rfc = RfcFactory()
722+
719723
# Test with full_index = False
720724
requests_get_mock.side_effect = (index_response, errata_response) # will step through these
721725
parse_index_mock.return_value = MockIndexData(length=rfceditor.MIN_INDEX_RESULTS)
722-
update_docs_mock.return_value = [] # not tested
726+
update_docs_mock.return_value = (
727+
(rfc.rfc_number, ("something changed",), rfc, False),
728+
)
723729

724730
tasks.rfc_editor_index_update_task(full_index=False)
725731

@@ -741,9 +747,10 @@ def json(self):
741747
self.assertIsNotNone(update_docs_kwargs["skip_older_than_date"])
742748

743749
# Test again with full_index = True
750+
requests_get_mock.reset_mock()
751+
parse_index_mock.reset_mock()
752+
update_docs_mock.reset_mock()
744753
requests_get_mock.side_effect = (index_response, errata_response) # will step through these
745-
parse_index_mock.return_value = MockIndexData(length=rfceditor.MIN_INDEX_RESULTS)
746-
update_docs_mock.return_value = [] # not tested
747754
tasks.rfc_editor_index_update_task(full_index=True)
748755

749756
# Check parse_index() call
@@ -762,3 +769,38 @@ def json(self):
762769
update_docs_args, (parse_index_mock.return_value, errata_response.json())
763770
)
764771
self.assertIsNone(update_docs_kwargs["skip_older_than_date"])
772+
773+
# Test error handling
774+
requests_get_mock.reset_mock()
775+
parse_index_mock.reset_mock()
776+
update_docs_mock.reset_mock()
777+
requests_get_mock.side_effect = requests.Timeout # timeout on every get()
778+
tasks.rfc_editor_index_update_task(full_index=False)
779+
self.assertFalse(parse_index_mock.called)
780+
self.assertFalse(update_docs_mock.called)
781+
782+
requests_get_mock.reset_mock()
783+
parse_index_mock.reset_mock()
784+
update_docs_mock.reset_mock()
785+
requests_get_mock.side_effect = [index_response, requests.Timeout] # timeout second get()
786+
tasks.rfc_editor_index_update_task(full_index=False)
787+
self.assertFalse(update_docs_mock.called)
788+
789+
requests_get_mock.reset_mock()
790+
parse_index_mock.reset_mock()
791+
update_docs_mock.reset_mock()
792+
requests_get_mock.side_effect = [index_response, errata_response]
793+
# feed in an index that is too short
794+
parse_index_mock.return_value = MockIndexData(length=rfceditor.MIN_INDEX_RESULTS - 1)
795+
tasks.rfc_editor_index_update_task(full_index=False)
796+
self.assertTrue(parse_index_mock.called)
797+
self.assertFalse(update_docs_mock.called)
798+
799+
requests_get_mock.reset_mock()
800+
parse_index_mock.reset_mock()
801+
update_docs_mock.reset_mock()
802+
requests_get_mock.side_effect = [index_response, errata_response]
803+
errata_response.json_length = rfceditor.MIN_ERRATA_RESULTS - 1 # too short
804+
parse_index_mock.return_value = MockIndexData(length=rfceditor.MIN_INDEX_RESULTS)
805+
tasks.rfc_editor_index_update_task(full_index=False)
806+
self.assertFalse(update_docs_mock.called)

ietf/utils/management/commands/periodic_tasks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def get_or_create_crontabs(self):
5656
def create_default_tasks(self):
5757
PeriodicTask.objects.get_or_create(
5858
name="Send scheduled mail",
59-
task="ietf.utils.tasks.send_scheduled_mail_task",
59+
task="ietf.meeting.tasks.send_scheduled_mail_task",
6060
defaults=dict(
6161
enabled=False,
6262
crontab=self.crontabs["every_15m"],

0 commit comments

Comments
 (0)