Skip to content

#3530, #3531: Email notifications [MEOWARD] #3671

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ A domain manager was removed from {{ domain.name }}.

REMOVED BY: {{ removed_by.email }}
REMOVED ON: {{ date }}
MANAGER REMOVED: {{ manager_removed.email }}
MANAGER REMOVED: {{ manager_removed_email }}

----------------------------------------------------------------

Expand Down
171 changes: 160 additions & 11 deletions src/registrar/tests/test_email_invitations.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
_send_portfolio_admin_addition_emails_to_portfolio_admins,
_send_portfolio_admin_removal_emails_to_portfolio_admins,
send_domain_invitation_email,
send_emails_to_domain_managers,
_send_domain_invitation_update_emails_to_domain_managers,
send_domain_manager_removal_emails_to_domain_managers,
send_portfolio_admin_addition_emails,
send_portfolio_admin_removal_emails,
send_portfolio_invitation_email,
Expand All @@ -33,7 +34,7 @@ class DomainInvitationEmail(unittest.TestCase):
@patch("registrar.utility.email_invitations.UserDomainRole.objects.filter")
@patch("registrar.utility.email_invitations._validate_invitation")
@patch("registrar.utility.email_invitations._get_requestor_email")
@patch("registrar.utility.email_invitations.send_invitation_email")
@patch("registrar.utility.email_invitations._send_domain_invitation_email")
@patch("registrar.utility.email_invitations._normalize_domains")
def test_send_domain_invitation_email(
self,
Expand Down Expand Up @@ -98,7 +99,7 @@ def test_send_domain_invitation_email(
@patch("registrar.utility.email_invitations.UserDomainRole.objects.filter")
@patch("registrar.utility.email_invitations._validate_invitation")
@patch("registrar.utility.email_invitations._get_requestor_email")
@patch("registrar.utility.email_invitations.send_invitation_email")
@patch("registrar.utility.email_invitations._send_domain_invitation_email")
@patch("registrar.utility.email_invitations._normalize_domains")
def test_send_domain_invitation_email_multiple_domains(
self,
Expand Down Expand Up @@ -234,7 +235,7 @@ def test_send_domain_invitation_email_raises_get_requestor_email_exception(self,
@less_console_noise_decorator
@patch("registrar.utility.email_invitations._validate_invitation")
@patch("registrar.utility.email_invitations._get_requestor_email")
@patch("registrar.utility.email_invitations.send_invitation_email")
@patch("registrar.utility.email_invitations._send_domain_invitation_email")
@patch("registrar.utility.email_invitations._normalize_domains")
def test_send_domain_invitation_email_raises_sending_email_exception(
self,
Expand Down Expand Up @@ -281,10 +282,10 @@ def test_send_domain_invitation_email_raises_sending_email_exception(
self.assertEqual(str(context.exception), "Error sending email")

@less_console_noise_decorator
@patch("registrar.utility.email_invitations.send_emails_to_domain_managers")
@patch("registrar.utility.email_invitations._send_domain_invitation_update_emails_to_domain_managers")
@patch("registrar.utility.email_invitations._validate_invitation")
@patch("registrar.utility.email_invitations._get_requestor_email")
@patch("registrar.utility.email_invitations.send_invitation_email")
@patch("registrar.utility.email_invitations._send_domain_invitation_email")
@patch("registrar.utility.email_invitations._normalize_domains")
def test_send_domain_invitation_email_manager_emails_send_mail_exception(
self,
Expand All @@ -295,7 +296,7 @@ def test_send_domain_invitation_email_manager_emails_send_mail_exception(
mock_send_domain_manager_emails,
):
"""Test sending domain invitation email for one domain and assert exception
when send_emails_to_domain_managers fails.
when _send_domain_invitation_update_emails_to_domain_managers fails.
"""
# Setup
mock_domain = MagicMock(name="domain1")
Expand Down Expand Up @@ -354,7 +355,7 @@ def test_send_emails_to_domain_managers_all_emails_sent_successfully(self, mock_
mock_send_templated_email.return_value = None # No exception means success

# Call function
result = send_emails_to_domain_managers(mock_email, mock_requestor_email, mock_domain)
result = _send_domain_invitation_update_emails_to_domain_managers(mock_email, mock_requestor_email, mock_domain)

# Assertions
self.assertTrue(result) # All emails should be successfully sent
Expand Down Expand Up @@ -394,7 +395,7 @@ def test_send_emails_to_domain_managers_email_send_fails(self, mock_filter, mock
mock_send_templated_email.side_effect = EmailSendingError("Email sending failed")

# Call function
result = send_emails_to_domain_managers(mock_email, mock_requestor_email, mock_domain)
result = _send_domain_invitation_update_emails_to_domain_managers(mock_email, mock_requestor_email, mock_domain)

# Assertions
self.assertFalse(result) # The result should be False as email sending failed
Expand Down Expand Up @@ -426,7 +427,7 @@ def test_send_emails_to_domain_managers_no_domain_managers(self, mock_filter, mo
mock_filter.return_value = []

# Call function
result = send_emails_to_domain_managers(mock_email, mock_requestor_email, mock_domain)
result = _send_domain_invitation_update_emails_to_domain_managers(mock_email, mock_requestor_email, mock_domain)

# Assertions
self.assertTrue(result) # No emails to send, so it should return True
Expand Down Expand Up @@ -457,7 +458,7 @@ def test_send_emails_to_domain_managers_some_emails_fail(self, mock_filter, mock
mock_send_templated_email.side_effect = [None, EmailSendingError("Failed to send email")]

# Call function
result = send_emails_to_domain_managers(mock_email, mock_requestor_email, mock_domain)
result = _send_domain_invitation_update_emails_to_domain_managers(mock_email, mock_requestor_email, mock_domain)

# Assertions
self.assertFalse(result) # One email failed, so result should be False
Expand Down Expand Up @@ -1112,3 +1113,151 @@ def test_requestor_email_retrieval_failure(self, mock_logger, mock_get_requestor

# Assertions
mock_logger.warning.assert_not_called() # Function should fail before logging email failure


class SendDomainManagerRemovalEmailsToManagersTests(unittest.TestCase):
"""Unit tests for send_domain_manager_removal_emails_to_domain_managers function."""

def setUp(self):
"""Set up test data."""
self.email = "[email protected]"
self.requestor_email = "[email protected]"
self.domain = MagicMock(spec=Domain)
self.domain.name = "Test Domain"

# Mock domain manager users
self.manager_user1 = MagicMock(spec=User)
self.manager_user1.email = "[email protected]"

self.manager_user2 = MagicMock(spec=User)
self.manager_user2.email = "[email protected]"

self.domain_manager1 = MagicMock(spec=UserDomainRole)
self.domain_manager1.user = self.manager_user1

self.domain_manager2 = MagicMock(spec=UserDomainRole)
self.domain_manager2.user = self.manager_user2

@less_console_noise_decorator
@patch("registrar.utility.email_invitations.send_templated_email")
@patch("registrar.utility.email_invitations.UserDomainRole.objects.filter")
def test_send_email_success(self, mock_filter, mock_send_templated_email):
"""Test successful sending of domain manager removal emails."""
mock_filter.return_value.exclude.return_value = [self.domain_manager1]
mock_send_templated_email.return_value = None # No exception means success

result = send_domain_manager_removal_emails_to_domain_managers(
removed_by_user=self.manager_user1,
manager_removed=self.manager_user2,
manager_removed_email=self.manager_user2.email,
domain=self.domain,
)

mock_filter.assert_called_once_with(domain=self.domain)
mock_send_templated_email.assert_any_call(
"emails/domain_manager_deleted_notification.txt",
"emails/domain_manager_deleted_notification_subject.txt",
to_address=self.manager_user1.email,
context={
"domain": self.domain,
"removed_by": self.manager_user1,
"manager_removed_email": self.manager_user2.email,
"date": date.today(),
},
)
self.assertTrue(result)

@less_console_noise_decorator
@patch("registrar.utility.email_invitations.send_templated_email")
@patch("registrar.utility.email_invitations.UserDomainRole.objects.filter")
def test_send_email_success_when_no_user(self, mock_filter, mock_send_templated_email):
"""Test successful sending of domain manager removal emails."""
mock_filter.return_value = [self.domain_manager1, self.domain_manager2]
mock_send_templated_email.return_value = None # No exception means success

result = send_domain_manager_removal_emails_to_domain_managers(
removed_by_user=self.manager_user1,
manager_removed=None,
manager_removed_email=self.manager_user2.email,
domain=self.domain,
)

mock_filter.assert_called_once_with(domain=self.domain)
mock_send_templated_email.assert_any_call(
"emails/domain_manager_deleted_notification.txt",
"emails/domain_manager_deleted_notification_subject.txt",
to_address=self.manager_user1.email,
context={
"domain": self.domain,
"removed_by": self.manager_user1,
"manager_removed_email": self.manager_user2.email,
"date": date.today(),
},
)
mock_send_templated_email.assert_any_call(
"emails/domain_manager_deleted_notification.txt",
"emails/domain_manager_deleted_notification_subject.txt",
to_address=self.manager_user2.email,
context={
"domain": self.domain,
"removed_by": self.manager_user1,
"manager_removed_email": self.manager_user2.email,
"date": date.today(),
},
)
self.assertTrue(result)

@less_console_noise_decorator
@patch("registrar.utility.email_invitations.send_templated_email", side_effect=EmailSendingError)
@patch("registrar.utility.email_invitations.UserDomainRole.objects.filter")
def test_send_email_failure(self, mock_filter, mock_send_templated_email):
"""Test handling of failure in sending admin removal emails."""
mock_filter.return_value.exclude.return_value = [self.domain_manager1, self.domain_manager2]

result = send_domain_manager_removal_emails_to_domain_managers(
removed_by_user=self.manager_user1,
manager_removed=self.manager_user2,
manager_removed_email=self.manager_user2.email,
domain=self.domain,
)

self.assertFalse(result)
mock_filter.assert_called_once_with(domain=self.domain)
mock_send_templated_email.assert_any_call(
"emails/domain_manager_deleted_notification.txt",
"emails/domain_manager_deleted_notification_subject.txt",
to_address=self.manager_user1.email,
context={
"domain": self.domain,
"removed_by": self.manager_user1,
"manager_removed_email": self.manager_user2.email,
"date": date.today(),
},
)
mock_send_templated_email.assert_any_call(
"emails/domain_manager_deleted_notification.txt",
"emails/domain_manager_deleted_notification_subject.txt",
to_address=self.manager_user2.email,
context={
"domain": self.domain,
"removed_by": self.manager_user1,
"manager_removed_email": self.manager_user2.email,
"date": date.today(),
},
)

@less_console_noise_decorator
@patch("registrar.utility.email_invitations.UserDomainRole.objects.filter")
def test_no_managers_to_notify(self, mock_filter):
"""Test case where there are no domain managers to notify."""
mock_filter.return_value.exclude.return_value = [] # No managers

result = send_domain_manager_removal_emails_to_domain_managers(
removed_by_user=self.manager_user1,
manager_removed=self.manager_user2,
manager_removed_email=self.manager_user2.email,
domain=self.domain,
)

self.assertTrue(result) # No emails sent, but also no failures
mock_filter.assert_called_once_with(domain=self.domain)
14 changes: 7 additions & 7 deletions src/registrar/tests/test_views_domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -1084,8 +1084,8 @@ def test_domain_user_add_form_sends_portfolio_invitation_raises_email_sending_er

@boto3_mocking.patching
@less_console_noise_decorator
@patch("registrar.views.domain.send_templated_email")
def test_domain_remove_manager(self, mock_send_templated_email):
@patch("registrar.views.domain.send_domain_manager_removal_emails_to_domain_managers")
def test_domain_remove_manager(self, mock_send_email):
"""Removing a domain manager sends notification email to other domain managers."""
self.manager, _ = User.objects.get_or_create(email="[email protected]", first_name="Hello", last_name="World")
self.manager_domain_permission, _ = UserDomainRole.objects.get_or_create(user=self.manager, domain=self.domain)
Expand All @@ -1094,11 +1094,11 @@ def test_domain_remove_manager(self, mock_send_templated_email):
)

# Verify that the notification emails were sent to domain manager
mock_send_templated_email.assert_called_once_with(
"emails/domain_manager_deleted_notification.txt",
"emails/domain_manager_deleted_notification_subject.txt",
to_address="[email protected]",
context=ANY,
mock_send_email.assert_called_once_with(
removed_by_user=self.user,
manager_removed=self.manager,
manager_removed_email=self.manager.email,
domain=self.domain,
)

@less_console_noise_decorator
Expand Down
Loading