Skip to content

Commit

Permalink
chore: add task tests; move message task to message app (#6964)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
jennifer-richards committed Jan 24, 2024
1 parent 7438a4e commit 36c43c8
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 10 deletions.
27 changes: 27 additions & 0 deletions ietf/message/factories.py
@@ -0,0 +1,27 @@
# Copyright The IETF Trust 2024, All Rights Reserved
import factory

from ietf.person.models import Person
from .models import Message, SendQueue


class MessageFactory(factory.django.DjangoModelFactory):
class Meta:
model = Message

by = factory.LazyFunction(lambda: Person.objects.get(name="(System)"))
subject = factory.Faker("sentence")
to = factory.Faker("email")
frm = factory.Faker("email")
cc = factory.Faker("email")
bcc = factory.Faker("email")
body = factory.Faker("paragraph")
content_type = "text/plain"


class SendQueueFactory(factory.django.DjangoModelFactory):
class Meta:
model = SendQueue

by = factory.LazyFunction(lambda: Person.objects.get(name="(System)"))
message = factory.SubFactory(MessageFactory)
File renamed without changes.
26 changes: 24 additions & 2 deletions ietf/message/tests.py
@@ -1,16 +1,19 @@
# Copyright The IETF Trust 2013-2020, All Rights Reserved
# -*- coding: utf-8 -*-


import datetime
import mock

from smtplib import SMTPException

from django.urls import reverse as urlreverse
from django.utils import timezone

import debug # pyflakes:ignore

from ietf.group.factories import GroupFactory
from ietf.message.factories import SendQueueFactory
from ietf.message.models import Message, SendQueue
from ietf.message.tasks import send_scheduled_mail_task
from ietf.message.utils import send_scheduled_message_from_send_queue
from ietf.person.models import Person
from ietf.utils.mail import outbox, send_mail_text, send_mail_message, get_payload_text
Expand Down Expand Up @@ -128,3 +131,22 @@ def test_send_mime_announcement(self):
self.assertTrue("This is a test" in outbox[-1]["Subject"])
self.assertTrue("--NextPart" in outbox[-1].as_string())
self.assertTrue(SendQueue.objects.get(id=q.id).sent_at)


class TaskTests(TestCase):
@mock.patch("ietf.message.tasks.log_smtp_exception")
@mock.patch("ietf.message.tasks.send_scheduled_message_from_send_queue")
def test_send_scheduled_mail_task(self, mock_send_message, mock_log_smtp_exception):
not_yet_sent = SendQueueFactory()
SendQueueFactory(sent_at=timezone.now()) # already sent
send_scheduled_mail_task()
self.assertEqual(mock_send_message.call_count, 1)
self.assertEqual(mock_send_message.call_args[0], (not_yet_sent,))
self.assertFalse(mock_log_smtp_exception.called)

mock_send_message.reset_mock()
mock_send_message.side_effect = SMTPException
send_scheduled_mail_task()
self.assertEqual(mock_send_message.call_count, 1)
self.assertEqual(mock_send_message.call_args[0], (not_yet_sent,))
self.assertTrue(mock_log_smtp_exception.called)
66 changes: 66 additions & 0 deletions ietf/review/tests.py
@@ -1,9 +1,11 @@
# Copyright The IETF Trust 2019-2020, All Rights Reserved
# -*- coding: utf-8 -*-
import datetime
import mock
import debug # pyflakes:ignore

from pyquery import PyQuery

from ietf.group.factories import RoleFactory
from ietf.doc.factories import WgDraftFactory
from ietf.utils.mail import empty_outbox, get_payload_text, outbox
Expand All @@ -13,6 +15,7 @@
from .factories import ReviewAssignmentFactory, ReviewRequestFactory, ReviewerSettingsFactory
from .mailarch import hash_list_message_id
from .models import ReviewerSettings, ReviewSecretarySettings, ReviewTeamSettings, UnavailablePeriod
from .tasks import send_review_reminders_task
from .utils import (email_secretary_reminder, review_assignments_needing_secretary_reminder,
email_reviewer_reminder, review_assignments_needing_reviewer_reminder,
send_reminder_unconfirmed_assignments, send_review_reminder_overdue_assignment,
Expand Down Expand Up @@ -550,3 +553,66 @@ def test_review_add_comment(self):
# But can't have the comment we are goint to add.
self.assertContains(r, 'This is a test.')


class TaskTests(TestCase):
# hyaaa it's mockzilla
@mock.patch("ietf.review.tasks.date_today")
@mock.patch("ietf.review.tasks.review_assignments_needing_reviewer_reminder")
@mock.patch("ietf.review.tasks.email_reviewer_reminder")
@mock.patch("ietf.review.tasks.review_assignments_needing_secretary_reminder")
@mock.patch("ietf.review.tasks.email_secretary_reminder")
@mock.patch("ietf.review.tasks.send_unavailability_period_ending_reminder")
@mock.patch("ietf.review.tasks.send_reminder_all_open_reviews")
@mock.patch("ietf.review.tasks.send_review_reminder_overdue_assignment")
@mock.patch("ietf.review.tasks.send_reminder_unconfirmed_assignments")
def test_send_review_reminders_task(
self,
mock_send_reminder_unconfirmed_assignments,
mock_send_review_reminder_overdue_assignment,
mock_send_reminder_all_open_reviews,
mock_send_unavailability_period_ending_reminder,
mock_email_secretary_reminder,
mock_review_assignments_needing_secretary_reminder,
mock_email_reviewer_reminder,
mock_review_assignments_needing_reviewer_reminder,
mock_date_today,
):
"""Test that send_review_reminders calls functions correctly
Does not test individual methods, just that they are called as expected.
"""
mock_today = object()
assignment = ReviewAssignmentFactory()
secretary_role = RoleFactory(name_id="secr")

mock_date_today.return_value = mock_today
mock_review_assignments_needing_reviewer_reminder.return_value = [assignment]
mock_review_assignments_needing_secretary_reminder.return_value = [[assignment, secretary_role]]
mock_send_unavailability_period_ending_reminder.return_value = ["pretending I sent a period end reminder"]
mock_send_review_reminder_overdue_assignment.return_value = ["pretending I sent an overdue reminder"]
mock_send_reminder_all_open_reviews.return_value = ["pretending I sent an open review reminder"]
mock_send_reminder_unconfirmed_assignments.return_value = ["pretending I sent an unconfirmed reminder"]

send_review_reminders_task()

self.assertEqual(mock_review_assignments_needing_reviewer_reminder.call_count, 1)
self.assertEqual(mock_review_assignments_needing_reviewer_reminder.call_args[0], (mock_today,))
self.assertEqual(mock_email_reviewer_reminder.call_count, 1)
self.assertEqual(mock_email_reviewer_reminder.call_args[0], (assignment,))

self.assertEqual(mock_review_assignments_needing_secretary_reminder.call_count, 1)
self.assertEqual(mock_review_assignments_needing_secretary_reminder.call_args[0], (mock_today,))
self.assertEqual(mock_email_secretary_reminder.call_count, 1)
self.assertEqual(mock_email_secretary_reminder.call_args[0], (assignment, secretary_role))

self.assertEqual(mock_send_unavailability_period_ending_reminder.call_count, 1)
self.assertEqual(mock_send_unavailability_period_ending_reminder.call_args[0], (mock_today,))

self.assertEqual(mock_send_review_reminder_overdue_assignment.call_count, 1)
self.assertEqual(mock_send_review_reminder_overdue_assignment.call_args[0], (mock_today,))

self.assertEqual(mock_send_reminder_all_open_reviews.call_count, 1)
self.assertEqual(mock_send_reminder_all_open_reviews.call_args[0], (mock_today,))

self.assertEqual(mock_send_reminder_unconfirmed_assignments.call_count, 1)
self.assertEqual(mock_send_reminder_unconfirmed_assignments.call_args[0], (mock_today,))
32 changes: 30 additions & 2 deletions ietf/stats/tests.py
Expand Up @@ -30,7 +30,7 @@
from ietf.stats.models import MeetingRegistration, CountryAlias
from ietf.stats.factories import MeetingRegistrationFactory
from ietf.stats.tasks import fetch_meeting_attendance_task
from ietf.stats.utils import get_meeting_registration_data, FetchStats
from ietf.stats.utils import get_meeting_registration_data, FetchStats, fetch_attendance_from_meetings
from ietf.utils.timezone import date_today


Expand Down Expand Up @@ -302,6 +302,28 @@ def test_get_meeting_registration_data_duplicates(self, mock_get):
query = MeetingRegistration.objects.all()
self.assertEqual(query.count(), 2)

@patch("ietf.stats.utils.get_meeting_registration_data")
def test_fetch_attendance_from_meetings(self, mock_get_mtg_reg_data):
mock_meetings = [object(), object(), object()]
mock_get_mtg_reg_data.side_effect = (
(1, 2, 3),
(4, 5, 6),
(7, 8, 9),
)
stats = fetch_attendance_from_meetings(mock_meetings)
self.assertEqual(
[mock_get_mtg_reg_data.call_args_list[n][0][0] for n in range(3)],
mock_meetings,
)
self.assertEqual(
stats,
[
FetchStats(1, 2, 3),
FetchStats(4, 5, 6),
FetchStats(7, 8, 9),
]
)


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

fetch_meeting_attendance_task()

self.assertEqual(mock_fetch_attendance.call_count, 1)
self.assertCountEqual(mock_fetch_attendance.call_args[0][0], meetings[0:2])

# test handling of RuntimeError
mock_fetch_attendance.reset_mock()
mock_fetch_attendance.side_effect = RuntimeError
fetch_meeting_attendance_task()
self.assertTrue(mock_fetch_attendance.called)
# Good enough that we got here without raising an exception
52 changes: 47 additions & 5 deletions ietf/sync/tests.py
Expand Up @@ -8,6 +8,7 @@
import datetime
import mock
import quopri
import requests

from dataclasses import dataclass

Expand All @@ -18,7 +19,7 @@

import debug # pyflakes:ignore

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

RfcFactory(rfc_number=123)
Expand Down Expand Up @@ -381,6 +383,7 @@ def test_rfc_index(self):

rfc_doc = Document.objects.filter(rfc_number=1234, type_id="rfc").first()
self.assertIsNotNone(rfc_doc, "RFC document should have been created")
self.assertEqual(rfc_doc.authors(), draft_doc.authors())
rfc_events = rfc_doc.docevent_set.all()
self.assertEqual(len(rfc_events), 8)
expected_events = [
Expand Down Expand Up @@ -715,11 +718,14 @@ def json(self):
errata_response = MockResponse(
text="these are the errata", json_length=rfceditor.MIN_ERRATA_RESULTS
)

rfc = RfcFactory()

# Test with full_index = False
requests_get_mock.side_effect = (index_response, errata_response) # will step through these
parse_index_mock.return_value = MockIndexData(length=rfceditor.MIN_INDEX_RESULTS)
update_docs_mock.return_value = [] # not tested
update_docs_mock.return_value = (
(rfc.rfc_number, ("something changed",), rfc, False),
)

tasks.rfc_editor_index_update_task(full_index=False)

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

# Test again with full_index = True
requests_get_mock.reset_mock()
parse_index_mock.reset_mock()
update_docs_mock.reset_mock()
requests_get_mock.side_effect = (index_response, errata_response) # will step through these
parse_index_mock.return_value = MockIndexData(length=rfceditor.MIN_INDEX_RESULTS)
update_docs_mock.return_value = [] # not tested
tasks.rfc_editor_index_update_task(full_index=True)

# Check parse_index() call
Expand All @@ -762,3 +769,38 @@ def json(self):
update_docs_args, (parse_index_mock.return_value, errata_response.json())
)
self.assertIsNone(update_docs_kwargs["skip_older_than_date"])

# Test error handling
requests_get_mock.reset_mock()
parse_index_mock.reset_mock()
update_docs_mock.reset_mock()
requests_get_mock.side_effect = requests.Timeout # timeout on every get()
tasks.rfc_editor_index_update_task(full_index=False)
self.assertFalse(parse_index_mock.called)
self.assertFalse(update_docs_mock.called)

requests_get_mock.reset_mock()
parse_index_mock.reset_mock()
update_docs_mock.reset_mock()
requests_get_mock.side_effect = [index_response, requests.Timeout] # timeout second get()
tasks.rfc_editor_index_update_task(full_index=False)
self.assertFalse(update_docs_mock.called)

requests_get_mock.reset_mock()
parse_index_mock.reset_mock()
update_docs_mock.reset_mock()
requests_get_mock.side_effect = [index_response, errata_response]
# feed in an index that is too short
parse_index_mock.return_value = MockIndexData(length=rfceditor.MIN_INDEX_RESULTS - 1)
tasks.rfc_editor_index_update_task(full_index=False)
self.assertTrue(parse_index_mock.called)
self.assertFalse(update_docs_mock.called)

requests_get_mock.reset_mock()
parse_index_mock.reset_mock()
update_docs_mock.reset_mock()
requests_get_mock.side_effect = [index_response, errata_response]
errata_response.json_length = rfceditor.MIN_ERRATA_RESULTS - 1 # too short
parse_index_mock.return_value = MockIndexData(length=rfceditor.MIN_INDEX_RESULTS)
tasks.rfc_editor_index_update_task(full_index=False)
self.assertFalse(update_docs_mock.called)
2 changes: 1 addition & 1 deletion ietf/utils/management/commands/periodic_tasks.py
Expand Up @@ -56,7 +56,7 @@ def get_or_create_crontabs(self):
def create_default_tasks(self):
PeriodicTask.objects.get_or_create(
name="Send scheduled mail",
task="ietf.utils.tasks.send_scheduled_mail_task",
task="ietf.meeting.tasks.send_scheduled_mail_task",
defaults=dict(
enabled=False,
crontab=self.crontabs["every_15m"],
Expand Down

0 comments on commit 36c43c8

Please sign in to comment.