Merge branch 'main' into za/1484-domain-manager-delete

This commit is contained in:
zandercymatics 2024-01-04 09:12:09 -07:00
commit 25303c92f1
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
11 changed files with 469 additions and 294 deletions

View file

@ -519,7 +519,7 @@ LOGIN_REQUIRED_IGNORE_PATHS = [
]
# where to go after logging out
LOGOUT_REDIRECT_URL = "home"
LOGOUT_REDIRECT_URL = "https://get.gov/"
# disable dynamic client registration,
# only the OP inside OIDC_PROVIDERS will be available

View file

@ -218,5 +218,8 @@ class DomainFixture(DomainApplicationFixture):
creator=user, status=DomainApplication.ApplicationStatus.IN_REVIEW
).last()
logger.debug(f"Approving {application} for {user}")
application.approve()
# We don't want fixtures sending out real emails to
# fake email addresses, so we just skip that and log it instead
application.approve(send_email=False)
application.save()

View file

@ -570,17 +570,25 @@ class DomainApplication(TimeStampedModel):
return not self.approved_domain.is_active()
return True
def _send_status_update_email(self, new_status, email_template, email_template_subject):
"""Send a atatus update email to the submitter.
def _send_status_update_email(self, new_status, email_template, email_template_subject, send_email=True):
"""Send a status update email to the submitter.
The email goes to the email address that the submitter gave as their
contact information. If there is not submitter information, then do
nothing.
send_email: bool -> Used to bypass the send_templated_email function, in the event
we just want to log that an email would have been sent, rather than actually sending one.
"""
if self.submitter is None or self.submitter.email is None:
logger.warning(f"Cannot send {new_status} email, no submitter email address.")
return
return None
if not send_email:
logger.info(f"Email was not sent. Would send {new_status} email: {self.submitter.email}")
return None
try:
send_templated_email(
email_template,
@ -684,7 +692,7 @@ class DomainApplication(TimeStampedModel):
],
target=ApplicationStatus.APPROVED,
)
def approve(self):
def approve(self, send_email=True):
"""Approve an application that has been submitted.
This has substantial side-effects because it creates another database
@ -713,6 +721,7 @@ class DomainApplication(TimeStampedModel):
"application approved",
"emails/status_change_approved.txt",
"emails/status_change_approved_subject.txt",
send_email,
)
@transition(

View file

@ -18,9 +18,11 @@ from registrar.admin import (
from registrar.models import Domain, DomainApplication, DomainInformation, User, DomainInvitation, Contact, Website
from registrar.models.user_domain_role import UserDomainRole
from .common import (
MockSESClient,
AuditedAdminMockData,
completed_application,
generic_domain_object,
less_console_noise,
mock_user,
create_superuser,
create_user,
@ -35,7 +37,6 @@ from unittest.mock import patch
from unittest import skip
from django.conf import settings
from unittest.mock import MagicMock
import boto3_mocking # type: ignore
import logging
@ -58,7 +59,10 @@ class TestDomainAdmin(MockEppLib):
"""
self.client.force_login(self.superuser)
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
application.approve()
mock_client = MockSESClient()
with boto3_mocking.clients.handler_for("sesv2", mock_client):
with less_console_noise():
application.approve()
response = self.client.get("/admin/registrar/domain/")
@ -326,6 +330,7 @@ class TestDomainApplicationAdmin(MockEppLib):
url="/admin/registrar/DomainApplication/",
model=DomainApplication,
)
self.mock_client = MockSESClient()
def test_domain_sortable(self):
"""Tests if the DomainApplication sorts by domain correctly"""
@ -420,25 +425,23 @@ class TestDomainApplicationAdmin(MockEppLib):
EMAIL = "mayor@igorville.gov"
User.objects.filter(email=EMAIL).delete()
mock_client = MagicMock()
mock_client_instance = mock_client.return_value
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
# Create a sample application
application = completed_application()
with boto3_mocking.clients.handler_for("sesv2", mock_client):
# Create a sample application
application = completed_application()
# Create a mock request
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
# Create a mock request
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
# Modify the application's property
application.status = DomainApplication.ApplicationStatus.SUBMITTED
# Modify the application's property
application.status = DomainApplication.ApplicationStatus.SUBMITTED
# Use the model admin's save_model method
self.admin.save_model(request, application, form=None, change=True)
# Use the model admin's save_model method
self.admin.save_model(request, application, form=None, change=True)
# Access the arguments passed to send_email
call_args = mock_client_instance.send_email.call_args
args, kwargs = call_args
call_args = self.mock_client.EMAILS_SENT
kwargs = call_args[0]["kwargs"]
# Retrieve the email details from the arguments
from_email = kwargs.get("FromEmailAddress")
@ -452,8 +455,7 @@ class TestDomainApplicationAdmin(MockEppLib):
self.assertEqual(to_email, EMAIL)
self.assertIn(expected_string, email_body)
# Perform assertions on the mock call itself
mock_client_instance.send_email.assert_called_once()
self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
@boto3_mocking.patching
def test_save_model_sends_in_review_email(self):
@ -461,25 +463,23 @@ class TestDomainApplicationAdmin(MockEppLib):
EMAIL = "mayor@igorville.gov"
User.objects.filter(email=EMAIL).delete()
mock_client = MagicMock()
mock_client_instance = mock_client.return_value
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
# Create a sample application
application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED)
with boto3_mocking.clients.handler_for("sesv2", mock_client):
# Create a sample application
application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED)
# Create a mock request
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
# Create a mock request
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
# Modify the application's property
application.status = DomainApplication.ApplicationStatus.IN_REVIEW
# Modify the application's property
application.status = DomainApplication.ApplicationStatus.IN_REVIEW
# Use the model admin's save_model method
self.admin.save_model(request, application, form=None, change=True)
# Use the model admin's save_model method
self.admin.save_model(request, application, form=None, change=True)
# Access the arguments passed to send_email
call_args = mock_client_instance.send_email.call_args
args, kwargs = call_args
call_args = self.mock_client.EMAILS_SENT
kwargs = call_args[0]["kwargs"]
# Retrieve the email details from the arguments
from_email = kwargs.get("FromEmailAddress")
@ -493,8 +493,7 @@ class TestDomainApplicationAdmin(MockEppLib):
self.assertEqual(to_email, EMAIL)
self.assertIn(expected_string, email_body)
# Perform assertions on the mock call itself
mock_client_instance.send_email.assert_called_once()
self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
@boto3_mocking.patching
def test_save_model_sends_approved_email(self):
@ -502,25 +501,23 @@ class TestDomainApplicationAdmin(MockEppLib):
EMAIL = "mayor@igorville.gov"
User.objects.filter(email=EMAIL).delete()
mock_client = MagicMock()
mock_client_instance = mock_client.return_value
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
# Create a sample application
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
with boto3_mocking.clients.handler_for("sesv2", mock_client):
# Create a sample application
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
# Create a mock request
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
# Create a mock request
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
# Modify the application's property
application.status = DomainApplication.ApplicationStatus.APPROVED
# Modify the application's property
application.status = DomainApplication.ApplicationStatus.APPROVED
# Use the model admin's save_model method
self.admin.save_model(request, application, form=None, change=True)
# Use the model admin's save_model method
self.admin.save_model(request, application, form=None, change=True)
# Access the arguments passed to send_email
call_args = mock_client_instance.send_email.call_args
args, kwargs = call_args
call_args = self.mock_client.EMAILS_SENT
kwargs = call_args[0]["kwargs"]
# Retrieve the email details from the arguments
from_email = kwargs.get("FromEmailAddress")
@ -534,9 +531,9 @@ class TestDomainApplicationAdmin(MockEppLib):
self.assertEqual(to_email, EMAIL)
self.assertIn(expected_string, email_body)
# Perform assertions on the mock call itself
mock_client_instance.send_email.assert_called_once()
self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
@boto3_mocking.patching
def test_save_model_sets_approved_domain(self):
# make sure there is no user with this email
EMAIL = "mayor@igorville.gov"
@ -548,11 +545,13 @@ class TestDomainApplicationAdmin(MockEppLib):
# Create a mock request
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
# Modify the application's property
application.status = DomainApplication.ApplicationStatus.APPROVED
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
# Modify the application's property
application.status = DomainApplication.ApplicationStatus.APPROVED
# Use the model admin's save_model method
self.admin.save_model(request, application, form=None, change=True)
# Use the model admin's save_model method
self.admin.save_model(request, application, form=None, change=True)
# Test that approved domain exists and equals requested domain
self.assertEqual(application.requested_domain.name, application.approved_domain.name)
@ -563,25 +562,23 @@ class TestDomainApplicationAdmin(MockEppLib):
EMAIL = "mayor@igorville.gov"
User.objects.filter(email=EMAIL).delete()
mock_client = MagicMock()
mock_client_instance = mock_client.return_value
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
# Create a sample application
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
with boto3_mocking.clients.handler_for("sesv2", mock_client):
# Create a sample application
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
# Create a mock request
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
# Create a mock request
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
# Modify the application's property
application.status = DomainApplication.ApplicationStatus.ACTION_NEEDED
# Modify the application's property
application.status = DomainApplication.ApplicationStatus.ACTION_NEEDED
# Use the model admin's save_model method
self.admin.save_model(request, application, form=None, change=True)
# Use the model admin's save_model method
self.admin.save_model(request, application, form=None, change=True)
# Access the arguments passed to send_email
call_args = mock_client_instance.send_email.call_args
args, kwargs = call_args
call_args = self.mock_client.EMAILS_SENT
kwargs = call_args[0]["kwargs"]
# Retrieve the email details from the arguments
from_email = kwargs.get("FromEmailAddress")
@ -595,8 +592,7 @@ class TestDomainApplicationAdmin(MockEppLib):
self.assertEqual(to_email, EMAIL)
self.assertIn(expected_string, email_body)
# Perform assertions on the mock call itself
mock_client_instance.send_email.assert_called_once()
self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
@boto3_mocking.patching
def test_save_model_sends_rejected_email(self):
@ -604,25 +600,23 @@ class TestDomainApplicationAdmin(MockEppLib):
EMAIL = "mayor@igorville.gov"
User.objects.filter(email=EMAIL).delete()
mock_client = MagicMock()
mock_client_instance = mock_client.return_value
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
# Create a sample application
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
with boto3_mocking.clients.handler_for("sesv2", mock_client):
# Create a sample application
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
# Create a mock request
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
# Create a mock request
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
# Modify the application's property
application.status = DomainApplication.ApplicationStatus.REJECTED
# Modify the application's property
application.status = DomainApplication.ApplicationStatus.REJECTED
# Use the model admin's save_model method
self.admin.save_model(request, application, form=None, change=True)
# Use the model admin's save_model method
self.admin.save_model(request, application, form=None, change=True)
# Access the arguments passed to send_email
call_args = mock_client_instance.send_email.call_args
args, kwargs = call_args
call_args = self.mock_client.EMAILS_SENT
kwargs = call_args[0]["kwargs"]
# Retrieve the email details from the arguments
from_email = kwargs.get("FromEmailAddress")
@ -636,9 +630,9 @@ class TestDomainApplicationAdmin(MockEppLib):
self.assertEqual(to_email, EMAIL)
self.assertIn(expected_string, email_body)
# Perform assertions on the mock call itself
mock_client_instance.send_email.assert_called_once()
self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
@boto3_mocking.patching
def test_save_model_sets_restricted_status_on_user(self):
# make sure there is no user with this email
EMAIL = "mayor@igorville.gov"
@ -650,19 +644,23 @@ class TestDomainApplicationAdmin(MockEppLib):
# Create a mock request
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
# Modify the application's property
application.status = DomainApplication.ApplicationStatus.INELIGIBLE
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
# Modify the application's property
application.status = DomainApplication.ApplicationStatus.INELIGIBLE
# Use the model admin's save_model method
self.admin.save_model(request, application, form=None, change=True)
# Use the model admin's save_model method
self.admin.save_model(request, application, form=None, change=True)
# Test that approved domain exists and equals requested domain
self.assertEqual(application.creator.status, "restricted")
def test_readonly_when_restricted_creator(self):
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
application.creator.status = User.RESTRICTED
application.creator.save()
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
application.creator.status = User.RESTRICTED
application.creator.save()
request = self.factory.get("/")
request.user = self.superuser
@ -740,8 +738,10 @@ class TestDomainApplicationAdmin(MockEppLib):
def test_saving_when_restricted_creator(self):
# Create an instance of the model
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
application.creator.status = User.RESTRICTED
application.creator.save()
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
application.creator.status = User.RESTRICTED
application.creator.save()
# Create a request object with a superuser
request = self.factory.get("/")
@ -763,8 +763,10 @@ class TestDomainApplicationAdmin(MockEppLib):
def test_change_view_with_restricted_creator(self):
# Create an instance of the model
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
application.creator.status = User.RESTRICTED
application.creator.save()
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
application.creator.status = User.RESTRICTED
application.creator.save()
with patch("django.contrib.messages.warning") as mock_warning:
# Create a request object with a superuser
@ -779,6 +781,7 @@ class TestDomainApplicationAdmin(MockEppLib):
"Cannot edit an application with a restricted creator.",
)
@boto3_mocking.patching
def test_error_when_saving_approved_to_rejected_and_domain_is_active(self):
# Create an instance of the model
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
@ -800,9 +803,11 @@ class TestDomainApplicationAdmin(MockEppLib):
stack.enter_context(patch.object(Domain, "is_active", custom_is_active))
stack.enter_context(patch.object(messages, "error"))
# Simulate saving the model
application.status = DomainApplication.ApplicationStatus.REJECTED
self.admin.save_model(request, application, None, True)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
# Simulate saving the model
application.status = DomainApplication.ApplicationStatus.REJECTED
self.admin.save_model(request, application, None, True)
# Assert that the error message was called with the correct argument
messages.error.assert_called_once_with(
@ -831,10 +836,11 @@ class TestDomainApplicationAdmin(MockEppLib):
# Patch Domain.is_active and django.contrib.messages.error simultaneously
stack.enter_context(patch.object(Domain, "is_active", custom_is_active))
stack.enter_context(patch.object(messages, "error"))
# Simulate saving the model
application.status = DomainApplication.ApplicationStatus.REJECTED
self.admin.save_model(request, application, None, True)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
# Simulate saving the model
application.status = DomainApplication.ApplicationStatus.REJECTED
self.admin.save_model(request, application, None, True)
# Assert that the error message was never called
messages.error.assert_not_called()
@ -1091,6 +1097,7 @@ class TestDomainApplicationAdmin(MockEppLib):
User.objects.all().delete()
Contact.objects.all().delete()
Website.objects.all().delete()
self.mock_client.EMAILS_SENT.clear()
class DomainInvitationAdminTest(TestCase):

View file

@ -3,7 +3,7 @@
from unittest.mock import MagicMock
from django.test import TestCase
from .common import completed_application
from .common import completed_application, less_console_noise
import boto3_mocking # type: ignore
@ -20,7 +20,8 @@ class TestEmails(TestCase):
application = completed_application()
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
application.submit()
with less_console_noise():
application.submit()
# check that an email was sent
self.assertTrue(self.mock_client.send_email.called)
@ -56,7 +57,8 @@ class TestEmails(TestCase):
"""Test line spacing without current_website."""
application = completed_application(has_current_website=False)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
application.submit()
with less_console_noise():
application.submit()
_, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertNotIn("Current website for your organization:", body)
@ -68,7 +70,8 @@ class TestEmails(TestCase):
"""Test line spacing with current_website."""
application = completed_application(has_current_website=True)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
application.submit()
with less_console_noise():
application.submit()
_, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertIn("Current website for your organization:", body)
@ -81,7 +84,8 @@ class TestEmails(TestCase):
"""Test line spacing with other contacts."""
application = completed_application(has_other_contacts=True)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
application.submit()
with less_console_noise():
application.submit()
_, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertIn("Other employees from your organization:", body)
@ -94,7 +98,8 @@ class TestEmails(TestCase):
"""Test line spacing without other contacts."""
application = completed_application(has_other_contacts=False)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
application.submit()
with less_console_noise():
application.submit()
_, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertNotIn("Other employees from your organization:", body)
@ -106,7 +111,8 @@ class TestEmails(TestCase):
"""Test line spacing with alternative .gov domain."""
application = completed_application(has_alternative_gov_domain=True)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
application.submit()
with less_console_noise():
application.submit()
_, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertIn("city1.gov", body)
@ -118,7 +124,8 @@ class TestEmails(TestCase):
"""Test line spacing without alternative .gov domain."""
application = completed_application(has_alternative_gov_domain=False)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
application.submit()
with less_console_noise():
application.submit()
_, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertNotIn("city1.gov", body)
@ -130,7 +137,8 @@ class TestEmails(TestCase):
"""Test line spacing with about your organization."""
application = completed_application(has_about_your_organization=True)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
application.submit()
with less_console_noise():
application.submit()
_, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertIn("About your organization:", body)
@ -142,7 +150,8 @@ class TestEmails(TestCase):
"""Test line spacing without about your organization."""
application = completed_application(has_about_your_organization=False)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
application.submit()
with less_console_noise():
application.submit()
_, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertNotIn("About your organization:", body)
@ -154,7 +163,8 @@ class TestEmails(TestCase):
"""Test line spacing with anything else."""
application = completed_application(has_anything_else=True)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
application.submit()
with less_console_noise():
application.submit()
_, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
# spacing should be right between adjacent elements
@ -165,7 +175,8 @@ class TestEmails(TestCase):
"""Test line spacing without anything else."""
application = completed_application(has_anything_else=False)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
application.submit()
with less_console_noise():
application.submit()
_, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertNotIn("Anything else", body)

View file

@ -19,8 +19,6 @@ from registrar.models.transition_domain import TransitionDomain # type: ignore
from .common import MockSESClient, less_console_noise, completed_application
from django_fsm import TransitionNotAllowed
boto3_mocking.clients.register_handler("sesv2", MockSESClient)
# Test comment for push -- will remove
# The DomainApplication submit method has a side effect of sending an email
@ -53,6 +51,12 @@ class TestDomainApplication(TestCase):
status=DomainApplication.ApplicationStatus.INELIGIBLE, name="ineligible.gov"
)
self.mock_client = MockSESClient()
def tearDown(self):
super().tearDown()
self.mock_client.EMAILS_SENT.clear()
def assertNotRaises(self, exception_type):
"""Helper method for testing allowed transitions."""
return self.assertRaises(Exception, None, exception_type)
@ -130,17 +134,23 @@ class TestDomainApplication(TestCase):
def test_status_fsm_submit_fail(self):
user, _ = User.objects.get_or_create(username="testy")
application = DomainApplication.objects.create(creator=user)
with self.assertRaises(ValueError):
# can't submit an application with a null domain name
application.submit()
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
with self.assertRaises(ValueError):
# can't submit an application with a null domain name
application.submit()
def test_status_fsm_submit_succeed(self):
user, _ = User.objects.get_or_create(username="testy")
site = DraftDomain.objects.create(name="igorville.gov")
application = DomainApplication.objects.create(creator=user, requested_domain=site)
# no submitter email so this emits a log warning
with less_console_noise():
application.submit()
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
application.submit()
self.assertEqual(application.status, application.ApplicationStatus.SUBMITTED)
def test_submit_sends_email(self):
@ -154,7 +164,10 @@ class TestDomainApplication(TestCase):
submitter=contact,
)
application.save()
application.submit()
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
application.submit()
# check to see if an email was sent
self.assertGreater(
@ -179,12 +192,14 @@ class TestDomainApplication(TestCase):
(self.withdrawn_application, TransitionNotAllowed),
]
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
try:
application.submit()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
try:
application.submit()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
def test_submit_transition_not_allowed(self):
"""
@ -197,10 +212,12 @@ class TestDomainApplication(TestCase):
(self.ineligible_application, TransitionNotAllowed),
]
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.submit()
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.submit()
def test_in_review_transition_allowed(self):
"""
@ -214,12 +231,14 @@ class TestDomainApplication(TestCase):
(self.ineligible_application, TransitionNotAllowed),
]
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
try:
application.in_review()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
try:
application.in_review()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
def test_in_review_transition_not_allowed(self):
"""
@ -231,10 +250,12 @@ class TestDomainApplication(TestCase):
(self.withdrawn_application, TransitionNotAllowed),
]
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.in_review()
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.in_review()
def test_action_needed_transition_allowed(self):
"""
@ -247,12 +268,14 @@ class TestDomainApplication(TestCase):
(self.ineligible_application, TransitionNotAllowed),
]
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
try:
application.action_needed()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
try:
application.action_needed()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
def test_action_needed_transition_not_allowed(self):
"""
@ -265,10 +288,12 @@ class TestDomainApplication(TestCase):
(self.withdrawn_application, TransitionNotAllowed),
]
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.action_needed()
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.action_needed()
def test_approved_transition_allowed(self):
"""
@ -281,12 +306,27 @@ class TestDomainApplication(TestCase):
(self.rejected_application, TransitionNotAllowed),
]
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
try:
application.approve()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
try:
application.approve()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
def test_approved_skips_sending_email(self):
"""
Test that calling .approve with send_email=False doesn't actually send
an email
"""
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
self.submitted_application.approve(send_email=False)
# Assert that no emails were sent
self.assertEqual(len(self.mock_client.EMAILS_SENT), 0)
def test_approved_transition_not_allowed(self):
"""
@ -299,10 +339,12 @@ class TestDomainApplication(TestCase):
(self.ineligible_application, TransitionNotAllowed),
]
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.approve()
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.approve()
def test_withdraw_transition_allowed(self):
"""
@ -314,12 +356,14 @@ class TestDomainApplication(TestCase):
(self.action_needed_application, TransitionNotAllowed),
]
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
try:
application.withdraw()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
try:
application.withdraw()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
def test_withdraw_transition_not_allowed(self):
"""
@ -333,10 +377,12 @@ class TestDomainApplication(TestCase):
(self.ineligible_application, TransitionNotAllowed),
]
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.withdraw()
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.withdraw()
def test_reject_transition_allowed(self):
"""
@ -348,12 +394,14 @@ class TestDomainApplication(TestCase):
(self.approved_application, TransitionNotAllowed),
]
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
try:
application.reject()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
try:
application.reject()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
def test_reject_transition_not_allowed(self):
"""
@ -367,10 +415,12 @@ class TestDomainApplication(TestCase):
(self.ineligible_application, TransitionNotAllowed),
]
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.reject()
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.reject()
def test_reject_with_prejudice_transition_allowed(self):
"""
@ -383,12 +433,14 @@ class TestDomainApplication(TestCase):
(self.rejected_application, TransitionNotAllowed),
]
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
try:
application.reject_with_prejudice()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
try:
application.reject_with_prejudice()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
def test_reject_with_prejudice_transition_not_allowed(self):
"""
@ -401,10 +453,12 @@ class TestDomainApplication(TestCase):
(self.ineligible_application, TransitionNotAllowed),
]
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.reject_with_prejudice()
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
for application, exception_type in test_cases:
with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.reject_with_prejudice()
def test_transition_not_allowed_approved_rejected_when_domain_is_active(self):
"""Create an application with status approved, create a matching domain that
@ -418,11 +472,13 @@ class TestDomainApplication(TestCase):
def custom_is_active(self):
return True # Override to return True
# Use patch to temporarily replace is_active with the custom implementation
with patch.object(Domain, "is_active", custom_is_active):
# Now, when you call is_active on Domain, it will return True
with self.assertRaises(TransitionNotAllowed):
self.approved_application.reject()
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
# Use patch to temporarily replace is_active with the custom implementation
with patch.object(Domain, "is_active", custom_is_active):
# Now, when you call is_active on Domain, it will return True
with self.assertRaises(TransitionNotAllowed):
self.approved_application.reject()
def test_transition_not_allowed_approved_ineligible_when_domain_is_active(self):
"""Create an application with status approved, create a matching domain that
@ -436,24 +492,37 @@ class TestDomainApplication(TestCase):
def custom_is_active(self):
return True # Override to return True
# Use patch to temporarily replace is_active with the custom implementation
with patch.object(Domain, "is_active", custom_is_active):
# Now, when you call is_active on Domain, it will return True
with self.assertRaises(TransitionNotAllowed):
self.approved_application.reject_with_prejudice()
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
# Use patch to temporarily replace is_active with the custom implementation
with patch.object(Domain, "is_active", custom_is_active):
# Now, when you call is_active on Domain, it will return True
with self.assertRaises(TransitionNotAllowed):
self.approved_application.reject_with_prejudice()
class TestPermissions(TestCase):
"""Test the User-Domain-Role connection."""
def setUp(self):
super().setUp()
self.mock_client = MockSESClient()
def tearDown(self):
super().tearDown()
self.mock_client.EMAILS_SENT.clear()
@boto3_mocking.patching
def test_approval_creates_role(self):
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
user, _ = User.objects.get_or_create()
application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain)
# skip using the submit method
application.status = DomainApplication.ApplicationStatus.SUBMITTED
application.approve()
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
# skip using the submit method
application.status = DomainApplication.ApplicationStatus.SUBMITTED
application.approve()
# should be a role for this user
domain = Domain.objects.get(name="igorville.gov")
@ -464,13 +533,25 @@ class TestDomainInfo(TestCase):
"""Test creation of Domain Information when approved."""
def setUp(self):
super().setUp()
self.mock_client = MockSESClient()
def tearDown(self):
super().tearDown()
self.mock_client.EMAILS_SENT.clear()
@boto3_mocking.patching
def test_approval_creates_info(self):
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
user, _ = User.objects.get_or_create()
application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain)
# skip using the submit method
application.status = DomainApplication.ApplicationStatus.SUBMITTED
application.approve()
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
# skip using the submit method
application.status = DomainApplication.ApplicationStatus.SUBMITTED
application.approve()
# should be an information present for this domain
domain = Domain.objects.get(name="igorville.gov")
@ -572,11 +653,12 @@ class TestUser(TestCase):
caps_email = "MAYOR@igorville.gov"
# mock the domain invitation save routine
with patch("registrar.models.DomainInvitation.save") as save_mock:
DomainInvitation.objects.get_or_create(email=caps_email, domain=self.domain)
self.user.check_domain_invitations_on_login()
# if check_domain_invitations_on_login properly matches exactly one
# Domain Invitation, then save routine should be called exactly once
save_mock.assert_called_once()
with less_console_noise():
DomainInvitation.objects.get_or_create(email=caps_email, domain=self.domain)
self.user.check_domain_invitations_on_login()
# if check_domain_invitations_on_login properly matches exactly one
# Domain Invitation, then save routine should be called exactly once
save_mock.assert_called_once()
class TestContact(TestCase):

View file

@ -29,8 +29,9 @@ from epplibwrapper import (
RegistryError,
ErrorCode,
)
from .common import MockEppLib
from .common import MockEppLib, MockSESClient, less_console_noise
import logging
import boto3_mocking # type: ignore
logger = logging.getLogger(__name__)
@ -252,6 +253,7 @@ class TestDomainCache(MockEppLib):
class TestDomainCreation(MockEppLib):
"""Rule: An approved domain application must result in a domain"""
@boto3_mocking.patching
def test_approved_application_creates_domain_locally(self):
"""
Scenario: Analyst approves a domain application
@ -262,10 +264,14 @@ class TestDomainCreation(MockEppLib):
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
user, _ = User.objects.get_or_create()
application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain)
# skip using the submit method
application.status = DomainApplication.ApplicationStatus.SUBMITTED
# transition to approve state
application.approve()
mock_client = MockSESClient()
with boto3_mocking.clients.handler_for("sesv2", mock_client):
with less_console_noise():
# skip using the submit method
application.status = DomainApplication.ApplicationStatus.SUBMITTED
# transition to approve state
application.approve()
# should have information present for this domain
domain = Domain.objects.get(name="igorville.gov")
self.assertTrue(domain)

View file

@ -18,7 +18,8 @@ from unittest.mock import patch
from registrar.models.contact import Contact
from .common import MockEppLib, less_console_noise
from .common import MockEppLib, MockSESClient, less_console_noise
import boto3_mocking # type: ignore
class TestExtendExpirationDates(MockEppLib):
@ -706,17 +707,21 @@ class TestMigrations(TestCase):
def run_master_script(self):
# noqa here (E501) because splitting this up makes it
# confusing to read.
with patch(
"registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa
return_value=True,
):
call_command(
"master_domain_migrations",
runMigrations=True,
migrationDirectory=self.test_data_file_location,
migrationJSON=self.migration_json_filename,
disablePrompts=True,
)
mock_client = MockSESClient()
with boto3_mocking.clients.handler_for("sesv2", mock_client):
with patch(
"registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa
return_value=True,
):
with patch("registrar.utility.email.send_templated_email", return_value=None):
call_command(
"master_domain_migrations",
runMigrations=True,
migrationDirectory=self.test_data_file_location,
migrationJSON=self.migration_json_filename,
disablePrompts=True,
)
print(f"here: {mock_client.EMAILS_SENT}")
def compare_tables(
self,
@ -1019,6 +1024,7 @@ class TestMigrations(TestCase):
expected_missing_domain_invitations,
)
@boto3_mocking.patching
def test_send_domain_invitations_email(self):
"""Can send only a single domain invitation email."""
with less_console_noise():
@ -1027,9 +1033,12 @@ class TestMigrations(TestCase):
# this is one of the email addresses in data/test_contacts.txt
output_stream = StringIO()
# also have to re-point the logging handlers to output_stream
with less_console_noise(output_stream):
call_command("send_domain_invitations", "testuser@gmail.com", stdout=output_stream)
mock_client = MockSESClient()
with boto3_mocking.clients.handler_for("sesv2", mock_client):
# also have to re-point the logging handlers to output_stream
with less_console_noise(output_stream):
call_command("send_domain_invitations", "testuser@gmail.com", stdout=output_stream)
# Check that we had the right numbers in our output
output = output_stream.getvalue()
@ -1037,6 +1046,7 @@ class TestMigrations(TestCase):
self.assertIn("Found 1 transition domains", output)
self.assertTrue("would send email to testuser@gmail.com", output)
@boto3_mocking.patching
def test_send_domain_invitations_two_emails(self):
"""Can send only a single domain invitation email."""
with less_console_noise():
@ -1045,11 +1055,14 @@ class TestMigrations(TestCase):
# these are two email addresses in data/test_contacts.txt
output_stream = StringIO()
# also have to re-point the logging handlers to output_stream
with less_console_noise(output_stream):
call_command(
"send_domain_invitations", "testuser@gmail.com", "agustina.wyman7@test.com", stdout=output_stream
)
mock_client = MockSESClient()
with boto3_mocking.clients.handler_for("sesv2", mock_client):
# also have to re-point the logging handlers to output_stream
with less_console_noise(output_stream):
call_command(
"send_domain_invitations", "testuser@gmail.com", "agustina.wyman7@test.com", stdout=output_stream
)
# Check that we had the right numbers in our output
output = output_stream.getvalue()

View file

@ -5,7 +5,7 @@ from django.conf import settings
from django.test import Client, TestCase
from django.urls import reverse
from django.contrib.auth import get_user_model
from .common import MockEppLib, completed_application, create_user # type: ignore
from .common import MockEppLib, MockSESClient, completed_application, create_user # type: ignore
from django_webtest import WebTest # type: ignore
import boto3_mocking # type: ignore
@ -149,8 +149,11 @@ class DomainApplicationTests(TestWithUser, WebTest):
"""Test that an info message appears when user has multiple applications already"""
# create and submit an application
application = completed_application(user=self.user)
application.submit()
application.save()
mock_client = MockSESClient()
with boto3_mocking.clients.handler_for("sesv2", mock_client):
with less_console_noise():
application.submit()
application.save()
# now, attempt to create another one
with less_console_noise():
@ -1440,6 +1443,7 @@ class TestDomainManagers(TestDomainOverview):
response = self.client.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
self.assertContains(response, "Add a domain manager")
@boto3_mocking.patching
def test_domain_user_add_form(self):
"""Adding an existing user works."""
other_user, _ = get_user_model().objects.get_or_create(email="mayor@igorville.gov")
@ -1449,7 +1453,11 @@ class TestDomainManagers(TestDomainOverview):
add_page.form["email"] = "mayor@igorville.gov"
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
success_result = add_page.form.submit()
mock_client = MockSESClient()
with boto3_mocking.clients.handler_for("sesv2", mock_client):
with less_console_noise():
success_result = add_page.form.submit()
self.assertEqual(success_result.status_code, 302)
self.assertEqual(
@ -1478,7 +1486,12 @@ class TestDomainManagers(TestDomainOverview):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
add_page.form["email"] = email_address
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
success_result = add_page.form.submit()
mock_client = MockSESClient()
with boto3_mocking.clients.handler_for("sesv2", mock_client):
with less_console_noise():
success_result = add_page.form.submit()
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
success_page = success_result.follow()
@ -1504,7 +1517,12 @@ class TestDomainManagers(TestDomainOverview):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
add_page.form["email"] = caps_email_address
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
success_result = add_page.form.submit()
mock_client = MockSESClient()
with boto3_mocking.clients.handler_for("sesv2", mock_client):
with less_console_noise():
success_result = add_page.form.submit()
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
success_page = success_result.follow()
@ -1524,11 +1542,12 @@ class TestDomainManagers(TestDomainOverview):
mock_client = MagicMock()
mock_client_instance = mock_client.return_value
with boto3_mocking.clients.handler_for("sesv2", mock_client):
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
add_page.form["email"] = email_address
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
add_page.form.submit()
with less_console_noise():
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
add_page.form["email"] = email_address
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
add_page.form.submit()
# check the mock instance to see if `send_email` was called right
mock_client_instance.send_email.assert_called_once_with(
@ -1550,11 +1569,12 @@ class TestDomainManagers(TestDomainOverview):
mock_client_instance = mock_client.return_value
with boto3_mocking.clients.handler_for("sesv2", mock_client):
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
add_page.form["email"] = email_address
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
add_page.form.submit()
with less_console_noise():
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
add_page.form["email"] = email_address
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
add_page.form.submit()
# check the mock instance to see if `send_email` was called right
mock_client_instance.send_email.assert_called_once_with(
@ -1588,11 +1608,12 @@ class TestDomainManagers(TestDomainOverview):
mock_client_instance = mock_client.return_value
with boto3_mocking.clients.handler_for("sesv2", mock_client):
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
add_page.form["email"] = email_address
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
add_page.form.submit()
with less_console_noise():
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
add_page.form["email"] = email_address
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
add_page.form.submit()
# check the mock instance to see if `send_email` was called right
mock_client_instance.send_email.assert_called_once_with(
@ -1630,11 +1651,12 @@ class TestDomainManagers(TestDomainOverview):
mock_client_instance = mock_client.return_value
with boto3_mocking.clients.handler_for("sesv2", mock_client):
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
add_page.form["email"] = email_address
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
add_page.form.submit()
with less_console_noise():
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
add_page.form["email"] = email_address
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
add_page.form.submit()
# check the mock instance to see if `send_email` was called right
mock_client_instance.send_email.assert_called_once_with(
@ -1669,15 +1691,15 @@ class TestDomainManagers(TestDomainOverview):
self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain)
mock_client = MagicMock()
mock_error_message = MagicMock()
with boto3_mocking.clients.handler_for("sesv2", mock_client):
with patch("django.contrib.messages.error") as mock_error_message:
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
add_page.form["email"] = email_address
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
add_page.form.submit().follow()
with less_console_noise():
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
add_page.form["email"] = email_address
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
add_page.form.submit().follow()
expected_message_content = "Can't send invitation email. No email is associated with your account."
@ -1706,11 +1728,12 @@ class TestDomainManagers(TestDomainOverview):
mock_error_message = MagicMock()
with boto3_mocking.clients.handler_for("sesv2", mock_client):
with patch("django.contrib.messages.error") as mock_error_message:
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
add_page.form["email"] = email_address
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
add_page.form.submit().follow()
with less_console_noise():
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
add_page.form["email"] = email_address
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
add_page.form.submit().follow()
expected_message_content = "Can't send invitation email. No email is associated with your account."
@ -1724,7 +1747,11 @@ class TestDomainManagers(TestDomainOverview):
"""Posting to the delete view deletes an invitation."""
email_address = "mayor@igorville.gov"
invitation, _ = DomainInvitation.objects.get_or_create(domain=self.domain, email=email_address)
self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id}))
mock_client = MockSESClient()
with boto3_mocking.clients.handler_for("sesv2", mock_client):
with less_console_noise():
self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id}))
mock_client.EMAILS_SENT.clear()
with self.assertRaises(DomainInvitation.DoesNotExist):
DomainInvitation.objects.get(id=invitation.id)
@ -1736,8 +1763,11 @@ class TestDomainManagers(TestDomainOverview):
other_user = User()
other_user.save()
self.client.force_login(other_user)
with less_console_noise(): # permission denied makes console errors
result = self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id}))
mock_client = MagicMock()
with boto3_mocking.clients.handler_for("sesv2", mock_client):
with less_console_noise(): # permission denied makes console errors
result = self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id}))
self.assertEqual(result.status_code, 403)
@boto3_mocking.patching
@ -1753,7 +1783,11 @@ class TestDomainManagers(TestDomainOverview):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
add_page.form["email"] = email_address
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
add_page.form.submit()
mock_client = MagicMock()
with boto3_mocking.clients.handler_for("sesv2", mock_client):
with less_console_noise():
add_page.form.submit()
# user was invited, create them
new_user = User.objects.create(username=email_address, email=email_address)
@ -1808,6 +1842,7 @@ class TestDomainNameservers(TestDomainOverview):
# attempt to submit the form without two hosts, both subdomains,
# only one has ips
nameservers_page.form["form-1-server"] = "ns2.igorville.gov"
with less_console_noise(): # swallow log warning message
result = nameservers_page.form.submit()
# form submission was a post with an error, response should be a 200
@ -2143,8 +2178,10 @@ class TestDomainSecurityEmail(TestDomainOverview):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
security_email_page.form["security_email"] = "mayor@igorville.gov"
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
with less_console_noise(): # swallow log warning message
result = security_email_page.form.submit()
mock_client = MagicMock()
with boto3_mocking.clients.handler_for("sesv2", mock_client):
with less_console_noise(): # swallow log warning message
result = security_email_page.form.submit()
self.assertEqual(result.status_code, 302)
self.assertEqual(
result["Location"],
@ -2490,9 +2527,12 @@ class TestApplicationStatus(TestWithUser, WebTest):
self.assertContains(detail_page, "Admin Tester")
self.assertContains(detail_page, "Status:")
# click the "Withdraw request" button
withdraw_page = detail_page.click("Withdraw request")
self.assertContains(withdraw_page, "Withdraw request for")
home_page = withdraw_page.click("Withdraw request")
mock_client = MockSESClient()
with boto3_mocking.clients.handler_for("sesv2", mock_client):
with less_console_noise():
withdraw_page = detail_page.click("Withdraw request")
self.assertContains(withdraw_page, "Withdraw request for")
home_page = withdraw_page.click("Withdraw request")
# confirm that it has redirected, and the status has been updated to withdrawn
self.assertRedirects(
home_page,

View file

@ -1,11 +1,14 @@
"""Utilities for sending emails."""
import boto3
import logging
from django.conf import settings
from django.template.loader import get_template
logger = logging.getLogger(__name__)
class EmailSendingError(RuntimeError):
"""Local error for handling all failures when sending email."""
@ -20,7 +23,7 @@ def send_templated_email(template_name: str, subject_template_name: str, to_addr
context as Django's HTML templates. context gives additional information
that the template may use.
"""
logger.info(f"An email was sent! Template name: {template_name} to {to_address}")
template = get_template(template_name)
email_body = template.render(context=context)

View file

@ -67,6 +67,7 @@
10038 OUTOFSCOPE http://app:8080/dns/nameservers
10038 OUTOFSCOPE http://app:8080/dns/dnssec
10038 OUTOFSCOPE http://app:8080/dns/dnssec/dsdata
10038 OUTOFSCOPE http://app:8080/org-name-address
# This URL always returns 404, so include it as well.
10038 OUTOFSCOPE http://app:8080/todo
# OIDC isn't configured in the test environment and DEBUG=True so this gives a 500 without CSP headers