From fdc9457241f70dc5299d0d5d1937dd0462c780b6 Mon Sep 17 00:00:00 2001 From: Piotr Banaszkiewicz Date: Sun, 4 Aug 2024 23:51:24 +0200 Subject: [PATCH] [#2681] Tests for Instructor Badge Awarded (multiple actions) --- .../actions/instructor_badge_awarded.py | 4 +- ...nstructor_badge_awarded_cancel_receiver.py | 251 +++++++++++++++++ ...test_instructor_badge_awarded_receiver.py} | 0 .../test_instructor_badge_awarded_strategy.py | 228 +++++++++++++++ ...nstructor_badge_awarded_update_receiver.py | 264 ++++++++++++++++++ 5 files changed, 746 insertions(+), 1 deletion(-) create mode 100644 amy/emails/tests/actions/test_instructor_badge_awarded_cancel_receiver.py rename amy/emails/tests/actions/{test_instructor_badge_awarded.py => test_instructor_badge_awarded_receiver.py} (100%) create mode 100644 amy/emails/tests/actions/test_instructor_badge_awarded_strategy.py create mode 100644 amy/emails/tests/actions/test_instructor_badge_awarded_update_receiver.py diff --git a/amy/emails/actions/instructor_badge_awarded.py b/amy/emails/actions/instructor_badge_awarded.py index 734fd3fa5..7d522067a 100644 --- a/amy/emails/actions/instructor_badge_awarded.py +++ b/amy/emails/actions/instructor_badge_awarded.py @@ -32,7 +32,9 @@ logger = logging.getLogger("amy") -def instructor_badge_awarded_strategy(award: Award, person: Person) -> StrategyEnum: +def instructor_badge_awarded_strategy( + award: Award | None, person: Person +) -> StrategyEnum: logger.info(f"Running InstructorBadgeAwarded strategy for {award=}") award_pk = getattr(award, "pk", None) diff --git a/amy/emails/tests/actions/test_instructor_badge_awarded_cancel_receiver.py b/amy/emails/tests/actions/test_instructor_badge_awarded_cancel_receiver.py new file mode 100644 index 000000000..73d43d3d0 --- /dev/null +++ b/amy/emails/tests/actions/test_instructor_badge_awarded_cancel_receiver.py @@ -0,0 +1,251 @@ +from datetime import UTC, datetime +from unittest.mock import MagicMock, call, patch + +from django.test import RequestFactory, TestCase, override_settings +from django.urls import reverse + +from emails.actions.instructor_badge_awarded import ( + instructor_badge_awarded_cancel_receiver, + instructor_badge_awarded_strategy, + run_instructor_badge_awarded_strategy, +) +from emails.models import EmailTemplate, ScheduledEmail, ScheduledEmailStatus +from emails.signals import ( + INSTRUCTOR_BADGE_AWARDED_SIGNAL_NAME, + instructor_badge_awarded_cancel_signal, +) +from workshops.models import Award, Badge, Person +from workshops.tests.base import TestBase + + +class TestInstructorBadgeAwardedCancelReceiver(TestCase): + def setUp(self) -> None: + self.badge = Badge.objects.create(name="instructor") + self.person = Person.objects.create(email="test@example.org") + self.award = Award.objects.create(badge=self.badge, person=self.person) + + def setUpEmailTemplate(self) -> EmailTemplate: + return EmailTemplate.objects.create( + name="Test Email Template", + signal=INSTRUCTOR_BADGE_AWARDED_SIGNAL_NAME, + from_header="workshops@carpentries.org", + cc_header=["team@carpentries.org"], + bcc_header=[], + subject="Greetings {{ name }}", + body="Hello, {{ name }}! Nice to meet **you**.", + ) + + @patch("emails.actions.base_action.logger") + def test_disabled_when_no_feature_flag(self, mock_logger) -> None: + # Arrange + request = RequestFactory().get("/") + with self.settings(FLAGS={"EMAIL_MODULE": [("boolean", False)]}): + # Act + instructor_badge_awarded_cancel_receiver(None, request=request) + # Assert + mock_logger.debug.assert_called_once_with( + "EMAIL_MODULE feature flag not set, skipping " + "instructor_badge_awarded_cancel" + ) + + def test_receiver_connected_to_signal(self) -> None: + # Arrange + original_receivers = instructor_badge_awarded_cancel_signal.receivers[:] + + # Act + # attempt to connect the receiver + instructor_badge_awarded_cancel_signal.connect( + instructor_badge_awarded_cancel_receiver + ) + new_receivers = instructor_badge_awarded_cancel_signal.receivers[:] + + # Assert + # the same receiver list means this receiver has already been connected + self.assertEqual(original_receivers, new_receivers) + + @override_settings(FLAGS={"EMAIL_MODULE": [("boolean", True)]}) + def test_action_triggered(self) -> None: + # Arrange + request = RequestFactory().get("/") + + template = self.setUpEmailTemplate() + scheduled_email = ScheduledEmail.objects.create( + template=template, + scheduled_at=datetime.now(UTC), + to_header=[], + cc_header=[], + bcc_header=[], + state=ScheduledEmailStatus.SCHEDULED, + generic_relation=self.person, + ) + + # Act + with patch( + "emails.actions.base_action.messages_action_cancelled" + ) as mock_messages_action_cancelled: + instructor_badge_awarded_cancel_signal.send( + sender=self.award, + request=request, + person_id=self.person.pk, + award_id=self.award.pk, + ) + + # Assert + scheduled_email = ScheduledEmail.objects.get(template=template) + mock_messages_action_cancelled.assert_called_once_with( + request, + INSTRUCTOR_BADGE_AWARDED_SIGNAL_NAME, + scheduled_email, + ) + + @override_settings(FLAGS={"EMAIL_MODULE": [("boolean", True)]}) + @patch("emails.actions.base_action.messages_action_cancelled") + def test_email_cancelled( + self, + mock_messages_action_cancelled: MagicMock, + ) -> None: + # Arrange + request = RequestFactory().get("/") + template = self.setUpEmailTemplate() + scheduled_email = ScheduledEmail.objects.create( + template=template, + scheduled_at=datetime.now(UTC), + to_header=[], + cc_header=[], + bcc_header=[], + state=ScheduledEmailStatus.SCHEDULED, + generic_relation=self.person, + ) + + # Act + with patch( + "emails.actions.base_action.EmailController.cancel_email" + ) as mock_cancel_email: + instructor_badge_awarded_cancel_signal.send( + sender=self.award, + request=request, + person_id=self.person.pk, + award_id=self.award.pk, + ) + + # Assert + mock_cancel_email.assert_called_once_with( + scheduled_email=scheduled_email, + author=None, + ) + + @override_settings(FLAGS={"EMAIL_MODULE": [("boolean", True)]}) + @patch("emails.actions.base_action.messages_action_cancelled") + def test_multiple_emails_cancelled( + self, + mock_messages_action_cancelled: MagicMock, + ) -> None: + # Arrange + request = RequestFactory().get("/") + template = self.setUpEmailTemplate() + scheduled_email1 = ScheduledEmail.objects.create( + template=template, + scheduled_at=datetime.now(UTC), + to_header=[], + cc_header=[], + bcc_header=[], + state=ScheduledEmailStatus.SCHEDULED, + generic_relation=self.person, + ) + scheduled_email2 = ScheduledEmail.objects.create( + template=template, + scheduled_at=datetime.now(UTC), + to_header=[], + cc_header=[], + bcc_header=[], + state=ScheduledEmailStatus.SCHEDULED, + generic_relation=self.person, + ) + + # Act + with patch( + "emails.actions.base_action.EmailController.cancel_email" + ) as mock_cancel_email: + instructor_badge_awarded_cancel_signal.send( + sender=self.person, + request=request, + award_id=self.award.pk, + person_id=self.person.pk, + ) + + # Assert + mock_cancel_email.assert_has_calls( + [ + call( + scheduled_email=scheduled_email1, + author=None, + ), + call( + scheduled_email=scheduled_email2, + author=None, + ), + ] + ) + + +class TestInstructorBadgeAwardedCancelIntegration(TestBase): + @override_settings(FLAGS={"EMAIL_MODULE": [("boolean", True)]}) + def test_integration(self) -> None: + # Arrange + self._setUpRoles() + self._setUpTags() + self._setUpUsersAndLogin() + badge = Badge.objects.get(name="instructor") + person = Person.objects.create( + personal="Kelsi", + middle="", + family="Purdy", + username="purdy_kelsi", + email="purdy.kelsi@example.com", + secondary_email="notused@amy.org", + gender="F", + airport=self.airport_0_0, + github="purdy_kelsi", + twitter="purdy_kelsi", + url="http://kelsipurdy.com/", + affiliation="University of Arizona", + occupation="TA at Biology Department", + orcid="0000-0000-0000", + is_active=True, + ) + award = Award.objects.create(badge=badge, person=person) + + template = EmailTemplate.objects.create( + name="Test Email Template", + signal=INSTRUCTOR_BADGE_AWARDED_SIGNAL_NAME, + from_header="workshops@carpentries.org", + cc_header=["team@carpentries.org"], + bcc_header=[], + subject="Greetings", + body="Hello! Nice to meet **you**.", + ) + + request = RequestFactory().get("/") + + with patch( + "emails.actions.base_action.messages_action_scheduled" + ) as mock_action_scheduled: + run_instructor_badge_awarded_strategy( + instructor_badge_awarded_strategy(award, person), + request, + person, + award_id=award.pk, + person_id=person.pk, + ) + scheduled_email = ScheduledEmail.objects.get(template=template) + + url = reverse("award_delete", args=[award.pk]) + + # Act + rv = self.client.post(url) + + # Arrange + mock_action_scheduled.assert_called_once() + self.assertEqual(rv.status_code, 302) + scheduled_email.refresh_from_db() + self.assertEqual(scheduled_email.state, ScheduledEmailStatus.CANCELLED) diff --git a/amy/emails/tests/actions/test_instructor_badge_awarded.py b/amy/emails/tests/actions/test_instructor_badge_awarded_receiver.py similarity index 100% rename from amy/emails/tests/actions/test_instructor_badge_awarded.py rename to amy/emails/tests/actions/test_instructor_badge_awarded_receiver.py diff --git a/amy/emails/tests/actions/test_instructor_badge_awarded_strategy.py b/amy/emails/tests/actions/test_instructor_badge_awarded_strategy.py new file mode 100644 index 000000000..f2e84cd85 --- /dev/null +++ b/amy/emails/tests/actions/test_instructor_badge_awarded_strategy.py @@ -0,0 +1,228 @@ +from datetime import UTC, datetime +from unittest.mock import MagicMock, patch + +from django.test import RequestFactory, TestCase + +from emails.actions.exceptions import EmailStrategyException +from emails.actions.instructor_badge_awarded import ( + instructor_badge_awarded_strategy, + run_instructor_badge_awarded_strategy, +) +from emails.models import EmailTemplate, ScheduledEmail, ScheduledEmailStatus +from emails.signals import INSTRUCTOR_BADGE_AWARDED_SIGNAL_NAME +from emails.types import StrategyEnum +from workshops.models import Award, Badge, Person + + +class TestInstructorBadgeAwardedStrategy(TestCase): + def setUp(self) -> None: + self.badge = Badge.objects.create(name="instructor") + self.person = Person.objects.create(email="test@example.org") + + def test_strategy_create(self) -> None: + # Arrange + award = Award.objects.create(badge=self.badge, person=self.person) + + # Act + result = instructor_badge_awarded_strategy(award, award.person) + + # Assert + self.assertEqual(result, StrategyEnum.CREATE) + + def test_strategy_update(self) -> None: + # Arrange + award = Award.objects.create(badge=self.badge, person=self.person) + template = EmailTemplate.objects.create( + name="Test Email Template", + signal=INSTRUCTOR_BADGE_AWARDED_SIGNAL_NAME, + from_header="workshops@carpentries.org", + cc_header=["team@carpentries.org"], + bcc_header=[], + subject="Greetings {{ name }}", + body="Hello, {{ name }}! Nice to meet **you**.", + ) + ScheduledEmail.objects.create( + template=template, + scheduled_at=datetime.now(UTC), + to_header=[], + cc_header=[], + bcc_header=[], + state=ScheduledEmailStatus.SCHEDULED, + generic_relation=award.person, + ) + + # Act + result = instructor_badge_awarded_strategy(award, award.person) + + # Assert + self.assertEqual(result, StrategyEnum.UPDATE) + + def test_strategy_cancel(self) -> None: + # Arrange + # Award intentionally not created + template = EmailTemplate.objects.create( + name="Test Email Template", + signal=INSTRUCTOR_BADGE_AWARDED_SIGNAL_NAME, + from_header="workshops@carpentries.org", + cc_header=["team@carpentries.org"], + bcc_header=[], + subject="Greetings {{ name }}", + body="Hello, {{ name }}! Nice to meet **you**.", + ) + ScheduledEmail.objects.create( + template=template, + scheduled_at=datetime.now(UTC), + to_header=[], + cc_header=[], + bcc_header=[], + state=ScheduledEmailStatus.SCHEDULED, + generic_relation=self.person, + ) + + # Act + result = instructor_badge_awarded_strategy(award=None, person=self.person) + + # Assert + self.assertEqual(result, StrategyEnum.CANCEL) + + +class TestRunInstructorBadgeAwardedStrategy(TestCase): + def setUp(self) -> None: + self.badge = Badge.objects.create(name="test") + self.person = Person.objects.create(username="test") + self.award = Award.objects.create(person=self.person, badge=self.badge) + + @patch("emails.actions.instructor_badge_awarded.instructor_badge_awarded_signal") + def test_strategy_calls_create_signal( + self, + mock_instructor_badge_awarded_signal, + ) -> None: + # Arrange + strategy = StrategyEnum.CREATE + request = RequestFactory().get("/") + + # Act + run_instructor_badge_awarded_strategy( + strategy, + request, + self.person, + award_id=self.award.pk, + person_id=self.person.pk, + ) + + # Assert + mock_instructor_badge_awarded_signal.send.assert_called_once_with( + sender=self.person, + request=request, + award_id=self.award.pk, + person_id=self.person.pk, + ) + + @patch( + "emails.actions.instructor_badge_awarded.instructor_badge_awarded_update_signal" + ) + def test_strategy_calls_update_signal( + self, + mock_instructor_badge_awarded_update_signal, + ) -> None: + # Arrange + strategy = StrategyEnum.UPDATE + request = RequestFactory().get("/") + + # Act + run_instructor_badge_awarded_strategy( + strategy, + request, + self.person, + award_id=self.award.pk, + person_id=self.person.pk, + ) + + # Assert + mock_instructor_badge_awarded_update_signal.send.assert_called_once_with( + sender=self.person, + request=request, + award_id=self.award.pk, + person_id=self.person.pk, + ) + + @patch( + "emails.actions.instructor_badge_awarded.instructor_badge_awarded_cancel_signal" + ) + def test_strategy_calls_cancel_signal( + self, + mock_instructor_badge_awarded_cancel_signal, + ) -> None: + # Arrange + strategy = StrategyEnum.CANCEL + request = RequestFactory().get("/") + + # Act + run_instructor_badge_awarded_strategy( + strategy, + request, + self.person, + award_id=self.award.pk, + person_id=self.person.pk, + ) + + # Assert + mock_instructor_badge_awarded_cancel_signal.send.assert_called_once_with( + sender=self.person, + request=request, + award_id=self.award.pk, + person_id=self.person.pk, + ) + + @patch("emails.actions.base_strategy.logger") + @patch("emails.actions.instructor_badge_awarded.instructor_badge_awarded_signal") + @patch( + "emails.actions.instructor_badge_awarded.instructor_badge_awarded_update_signal" + ) + @patch( + "emails.actions.instructor_badge_awarded.instructor_badge_awarded_cancel_signal" + ) + def test_invalid_strategy_no_signal_called( + self, + mock_instructor_badge_awarded_cancel_signal, + mock_instructor_badge_awarded_update_signal, + mock_instructor_badge_awarded_signal, + mock_logger, + ) -> None: + # Arrange + strategy = StrategyEnum.NOOP + request = RequestFactory().get("/") + + # Act + run_instructor_badge_awarded_strategy( + strategy, + request, + self.person, + award_id=self.award.pk, + person_id=self.person.pk, + ) + + # Assert + mock_instructor_badge_awarded_signal.send.assert_not_called() + mock_instructor_badge_awarded_update_signal.send.assert_not_called() + mock_instructor_badge_awarded_cancel_signal.send.assert_not_called() + mock_logger.debug.assert_called_once_with( + f"Strategy {strategy} for {self.person} is a no-op" + ) + + def test_invalid_strategy(self) -> None: + # Arrange + strategy = MagicMock() + request = RequestFactory().get("/") + + # Act & Assert + with self.assertRaises( + EmailStrategyException, msg=f"Unknown strategy {strategy}" + ): + run_instructor_badge_awarded_strategy( + strategy, + request, + self.person, + award_id=self.award.pk, + person_id=self.person.pk, + ) diff --git a/amy/emails/tests/actions/test_instructor_badge_awarded_update_receiver.py b/amy/emails/tests/actions/test_instructor_badge_awarded_update_receiver.py new file mode 100644 index 000000000..11f9fb572 --- /dev/null +++ b/amy/emails/tests/actions/test_instructor_badge_awarded_update_receiver.py @@ -0,0 +1,264 @@ +from datetime import UTC, datetime +from unittest.mock import MagicMock, patch + +from django.test import RequestFactory, TestCase, override_settings + +from emails.actions.instructor_badge_awarded import ( + instructor_badge_awarded_update_receiver, +) +from emails.models import EmailTemplate, ScheduledEmail, ScheduledEmailStatus +from emails.schemas import ContextModel, ToHeaderModel +from emails.signals import ( + INSTRUCTOR_BADGE_AWARDED_SIGNAL_NAME, + instructor_badge_awarded_update_signal, +) +from emails.utils import api_model_url +from workshops.models import Award, Badge, Person +from workshops.tests.base import TestBase + + +class TestInstructorBadgeAwardedUpdateReceiver(TestCase): + def setUp(self) -> None: + self.badge = Badge.objects.create(name="instructor") + self.person = Person.objects.create(email="test@example.org") + self.award = Award.objects.create(badge=self.badge, person=self.person) + + def setUpEmailTemplate(self) -> EmailTemplate: + return EmailTemplate.objects.create( + name="Test Email Template", + signal=INSTRUCTOR_BADGE_AWARDED_SIGNAL_NAME, + from_header="workshops@carpentries.org", + cc_header=["team@carpentries.org"], + bcc_header=[], + subject="Greetings {{ name }}", + body="Hello, {{ name }}! Nice to meet **you**.", + ) + + @patch("emails.actions.base_action.logger") + def test_disabled_when_no_feature_flag(self, mock_logger) -> None: + # Arrange + request = RequestFactory().get("/") + with self.settings(FLAGS={"EMAIL_MODULE": [("boolean", False)]}): + # Act + instructor_badge_awarded_update_receiver(None, request=request) + # Assert + mock_logger.debug.assert_called_once_with( + "EMAIL_MODULE feature flag not set, skipping " + "instructor_badge_awarded_update" + ) + + def test_receiver_connected_to_signal(self) -> None: + # Arrange + original_receivers = instructor_badge_awarded_update_signal.receivers[:] + + # Act + # attempt to connect the receiver + instructor_badge_awarded_update_signal.connect( + instructor_badge_awarded_update_receiver + ) + new_receivers = instructor_badge_awarded_update_signal.receivers[:] + + # Assert + # the same receiver list means this receiver has already been connected + self.assertEqual(original_receivers, new_receivers) + + @override_settings(FLAGS={"EMAIL_MODULE": [("boolean", True)]}) + def test_action_triggered(self) -> None: + # Arrange + request = RequestFactory().get("/") + + template = self.setUpEmailTemplate() + ScheduledEmail.objects.create( + template=template, + scheduled_at=datetime.now(UTC), + to_header=[], + cc_header=[], + bcc_header=[], + state=ScheduledEmailStatus.SCHEDULED, + generic_relation=self.person, + ) + + # Act + with patch( + "emails.actions.base_action.messages_action_updated" + ) as mock_messages_action_updated: + instructor_badge_awarded_update_signal.send( + sender=self.award, + request=request, + person_id=self.person.pk, + award_id=self.award.pk, + ) + + # Assert + scheduled_email = ScheduledEmail.objects.get(template=template) + mock_messages_action_updated.assert_called_once_with( + request, + INSTRUCTOR_BADGE_AWARDED_SIGNAL_NAME, + scheduled_email, + ) + + @override_settings(FLAGS={"EMAIL_MODULE": [("boolean", True)]}) + @patch("emails.actions.base_action.messages_action_updated") + @patch("emails.actions.instructor_badge_awarded.immediate_action") + def test_email_updated( + self, + mock_immediate_action: MagicMock, + mock_messages_action_updated: MagicMock, + ) -> None: + # Arrange + request = RequestFactory().get("/") + template = self.setUpEmailTemplate() + scheduled_email = ScheduledEmail.objects.create( + template=template, + scheduled_at=datetime.now(UTC), + to_header=[], + cc_header=[], + bcc_header=[], + state=ScheduledEmailStatus.SCHEDULED, + generic_relation=self.person, + ) + scheduled_at = datetime(2023, 8, 5, 12, 0, tzinfo=UTC) + mock_immediate_action.return_value = scheduled_at + + # Act + with patch( + "emails.actions.base_action.EmailController.update_scheduled_email" + ) as mock_update_scheduled_email: + instructor_badge_awarded_update_signal.send( + sender=self.award, + request=request, + person_id=self.person.pk, + award_id=self.award.pk, + ) + + # Assert + mock_update_scheduled_email.assert_called_once_with( + scheduled_email=scheduled_email, + context_json=ContextModel( + { + "person": api_model_url("person", self.person.pk), + "award": api_model_url("award", self.award.pk), + } + ), + scheduled_at=scheduled_at, + to_header=[self.person.email], + to_header_context_json=ToHeaderModel( + [ + { + "api_uri": api_model_url("person", self.person.pk), + "property": "email", + }, # type: ignore + ] + ), + generic_relation_obj=self.person, + author=None, + ) + + @override_settings(FLAGS={"EMAIL_MODULE": [("boolean", True)]}) + @patch("emails.actions.base_action.logger") + @patch("emails.actions.base_action.EmailController") + def test_previously_scheduled_email_not_existing( + self, mock_email_controller: MagicMock, mock_logger: MagicMock + ) -> None: + # Arrange + request = RequestFactory().get("/") + signal = INSTRUCTOR_BADGE_AWARDED_SIGNAL_NAME + award = self.award + person = award.person + + # Act + instructor_badge_awarded_update_signal.send( + sender=award, + request=request, + person_id=award.person.pk, + award_id=award.pk, + ) + + # Assert + mock_email_controller.update_scheduled_email.assert_not_called() + mock_logger.warning.assert_called_once_with( + f"Scheduled email for signal {signal} and generic_relation_obj={person!r} " + "does not exist." + ) + + @override_settings(FLAGS={"EMAIL_MODULE": [("boolean", True)]}) + @patch("emails.actions.base_action.logger") + @patch("emails.actions.base_action.EmailController") + def test_multiple_previously_scheduled_emails( + self, mock_email_controller: MagicMock, mock_logger: MagicMock + ) -> None: + # Arrange + request = RequestFactory().get("/") + signal = INSTRUCTOR_BADGE_AWARDED_SIGNAL_NAME + template = self.setUpEmailTemplate() + ScheduledEmail.objects.create( + template=template, + scheduled_at=datetime.now(UTC), + to_header=[], + cc_header=[], + bcc_header=[], + state=ScheduledEmailStatus.SCHEDULED, + generic_relation=self.person, + ) + ScheduledEmail.objects.create( + template=template, + scheduled_at=datetime.now(UTC), + to_header=[], + cc_header=[], + bcc_header=[], + state=ScheduledEmailStatus.SCHEDULED, + generic_relation=self.person, + ) + award = self.award + + # Act + instructor_badge_awarded_update_signal.send( + sender=award, + request=request, + person_id=award.person.pk, + award_id=award.pk, + ) + + # Assert + mock_email_controller.update_scheduled_email.assert_not_called() + mock_logger.warning.assert_called_once_with( + f"Too many scheduled emails for signal {signal} and " + f"generic_relation_obj={award.person!r}. Can't update them." + ) + + @override_settings(FLAGS={"EMAIL_MODULE": [("boolean", True)]}) + @patch("emails.actions.base_action.messages_missing_recipients") + def test_missing_recipients( + self, mock_messages_missing_recipients: MagicMock + ) -> None: + # Arrange + request = RequestFactory().get("/") + template = self.setUpEmailTemplate() + ScheduledEmail.objects.create( + template=template, + scheduled_at=datetime.now(UTC), + to_header=[], + cc_header=[], + bcc_header=[], + state=ScheduledEmailStatus.SCHEDULED, + generic_relation=self.person, + ) + signal = INSTRUCTOR_BADGE_AWARDED_SIGNAL_NAME + self.person.email = "" + self.person.save() + + # Act + instructor_badge_awarded_update_signal.send( + sender=self.award, + request=request, + person_id=self.award.person.pk, + award_id=self.award.pk, + ) + + # Assert + mock_messages_missing_recipients.assert_called_once_with(request, signal) + + +class TestInstructorBadgeAwardedUpdateIntegration(TestBase): + # Currently not possible to edit awards. + pass