Merge branch 'main' into dk/661-other-contacts

This commit is contained in:
David Kennedy 2024-01-05 06:13:06 -05:00
commit 185c1f0cb6
No known key found for this signature in database
GPG key ID: 6528A5386E66B96B
15 changed files with 791 additions and 330 deletions

View file

@ -519,7 +519,7 @@ LOGIN_REQUIRED_IGNORE_PATHS = [
] ]
# where to go after logging out # where to go after logging out
LOGOUT_REDIRECT_URL = "home" LOGOUT_REDIRECT_URL = "https://get.gov/"
# disable dynamic client registration, # disable dynamic client registration,
# only the OP inside OIDC_PROVIDERS will be available # 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 creator=user, status=DomainApplication.ApplicationStatus.IN_REVIEW
).last() ).last()
logger.debug(f"Approving {application} for {user}") 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() application.save()

View file

@ -0,0 +1,21 @@
# Generated by Django 4.2.7 on 2023-12-23 01:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("registrar", "0060_domain_deleted_domain_first_ready"),
]
operations = [
migrations.AddField(
model_name="domain",
name="security_contact_registry_id",
field=models.TextField(
editable=False,
help_text="Duplication of registry's security contact id for when registry unavailable",
null=True,
),
),
]

View file

@ -31,7 +31,7 @@ from epplibwrapper import (
from registrar.models.utility.contact_error import ContactError, ContactErrorCodes from registrar.models.utility.contact_error import ContactError, ContactErrorCodes
from django.db.models import DateField from django.db.models import DateField, TextField
from .utility.domain_field import DomainField from .utility.domain_field import DomainField
from .utility.domain_helper import DomainHelper from .utility.domain_helper import DomainHelper
from .utility.time_stamped_model import TimeStampedModel from .utility.time_stamped_model import TimeStampedModel
@ -974,6 +974,12 @@ class Domain(TimeStampedModel, DomainHelper):
help_text=("Duplication of registry's expiration date saved for ease of reporting"), help_text=("Duplication of registry's expiration date saved for ease of reporting"),
) )
security_contact_registry_id = TextField(
null=True,
help_text=("Duplication of registry's security contact id for when registry unavailable"),
editable=False,
)
deleted = DateField( deleted = DateField(
null=True, null=True,
editable=False, editable=False,
@ -1127,15 +1133,21 @@ class Domain(TimeStampedModel, DomainHelper):
# Grab from cache # Grab from cache
contacts = self._get_property(desired_property) contacts = self._get_property(desired_property)
except KeyError as error: except KeyError as error:
logger.error(f"Could not find {contact_type_choice}: {error}") # if contact type is security, attempt to retrieve registry id
return None # for the security contact from domain.security_contact_registry_id
else: if contact_type_choice == PublicContact.ContactTypeChoices.SECURITY and self.security_contact_registry_id:
cached_contact = self.get_contact_in_keys(contacts, contact_type_choice) logger.info(f"Could not access registry, using fallback value of {self.security_contact_registry_id}")
if cached_contact is None: contacts = {PublicContact.ContactTypeChoices.SECURITY: self.security_contact_registry_id}
# TODO - #1103 else:
raise ContactError("No contact was found in cache or the registry") logger.error(f"Could not find {contact_type_choice}: {error}")
return None
return cached_contact cached_contact = self.get_contact_in_keys(contacts, contact_type_choice)
if cached_contact is None:
# TODO - #1103
raise ContactError("No contact was found in cache or the registry")
return cached_contact
def get_default_security_contact(self): def get_default_security_contact(self):
"""Gets the default security contact.""" """Gets the default security contact."""
@ -1630,6 +1642,8 @@ class Domain(TimeStampedModel, DomainHelper):
self._update_hosts_and_contacts(cleaned, fetch_hosts, fetch_contacts) self._update_hosts_and_contacts(cleaned, fetch_hosts, fetch_contacts)
if fetch_hosts: if fetch_hosts:
self._update_hosts_and_ips_in_db(cleaned) self._update_hosts_and_ips_in_db(cleaned)
if fetch_contacts:
self._update_security_contact_in_db(cleaned)
self._update_dates(cleaned) self._update_dates(cleaned)
self._cache = cleaned self._cache = cleaned
@ -1739,6 +1753,23 @@ class Domain(TimeStampedModel, DomainHelper):
for ip_address in cleaned_ips: for ip_address in cleaned_ips:
HostIP.objects.get_or_create(address=ip_address, host=host_in_db) HostIP.objects.get_or_create(address=ip_address, host=host_in_db)
def _update_security_contact_in_db(self, cleaned):
"""Update security contact registry id in database if retrieved from registry.
If no value is retrieved from registry, set to empty string in db.
Parameters:
self: the domain to be updated with security from cleaned
cleaned: dict containing contact registry ids. Security contact is of type
PublicContact.ContactTypeChoices.SECURITY
"""
cleaned_contacts = cleaned["contacts"]
security_contact_registry_id = ""
security_contact = cleaned_contacts[PublicContact.ContactTypeChoices.SECURITY]
if security_contact:
security_contact_registry_id = security_contact
self.security_contact_registry_id = security_contact_registry_id
self.save()
def _update_dates(self, cleaned): def _update_dates(self, cleaned):
"""Update dates (expiration and creation) from cleaned""" """Update dates (expiration and creation) from cleaned"""
requires_save = False requires_save = False

View file

@ -570,17 +570,25 @@ class DomainApplication(TimeStampedModel):
return not self.approved_domain.is_active() return not self.approved_domain.is_active()
return True return True
def _send_status_update_email(self, new_status, email_template, email_template_subject): def _send_status_update_email(self, new_status, email_template, email_template_subject, send_email=True):
"""Send a atatus update email to the submitter. """Send a status update email to the submitter.
The email goes to the email address that the submitter gave as their The email goes to the email address that the submitter gave as their
contact information. If there is not submitter information, then do contact information. If there is not submitter information, then do
nothing. 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: if self.submitter is None or self.submitter.email is None:
logger.warning(f"Cannot send {new_status} email, no submitter email address.") 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: try:
send_templated_email( send_templated_email(
email_template, email_template,
@ -684,7 +692,7 @@ class DomainApplication(TimeStampedModel):
], ],
target=ApplicationStatus.APPROVED, target=ApplicationStatus.APPROVED,
) )
def approve(self): def approve(self, send_email=True):
"""Approve an application that has been submitted. """Approve an application that has been submitted.
This has substantial side-effects because it creates another database This has substantial side-effects because it creates another database
@ -713,6 +721,7 @@ class DomainApplication(TimeStampedModel):
"application approved", "application approved",
"emails/status_change_approved.txt", "emails/status_change_approved.txt",
"emails/status_change_approved_subject.txt", "emails/status_change_approved_subject.txt",
send_email,
) )
@transition( @transition(

View file

@ -0,0 +1,34 @@
{% extends 'base.html' %}
{% load static form_helpers url_helpers %}
{% block content %}
<main id="main-content" class="grid-container">
<div class="grid-col desktop:grid-offset-2 desktop:grid-col-8">
<form class="usa-form usa-form--extra-large" method="post" novalidate>
{% csrf_token %}
<h1>Youre about to start your .gov domain request.</h1>
<p>You dont have to complete the process in one session. You can save what you enter and come back to it when youre ready.</p>
<p>Well use the information you provide to verify your organizations eligibility for a .gov domain. Well also verify that the domain you request meets our guidelines.</p>
<h2>Time to complete the form</h2>
<p>If you have <a href="{% public_site_url 'domains/before/#information-you%E2%80%99ll-need-to-complete-the-domain-request-form' %}" target="_blank" class="usa-link">all the information you need</a>,
completing your domain request might take around 15 minutes.</p>
<p><a href="{% public_site_url 'contact/' %}" target="_blank" rel="noopener noreferrer" class="usa-link">Contact us if you need help with your request</a>.</p>
{% block form_buttons %}
<div class="stepnav">
<button
type="submit"
name="submit_button"
value="intro_acknowledge"
class="usa-button"
>Continue</button>
</div>
{% endblock %}
</form>
</div>
</main>
{% endblock %}

View file

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

View file

@ -3,7 +3,7 @@
from unittest.mock import MagicMock from unittest.mock import MagicMock
from django.test import TestCase from django.test import TestCase
from .common import completed_application from .common import completed_application, less_console_noise
import boto3_mocking # type: ignore import boto3_mocking # type: ignore
@ -20,7 +20,8 @@ class TestEmails(TestCase):
application = completed_application() application = completed_application()
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): 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 # check that an email was sent
self.assertTrue(self.mock_client.send_email.called) self.assertTrue(self.mock_client.send_email.called)
@ -56,7 +57,8 @@ class TestEmails(TestCase):
"""Test line spacing without current_website.""" """Test line spacing without current_website."""
application = completed_application(has_current_website=False) application = completed_application(has_current_website=False)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): 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 _, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertNotIn("Current website for your organization:", body) self.assertNotIn("Current website for your organization:", body)
@ -68,7 +70,8 @@ class TestEmails(TestCase):
"""Test line spacing with current_website.""" """Test line spacing with current_website."""
application = completed_application(has_current_website=True) application = completed_application(has_current_website=True)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): 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 _, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertIn("Current website for your organization:", body) self.assertIn("Current website for your organization:", body)
@ -81,7 +84,8 @@ class TestEmails(TestCase):
"""Test line spacing with other contacts.""" """Test line spacing with other contacts."""
application = completed_application(has_other_contacts=True) application = completed_application(has_other_contacts=True)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): 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 _, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertIn("Other employees from your organization:", body) self.assertIn("Other employees from your organization:", body)
@ -94,7 +98,8 @@ class TestEmails(TestCase):
"""Test line spacing without other contacts.""" """Test line spacing without other contacts."""
application = completed_application(has_other_contacts=False) application = completed_application(has_other_contacts=False)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): 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 _, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertNotIn("Other employees from your organization:", body) self.assertNotIn("Other employees from your organization:", body)
@ -106,7 +111,8 @@ class TestEmails(TestCase):
"""Test line spacing with alternative .gov domain.""" """Test line spacing with alternative .gov domain."""
application = completed_application(has_alternative_gov_domain=True) application = completed_application(has_alternative_gov_domain=True)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): 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 _, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertIn("city1.gov", body) self.assertIn("city1.gov", body)
@ -118,7 +124,8 @@ class TestEmails(TestCase):
"""Test line spacing without alternative .gov domain.""" """Test line spacing without alternative .gov domain."""
application = completed_application(has_alternative_gov_domain=False) application = completed_application(has_alternative_gov_domain=False)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): 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 _, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertNotIn("city1.gov", body) self.assertNotIn("city1.gov", body)
@ -130,7 +137,8 @@ class TestEmails(TestCase):
"""Test line spacing with about your organization.""" """Test line spacing with about your organization."""
application = completed_application(has_about_your_organization=True) application = completed_application(has_about_your_organization=True)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): 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 _, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertIn("About your organization:", body) self.assertIn("About your organization:", body)
@ -142,7 +150,8 @@ class TestEmails(TestCase):
"""Test line spacing without about your organization.""" """Test line spacing without about your organization."""
application = completed_application(has_about_your_organization=False) application = completed_application(has_about_your_organization=False)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): 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 _, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertNotIn("About your organization:", body) self.assertNotIn("About your organization:", body)
@ -154,7 +163,8 @@ class TestEmails(TestCase):
"""Test line spacing with anything else.""" """Test line spacing with anything else."""
application = completed_application(has_anything_else=True) application = completed_application(has_anything_else=True)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): 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 _, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
# spacing should be right between adjacent elements # spacing should be right between adjacent elements
@ -165,7 +175,8 @@ class TestEmails(TestCase):
"""Test line spacing without anything else.""" """Test line spacing without anything else."""
application = completed_application(has_anything_else=False) application = completed_application(has_anything_else=False)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): 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 _, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertNotIn("Anything else", body) 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 .common import MockSESClient, less_console_noise, completed_application
from django_fsm import TransitionNotAllowed from django_fsm import TransitionNotAllowed
boto3_mocking.clients.register_handler("sesv2", MockSESClient)
# Test comment for push -- will remove # Test comment for push -- will remove
# The DomainApplication submit method has a side effect of sending an email # 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" 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): def assertNotRaises(self, exception_type):
"""Helper method for testing allowed transitions.""" """Helper method for testing allowed transitions."""
return self.assertRaises(Exception, None, exception_type) return self.assertRaises(Exception, None, exception_type)
@ -130,17 +134,23 @@ class TestDomainApplication(TestCase):
def test_status_fsm_submit_fail(self): def test_status_fsm_submit_fail(self):
user, _ = User.objects.get_or_create(username="testy") user, _ = User.objects.get_or_create(username="testy")
application = DomainApplication.objects.create(creator=user) application = DomainApplication.objects.create(creator=user)
with self.assertRaises(ValueError):
# can't submit an application with a null domain name with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
application.submit() 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): def test_status_fsm_submit_succeed(self):
user, _ = User.objects.get_or_create(username="testy") user, _ = User.objects.get_or_create(username="testy")
site = DraftDomain.objects.create(name="igorville.gov") site = DraftDomain.objects.create(name="igorville.gov")
application = DomainApplication.objects.create(creator=user, requested_domain=site) application = DomainApplication.objects.create(creator=user, requested_domain=site)
# no submitter email so this emits a log warning # 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) self.assertEqual(application.status, application.ApplicationStatus.SUBMITTED)
def test_submit_sends_email(self): def test_submit_sends_email(self):
@ -154,7 +164,10 @@ class TestDomainApplication(TestCase):
submitter=contact, submitter=contact,
) )
application.save() 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 # check to see if an email was sent
self.assertGreater( self.assertGreater(
@ -179,12 +192,14 @@ class TestDomainApplication(TestCase):
(self.withdrawn_application, TransitionNotAllowed), (self.withdrawn_application, TransitionNotAllowed),
] ]
for application, exception_type in test_cases: with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with self.subTest(application=application, exception_type=exception_type): with less_console_noise():
try: for application, exception_type in test_cases:
application.submit() with self.subTest(application=application, exception_type=exception_type):
except TransitionNotAllowed: try:
self.fail("TransitionNotAllowed was raised, but it was not expected.") application.submit()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
def test_submit_transition_not_allowed(self): def test_submit_transition_not_allowed(self):
""" """
@ -197,10 +212,12 @@ class TestDomainApplication(TestCase):
(self.ineligible_application, TransitionNotAllowed), (self.ineligible_application, TransitionNotAllowed),
] ]
for application, exception_type in test_cases: with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with self.subTest(application=application, exception_type=exception_type): with less_console_noise():
with self.assertRaises(exception_type): for application, exception_type in test_cases:
application.submit() with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.submit()
def test_in_review_transition_allowed(self): def test_in_review_transition_allowed(self):
""" """
@ -214,12 +231,14 @@ class TestDomainApplication(TestCase):
(self.ineligible_application, TransitionNotAllowed), (self.ineligible_application, TransitionNotAllowed),
] ]
for application, exception_type in test_cases: with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with self.subTest(application=application, exception_type=exception_type): with less_console_noise():
try: for application, exception_type in test_cases:
application.in_review() with self.subTest(application=application, exception_type=exception_type):
except TransitionNotAllowed: try:
self.fail("TransitionNotAllowed was raised, but it was not expected.") application.in_review()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
def test_in_review_transition_not_allowed(self): def test_in_review_transition_not_allowed(self):
""" """
@ -231,10 +250,12 @@ class TestDomainApplication(TestCase):
(self.withdrawn_application, TransitionNotAllowed), (self.withdrawn_application, TransitionNotAllowed),
] ]
for application, exception_type in test_cases: with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with self.subTest(application=application, exception_type=exception_type): with less_console_noise():
with self.assertRaises(exception_type): for application, exception_type in test_cases:
application.in_review() with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.in_review()
def test_action_needed_transition_allowed(self): def test_action_needed_transition_allowed(self):
""" """
@ -247,12 +268,14 @@ class TestDomainApplication(TestCase):
(self.ineligible_application, TransitionNotAllowed), (self.ineligible_application, TransitionNotAllowed),
] ]
for application, exception_type in test_cases: with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with self.subTest(application=application, exception_type=exception_type): with less_console_noise():
try: for application, exception_type in test_cases:
application.action_needed() with self.subTest(application=application, exception_type=exception_type):
except TransitionNotAllowed: try:
self.fail("TransitionNotAllowed was raised, but it was not expected.") application.action_needed()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
def test_action_needed_transition_not_allowed(self): def test_action_needed_transition_not_allowed(self):
""" """
@ -265,10 +288,12 @@ class TestDomainApplication(TestCase):
(self.withdrawn_application, TransitionNotAllowed), (self.withdrawn_application, TransitionNotAllowed),
] ]
for application, exception_type in test_cases: with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with self.subTest(application=application, exception_type=exception_type): with less_console_noise():
with self.assertRaises(exception_type): for application, exception_type in test_cases:
application.action_needed() with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.action_needed()
def test_approved_transition_allowed(self): def test_approved_transition_allowed(self):
""" """
@ -281,12 +306,27 @@ class TestDomainApplication(TestCase):
(self.rejected_application, TransitionNotAllowed), (self.rejected_application, TransitionNotAllowed),
] ]
for application, exception_type in test_cases: with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with self.subTest(application=application, exception_type=exception_type): with less_console_noise():
try: for application, exception_type in test_cases:
application.approve() with self.subTest(application=application, exception_type=exception_type):
except TransitionNotAllowed: try:
self.fail("TransitionNotAllowed was raised, but it was not expected.") 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): def test_approved_transition_not_allowed(self):
""" """
@ -299,10 +339,12 @@ class TestDomainApplication(TestCase):
(self.ineligible_application, TransitionNotAllowed), (self.ineligible_application, TransitionNotAllowed),
] ]
for application, exception_type in test_cases: with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with self.subTest(application=application, exception_type=exception_type): with less_console_noise():
with self.assertRaises(exception_type): for application, exception_type in test_cases:
application.approve() with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.approve()
def test_withdraw_transition_allowed(self): def test_withdraw_transition_allowed(self):
""" """
@ -314,12 +356,14 @@ class TestDomainApplication(TestCase):
(self.action_needed_application, TransitionNotAllowed), (self.action_needed_application, TransitionNotAllowed),
] ]
for application, exception_type in test_cases: with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with self.subTest(application=application, exception_type=exception_type): with less_console_noise():
try: for application, exception_type in test_cases:
application.withdraw() with self.subTest(application=application, exception_type=exception_type):
except TransitionNotAllowed: try:
self.fail("TransitionNotAllowed was raised, but it was not expected.") application.withdraw()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
def test_withdraw_transition_not_allowed(self): def test_withdraw_transition_not_allowed(self):
""" """
@ -333,10 +377,12 @@ class TestDomainApplication(TestCase):
(self.ineligible_application, TransitionNotAllowed), (self.ineligible_application, TransitionNotAllowed),
] ]
for application, exception_type in test_cases: with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with self.subTest(application=application, exception_type=exception_type): with less_console_noise():
with self.assertRaises(exception_type): for application, exception_type in test_cases:
application.withdraw() with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.withdraw()
def test_reject_transition_allowed(self): def test_reject_transition_allowed(self):
""" """
@ -348,12 +394,14 @@ class TestDomainApplication(TestCase):
(self.approved_application, TransitionNotAllowed), (self.approved_application, TransitionNotAllowed),
] ]
for application, exception_type in test_cases: with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with self.subTest(application=application, exception_type=exception_type): with less_console_noise():
try: for application, exception_type in test_cases:
application.reject() with self.subTest(application=application, exception_type=exception_type):
except TransitionNotAllowed: try:
self.fail("TransitionNotAllowed was raised, but it was not expected.") application.reject()
except TransitionNotAllowed:
self.fail("TransitionNotAllowed was raised, but it was not expected.")
def test_reject_transition_not_allowed(self): def test_reject_transition_not_allowed(self):
""" """
@ -367,10 +415,12 @@ class TestDomainApplication(TestCase):
(self.ineligible_application, TransitionNotAllowed), (self.ineligible_application, TransitionNotAllowed),
] ]
for application, exception_type in test_cases: with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with self.subTest(application=application, exception_type=exception_type): with less_console_noise():
with self.assertRaises(exception_type): for application, exception_type in test_cases:
application.reject() with self.subTest(application=application, exception_type=exception_type):
with self.assertRaises(exception_type):
application.reject()
def test_reject_with_prejudice_transition_allowed(self): def test_reject_with_prejudice_transition_allowed(self):
""" """
@ -383,12 +433,14 @@ class TestDomainApplication(TestCase):
(self.rejected_application, TransitionNotAllowed), (self.rejected_application, TransitionNotAllowed),
] ]
for application, exception_type in test_cases: with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with self.subTest(application=application, exception_type=exception_type): with less_console_noise():
try: for application, exception_type in test_cases:
application.reject_with_prejudice() with self.subTest(application=application, exception_type=exception_type):
except TransitionNotAllowed: try:
self.fail("TransitionNotAllowed was raised, but it was not expected.") 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): def test_reject_with_prejudice_transition_not_allowed(self):
""" """
@ -401,10 +453,12 @@ class TestDomainApplication(TestCase):
(self.ineligible_application, TransitionNotAllowed), (self.ineligible_application, TransitionNotAllowed),
] ]
for application, exception_type in test_cases: with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with self.subTest(application=application, exception_type=exception_type): with less_console_noise():
with self.assertRaises(exception_type): for application, exception_type in test_cases:
application.reject_with_prejudice() 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): def test_transition_not_allowed_approved_rejected_when_domain_is_active(self):
"""Create an application with status approved, create a matching domain that """Create an application with status approved, create a matching domain that
@ -418,11 +472,13 @@ class TestDomainApplication(TestCase):
def custom_is_active(self): def custom_is_active(self):
return True # Override to return True return True # Override to return True
# Use patch to temporarily replace is_active with the custom implementation with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with patch.object(Domain, "is_active", custom_is_active): with less_console_noise():
# Now, when you call is_active on Domain, it will return True # Use patch to temporarily replace is_active with the custom implementation
with self.assertRaises(TransitionNotAllowed): with patch.object(Domain, "is_active", custom_is_active):
self.approved_application.reject() # 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): def test_transition_not_allowed_approved_ineligible_when_domain_is_active(self):
"""Create an application with status approved, create a matching domain that """Create an application with status approved, create a matching domain that
@ -436,11 +492,13 @@ class TestDomainApplication(TestCase):
def custom_is_active(self): def custom_is_active(self):
return True # Override to return True return True # Override to return True
# Use patch to temporarily replace is_active with the custom implementation with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with patch.object(Domain, "is_active", custom_is_active): with less_console_noise():
# Now, when you call is_active on Domain, it will return True # Use patch to temporarily replace is_active with the custom implementation
with self.assertRaises(TransitionNotAllowed): with patch.object(Domain, "is_active", custom_is_active):
self.approved_application.reject_with_prejudice() # Now, when you call is_active on Domain, it will return True
with self.assertRaises(TransitionNotAllowed):
self.approved_application.reject_with_prejudice()
def test_has_rationale_returns_true(self): def test_has_rationale_returns_true(self):
"""has_rationale() returns true when an application has no_other_contacts_rationale""" """has_rationale() returns true when an application has no_other_contacts_rationale"""
@ -466,16 +524,27 @@ class TestDomainApplication(TestCase):
class TestPermissions(TestCase): class TestPermissions(TestCase):
"""Test the User-Domain-Role connection.""" """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): def test_approval_creates_role(self):
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov") draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
user, _ = User.objects.get_or_create() user, _ = User.objects.get_or_create()
application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain) application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain)
# skip using the submit method
application.status = DomainApplication.ApplicationStatus.SUBMITTED with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
application.approve() with less_console_noise():
# skip using the submit method
application.status = DomainApplication.ApplicationStatus.SUBMITTED
application.approve()
# should be a role for this user # should be a role for this user
domain = Domain.objects.get(name="igorville.gov") domain = Domain.objects.get(name="igorville.gov")
@ -486,13 +555,25 @@ class TestDomainInfo(TestCase):
"""Test creation of Domain Information when approved.""" """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): def test_approval_creates_info(self):
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov") draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
user, _ = User.objects.get_or_create() user, _ = User.objects.get_or_create()
application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain) application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain)
# skip using the submit method
application.status = DomainApplication.ApplicationStatus.SUBMITTED with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
application.approve() with less_console_noise():
# skip using the submit method
application.status = DomainApplication.ApplicationStatus.SUBMITTED
application.approve()
# should be an information present for this domain # should be an information present for this domain
domain = Domain.objects.get(name="igorville.gov") domain = Domain.objects.get(name="igorville.gov")
@ -594,11 +675,12 @@ class TestUser(TestCase):
caps_email = "MAYOR@igorville.gov" caps_email = "MAYOR@igorville.gov"
# mock the domain invitation save routine # mock the domain invitation save routine
with patch("registrar.models.DomainInvitation.save") as save_mock: with patch("registrar.models.DomainInvitation.save") as save_mock:
DomainInvitation.objects.get_or_create(email=caps_email, domain=self.domain) with less_console_noise():
self.user.check_domain_invitations_on_login() DomainInvitation.objects.get_or_create(email=caps_email, domain=self.domain)
# if check_domain_invitations_on_login properly matches exactly one self.user.check_domain_invitations_on_login()
# Domain Invitation, then save routine should be called exactly once # if check_domain_invitations_on_login properly matches exactly one
save_mock.assert_called_once() # Domain Invitation, then save routine should be called exactly once
save_mock.assert_called_once()
class TestContact(TestCase): class TestContact(TestCase):

View file

@ -29,8 +29,9 @@ from epplibwrapper import (
RegistryError, RegistryError,
ErrorCode, ErrorCode,
) )
from .common import MockEppLib from .common import MockEppLib, MockSESClient, less_console_noise
import logging import logging
import boto3_mocking # type: ignore
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -252,6 +253,7 @@ class TestDomainCache(MockEppLib):
class TestDomainCreation(MockEppLib): class TestDomainCreation(MockEppLib):
"""Rule: An approved domain application must result in a domain""" """Rule: An approved domain application must result in a domain"""
@boto3_mocking.patching
def test_approved_application_creates_domain_locally(self): def test_approved_application_creates_domain_locally(self):
""" """
Scenario: Analyst approves a domain application Scenario: Analyst approves a domain application
@ -262,10 +264,14 @@ class TestDomainCreation(MockEppLib):
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov") draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
user, _ = User.objects.get_or_create() user, _ = User.objects.get_or_create()
application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain) application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain)
# skip using the submit method
application.status = DomainApplication.ApplicationStatus.SUBMITTED mock_client = MockSESClient()
# transition to approve state with boto3_mocking.clients.handler_for("sesv2", mock_client):
application.approve() 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 # should have information present for this domain
domain = Domain.objects.get(name="igorville.gov") domain = Domain.objects.get(name="igorville.gov")
self.assertTrue(domain) self.assertTrue(domain)
@ -739,6 +745,50 @@ class TestRegistrantContacts(MockEppLib):
self.mockedSendFunction.assert_has_calls(expected_calls, any_order=True) self.mockedSendFunction.assert_has_calls(expected_calls, any_order=True)
self.assertEqual(PublicContact.objects.filter(domain=self.domain).count(), 1) self.assertEqual(PublicContact.objects.filter(domain=self.domain).count(), 1)
def test_security_email_returns_on_registry_error(self):
"""
Scenario: Security email previously set through EPP and stored in registrar's database.
Registry is unavailable and throws exception when attempting to build cache from
registry. Security email retrieved from database.
"""
# Use self.domain_contact which has been initialized with existing contacts, including securityContact
# call get_security_email to initially set the security_contact_registry_id in the domain model
self.domain_contact.get_security_email()
# invalidate the cache so the next time get_security_email is called, it has to attempt to populate cache
self.domain_contact._invalidate_cache()
# mock that registry throws an error on the EPP send
def side_effect(_request, cleaned):
raise RegistryError(code=ErrorCode.COMMAND_FAILED)
patcher = patch("registrar.models.domain.registry.send")
mocked_send = patcher.start()
mocked_send.side_effect = side_effect
# when get_security_email is called, the registry error will force the security contact
# to be retrieved using the security_contact_registry_id in the domain model
security_email = self.domain_contact.get_security_email()
# assert that the proper security contact was retrieved by testing the email matches expected value
self.assertEqual(security_email, "security@mail.gov")
patcher.stop()
def test_security_email_stored_on_fetch_cache(self):
"""
Scenario: Security email is stored in db when security contact is retrieved from fetch_cache.
Verify the success of this by asserting get_or_create calls to db.
The mocked data for the EPP calls for the freeman.gov domain returns a security
contact with registry id of securityContact when InfoContact is called
"""
# Use self.domain_contact which has been initialized with existing contacts, including securityContact
# force fetch_cache to be called, which will return above documented mocked hosts
self.domain_contact.get_security_email()
# assert that the security_contact_registry_id in the db matches "securityContact"
self.assertEqual(self.domain_contact.security_contact_registry_id, "securityContact")
def test_not_disclosed_on_other_contacts(self): def test_not_disclosed_on_other_contacts(self):
""" """
Scenario: Registrant creates a new domain with multiple contacts Scenario: Registrant creates a new domain with multiple contacts

View file

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

View file

@ -5,7 +5,7 @@ from django.conf import settings
from django.test import Client, TestCase from django.test import Client, TestCase
from django.urls import reverse from django.urls import reverse
from django.contrib.auth import get_user_model 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 from django_webtest import WebTest # type: ignore
import boto3_mocking # type: ignore import boto3_mocking # type: ignore
@ -110,7 +110,7 @@ class LoggedInTests(TestWithUser):
response = self.client.get("/register/", follow=True) response = self.client.get("/register/", follow=True)
self.assertContains( self.assertContains(
response, response,
"What kind of U.S.-based government organization do you represent?", "Youre about to start your .gov domain request.",
) )
def test_domain_application_form_with_ineligible_user(self): def test_domain_application_form_with_ineligible_user(self):
@ -139,24 +139,70 @@ class DomainApplicationTests(TestWithUser, WebTest):
self.app.set_user(self.user.username) self.app.set_user(self.user.username)
self.TITLES = ApplicationWizard.TITLES self.TITLES = ApplicationWizard.TITLES
def test_application_form_intro_acknowledgement(self):
"""Tests that user is presented with intro acknowledgement page"""
intro_page = self.app.get(reverse("application:"))
self.assertContains(intro_page, "Youre about to start your .gov domain request")
def test_application_form_intro_is_skipped_when_edit_access(self):
"""Tests that user is NOT presented with intro acknowledgement page when accessed through 'edit'"""
completed_application(status=DomainApplication.ApplicationStatus.STARTED, user=self.user)
home_page = self.app.get("/")
self.assertContains(home_page, "city.gov")
# click the "Edit" link
detail_page = home_page.click("Edit", index=0)
# Check that the response is a redirect
self.assertEqual(detail_page.status_code, 302)
# You can access the 'Location' header to get the redirect URL
redirect_url = detail_page.url
self.assertEqual(redirect_url, "/register/organization_type/")
def test_application_form_empty_submit(self): def test_application_form_empty_submit(self):
# 302 redirect to the first form """Tests empty submit on the first page after the acknowledgement page"""
page = self.app.get(reverse("application:")).follow() intro_page = self.app.get(reverse("application:"))
# django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here
# and then setting the cookie on each request.
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
intro_form = intro_page.forms[0]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
intro_result = intro_form.submit()
# follow first redirect
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_page = intro_result.follow()
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
# submitting should get back the same page if the required field is empty # submitting should get back the same page if the required field is empty
result = page.forms[0].submit() result = type_page.forms[0].submit()
self.assertIn("What kind of U.S.-based government organization do you represent?", result) self.assertIn("What kind of U.S.-based government organization do you represent?", result)
def test_application_multiple_applications_exist(self): def test_application_multiple_applications_exist(self):
"""Test that an info message appears when user has multiple applications already""" """Test that an info message appears when user has multiple applications already"""
# create and submit an application # create and submit an application
application = completed_application(user=self.user) application = completed_application(user=self.user)
application.submit() mock_client = MockSESClient()
application.save() with boto3_mocking.clients.handler_for("sesv2", mock_client):
with less_console_noise():
application.submit()
application.save()
# now, attempt to create another one # now, attempt to create another one
with less_console_noise(): with less_console_noise():
page = self.app.get("/register/").follow() intro_page = self.app.get(reverse("application:"))
self.assertContains(page, "You cannot submit this request yet") session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
intro_form = intro_page.forms[0]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
intro_result = intro_form.submit()
# follow first redirect
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_page = intro_result.follow()
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.assertContains(type_page, "You cannot submit this request yet")
@boto3_mocking.patching @boto3_mocking.patching
def test_application_form_submission(self): def test_application_form_submission(self):
@ -175,13 +221,22 @@ class DomainApplicationTests(TestWithUser, WebTest):
SKIPPED_PAGES = 3 SKIPPED_PAGES = 3
num_pages = len(self.TITLES) - SKIPPED_PAGES num_pages = len(self.TITLES) - SKIPPED_PAGES
type_page = self.app.get(reverse("application:")).follow() intro_page = self.app.get(reverse("application:"))
# django-webtest does not handle cookie-based sessions well because it keeps # django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept # resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here # of a "session". We are going to do it manually, saving the session ID here
# and then setting the cookie on each request. # and then setting the cookie on each request.
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
intro_form = intro_page.forms[0]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
intro_result = intro_form.submit()
# follow first redirect
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_page = intro_result.follow()
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
# ---- TYPE PAGE ---- # ---- TYPE PAGE ----
type_form = type_page.forms[0] type_form = type_page.forms[0]
type_form["organization_type-organization_type"] = "federal" type_form["organization_type-organization_type"] = "federal"
@ -543,13 +598,22 @@ class DomainApplicationTests(TestWithUser, WebTest):
def test_application_form_conditional_federal(self): def test_application_form_conditional_federal(self):
"""Federal branch question is shown for federal organizations.""" """Federal branch question is shown for federal organizations."""
type_page = self.app.get(reverse("application:")).follow() intro_page = self.app.get(reverse("application:"))
# django-webtest does not handle cookie-based sessions well because it keeps # django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept # resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here # of a "session". We are going to do it manually, saving the session ID here
# and then setting the cookie on each request. # and then setting the cookie on each request.
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
intro_form = intro_page.forms[0]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
intro_result = intro_form.submit()
# follow first redirect
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_page = intro_result.follow()
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
# ---- TYPE PAGE ---- # ---- TYPE PAGE ----
# the conditional step titles shouldn't appear initially # the conditional step titles shouldn't appear initially
@ -589,13 +653,22 @@ class DomainApplicationTests(TestWithUser, WebTest):
def test_application_form_conditional_elections(self): def test_application_form_conditional_elections(self):
"""Election question is shown for other organizations.""" """Election question is shown for other organizations."""
type_page = self.app.get(reverse("application:")).follow() intro_page = self.app.get(reverse("application:"))
# django-webtest does not handle cookie-based sessions well because it keeps # django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept # resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here # of a "session". We are going to do it manually, saving the session ID here
# and then setting the cookie on each request. # and then setting the cookie on each request.
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
intro_form = intro_page.forms[0]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
intro_result = intro_form.submit()
# follow first redirect
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_page = intro_result.follow()
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
# ---- TYPE PAGE ---- # ---- TYPE PAGE ----
# the conditional step titles shouldn't appear initially # the conditional step titles shouldn't appear initially
@ -634,13 +707,22 @@ class DomainApplicationTests(TestWithUser, WebTest):
def test_application_form_section_skipping(self): def test_application_form_section_skipping(self):
"""Can skip forward and back in sections""" """Can skip forward and back in sections"""
type_page = self.app.get(reverse("application:")).follow() intro_page = self.app.get(reverse("application:"))
# django-webtest does not handle cookie-based sessions well because it keeps # django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept # resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here # of a "session". We are going to do it manually, saving the session ID here
# and then setting the cookie on each request. # and then setting the cookie on each request.
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
intro_form = intro_page.forms[0]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
intro_result = intro_form.submit()
# follow first redirect
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_page = intro_result.follow()
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
type_form = type_page.forms[0] type_form = type_page.forms[0]
type_form["organization_type-organization_type"] = "federal" type_form["organization_type-organization_type"] = "federal"
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
@ -662,13 +744,22 @@ class DomainApplicationTests(TestWithUser, WebTest):
def test_application_form_nonfederal(self): def test_application_form_nonfederal(self):
"""Non-federal organizations don't have to provide their federal agency.""" """Non-federal organizations don't have to provide their federal agency."""
type_page = self.app.get(reverse("application:")).follow() intro_page = self.app.get(reverse("application:"))
# django-webtest does not handle cookie-based sessions well because it keeps # django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept # resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here # of a "session". We are going to do it manually, saving the session ID here
# and then setting the cookie on each request. # and then setting the cookie on each request.
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
intro_form = intro_page.forms[0]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
intro_result = intro_form.submit()
# follow first redirect
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_page = intro_result.follow()
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
type_form = type_page.forms[0] type_form = type_page.forms[0]
type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.INTERSTATE type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.INTERSTATE
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
@ -698,13 +789,22 @@ class DomainApplicationTests(TestWithUser, WebTest):
def test_application_about_your_organization_special(self): def test_application_about_your_organization_special(self):
"""Special districts have to answer an additional question.""" """Special districts have to answer an additional question."""
type_page = self.app.get(reverse("application:")).follow() intro_page = self.app.get(reverse("application:"))
# django-webtest does not handle cookie-based sessions well because it keeps # django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept # resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here # of a "session". We are going to do it manually, saving the session ID here
# and then setting the cookie on each request. # and then setting the cookie on each request.
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
intro_form = intro_page.forms[0]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
intro_result = intro_form.submit()
# follow first redirect
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_page = intro_result.follow()
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
type_form = type_page.forms[0] type_form = type_page.forms[0]
type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.SPECIAL_DISTRICT type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.SPECIAL_DISTRICT
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
@ -1068,13 +1168,22 @@ class DomainApplicationTests(TestWithUser, WebTest):
def test_application_about_your_organiztion_interstate(self): def test_application_about_your_organiztion_interstate(self):
"""Special districts have to answer an additional question.""" """Special districts have to answer an additional question."""
type_page = self.app.get(reverse("application:")).follow() intro_page = self.app.get(reverse("application:"))
# django-webtest does not handle cookie-based sessions well because it keeps # django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept # resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here # of a "session". We are going to do it manually, saving the session ID here
# and then setting the cookie on each request. # and then setting the cookie on each request.
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
intro_form = intro_page.forms[0]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
intro_result = intro_form.submit()
# follow first redirect
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_page = intro_result.follow()
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
type_form = type_page.forms[0] type_form = type_page.forms[0]
type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.INTERSTATE type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.INTERSTATE
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
@ -1087,12 +1196,22 @@ class DomainApplicationTests(TestWithUser, WebTest):
def test_application_tribal_government(self): def test_application_tribal_government(self):
"""Tribal organizations have to answer an additional question.""" """Tribal organizations have to answer an additional question."""
type_page = self.app.get(reverse("application:")).follow() intro_page = self.app.get(reverse("application:"))
# django-webtest does not handle cookie-based sessions well because it keeps # django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept # resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here # of a "session". We are going to do it manually, saving the session ID here
# and then setting the cookie on each request. # and then setting the cookie on each request.
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
intro_form = intro_page.forms[0]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
intro_result = intro_form.submit()
# follow first redirect
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_page = intro_result.follow()
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
type_form = type_page.forms[0] type_form = type_page.forms[0]
type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.TRIBAL type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.TRIBAL
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
@ -1107,13 +1226,22 @@ class DomainApplicationTests(TestWithUser, WebTest):
self.assertContains(tribal_government_page, self.TITLES[Step.TRIBAL_GOVERNMENT]) self.assertContains(tribal_government_page, self.TITLES[Step.TRIBAL_GOVERNMENT])
def test_application_ao_dynamic_text(self): def test_application_ao_dynamic_text(self):
type_page = self.app.get(reverse("application:")).follow() intro_page = self.app.get(reverse("application:"))
# django-webtest does not handle cookie-based sessions well because it keeps # django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept # resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here # of a "session". We are going to do it manually, saving the session ID here
# and then setting the cookie on each request. # and then setting the cookie on each request.
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
intro_form = intro_page.forms[0]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
intro_result = intro_form.submit()
# follow first redirect
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_page = intro_result.follow()
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
# ---- TYPE PAGE ---- # ---- TYPE PAGE ----
type_form = type_page.forms[0] type_form = type_page.forms[0]
type_form["organization_type-organization_type"] = "federal" type_form["organization_type-organization_type"] = "federal"
@ -1169,12 +1297,22 @@ class DomainApplicationTests(TestWithUser, WebTest):
self.assertContains(ao_page, "Domain requests from cities") self.assertContains(ao_page, "Domain requests from cities")
def test_application_dotgov_domain_dynamic_text(self): def test_application_dotgov_domain_dynamic_text(self):
type_page = self.app.get(reverse("application:")).follow() intro_page = self.app.get(reverse("application:"))
# django-webtest does not handle cookie-based sessions well because it keeps # django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept # resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here # of a "session". We are going to do it manually, saving the session ID here
# and then setting the cookie on each request. # and then setting the cookie on each request.
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
intro_form = intro_page.forms[0]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
intro_result = intro_form.submit()
# follow first redirect
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_page = intro_result.follow()
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
# ---- TYPE PAGE ---- # ---- TYPE PAGE ----
type_form = type_page.forms[0] type_form = type_page.forms[0]
type_form["organization_type-organization_type"] = "federal" type_form["organization_type-organization_type"] = "federal"
@ -1418,8 +1556,22 @@ class DomainApplicationTests(TestWithUser, WebTest):
Make sure the long name is displaying in the application form, Make sure the long name is displaying in the application form,
org step org step
""" """
request = self.app.get(reverse("application:")).follow() intro_page = self.app.get(reverse("application:"))
self.assertContains(request, "Federal: an agency of the U.S. government") # django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here
# and then setting the cookie on each request.
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
intro_form = intro_page.forms[0]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
intro_result = intro_form.submit()
# follow first redirect
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_page = intro_result.follow()
self.assertContains(type_page, "Federal: an agency of the U.S. government")
def test_long_org_name_in_application_manage(self): def test_long_org_name_in_application_manage(self):
""" """
@ -1693,6 +1845,7 @@ class TestDomainManagers(TestDomainOverview):
response = self.client.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) response = self.client.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
self.assertContains(response, "Add a domain manager") self.assertContains(response, "Add a domain manager")
@boto3_mocking.patching
def test_domain_user_add_form(self): def test_domain_user_add_form(self):
"""Adding an existing user works.""" """Adding an existing user works."""
other_user, _ = get_user_model().objects.get_or_create(email="mayor@igorville.gov") other_user, _ = get_user_model().objects.get_or_create(email="mayor@igorville.gov")
@ -1702,7 +1855,11 @@ class TestDomainManagers(TestDomainOverview):
add_page.form["email"] = "mayor@igorville.gov" add_page.form["email"] = "mayor@igorville.gov"
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) 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(success_result.status_code, 302)
self.assertEqual( self.assertEqual(
@ -1731,7 +1888,12 @@ class TestDomainManagers(TestDomainOverview):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
add_page.form["email"] = email_address add_page.form["email"] = email_address
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) 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) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
success_page = success_result.follow() success_page = success_result.follow()
@ -1757,7 +1919,12 @@ class TestDomainManagers(TestDomainOverview):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
add_page.form["email"] = caps_email_address add_page.form["email"] = caps_email_address
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) 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) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
success_page = success_result.follow() success_page = success_result.follow()
@ -1777,11 +1944,12 @@ class TestDomainManagers(TestDomainOverview):
mock_client = MagicMock() mock_client = MagicMock()
mock_client_instance = mock_client.return_value mock_client_instance = mock_client.return_value
with boto3_mocking.clients.handler_for("sesv2", mock_client): with boto3_mocking.clients.handler_for("sesv2", mock_client):
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) with less_console_noise():
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
add_page.form["email"] = email_address session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) add_page.form["email"] = email_address
add_page.form.submit() 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 # check the mock instance to see if `send_email` was called right
mock_client_instance.send_email.assert_called_once_with( mock_client_instance.send_email.assert_called_once_with(
@ -1803,11 +1971,12 @@ class TestDomainManagers(TestDomainOverview):
mock_client_instance = mock_client.return_value mock_client_instance = mock_client.return_value
with boto3_mocking.clients.handler_for("sesv2", mock_client): with boto3_mocking.clients.handler_for("sesv2", mock_client):
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) with less_console_noise():
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
add_page.form["email"] = email_address session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) add_page.form["email"] = email_address
add_page.form.submit() 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 # check the mock instance to see if `send_email` was called right
mock_client_instance.send_email.assert_called_once_with( mock_client_instance.send_email.assert_called_once_with(
@ -1841,11 +2010,12 @@ class TestDomainManagers(TestDomainOverview):
mock_client_instance = mock_client.return_value mock_client_instance = mock_client.return_value
with boto3_mocking.clients.handler_for("sesv2", mock_client): with boto3_mocking.clients.handler_for("sesv2", mock_client):
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) with less_console_noise():
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
add_page.form["email"] = email_address session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) add_page.form["email"] = email_address
add_page.form.submit() 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 # check the mock instance to see if `send_email` was called right
mock_client_instance.send_email.assert_called_once_with( mock_client_instance.send_email.assert_called_once_with(
@ -1883,11 +2053,12 @@ class TestDomainManagers(TestDomainOverview):
mock_client_instance = mock_client.return_value mock_client_instance = mock_client.return_value
with boto3_mocking.clients.handler_for("sesv2", mock_client): with boto3_mocking.clients.handler_for("sesv2", mock_client):
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) with less_console_noise():
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
add_page.form["email"] = email_address session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) add_page.form["email"] = email_address
add_page.form.submit() 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 # check the mock instance to see if `send_email` was called right
mock_client_instance.send_email.assert_called_once_with( mock_client_instance.send_email.assert_called_once_with(
@ -1922,15 +2093,15 @@ class TestDomainManagers(TestDomainOverview):
self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain)
mock_client = MagicMock() mock_client = MagicMock()
mock_error_message = MagicMock() mock_error_message = MagicMock()
with boto3_mocking.clients.handler_for("sesv2", mock_client): with boto3_mocking.clients.handler_for("sesv2", mock_client):
with patch("django.contrib.messages.error") as mock_error_message: with patch("django.contrib.messages.error") as mock_error_message:
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) with less_console_noise():
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
add_page.form["email"] = email_address session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) add_page.form["email"] = email_address
add_page.form.submit().follow() 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." expected_message_content = "Can't send invitation email. No email is associated with your account."
@ -1959,11 +2130,12 @@ class TestDomainManagers(TestDomainOverview):
mock_error_message = MagicMock() mock_error_message = MagicMock()
with boto3_mocking.clients.handler_for("sesv2", mock_client): with boto3_mocking.clients.handler_for("sesv2", mock_client):
with patch("django.contrib.messages.error") as mock_error_message: with patch("django.contrib.messages.error") as mock_error_message:
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) with less_console_noise():
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
add_page.form["email"] = email_address session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) add_page.form["email"] = email_address
add_page.form.submit().follow() 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." expected_message_content = "Can't send invitation email. No email is associated with your account."
@ -1977,7 +2149,11 @@ class TestDomainManagers(TestDomainOverview):
"""Posting to the delete view deletes an invitation.""" """Posting to the delete view deletes an invitation."""
email_address = "mayor@igorville.gov" email_address = "mayor@igorville.gov"
invitation, _ = DomainInvitation.objects.get_or_create(domain=self.domain, email=email_address) 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): with self.assertRaises(DomainInvitation.DoesNotExist):
DomainInvitation.objects.get(id=invitation.id) DomainInvitation.objects.get(id=invitation.id)
@ -1989,8 +2165,11 @@ class TestDomainManagers(TestDomainOverview):
other_user = User() other_user = User()
other_user.save() other_user.save()
self.client.force_login(other_user) self.client.force_login(other_user)
with less_console_noise(): # permission denied makes console errors mock_client = MagicMock()
result = self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id})) 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) self.assertEqual(result.status_code, 403)
@boto3_mocking.patching @boto3_mocking.patching
@ -2006,7 +2185,11 @@ class TestDomainManagers(TestDomainOverview):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
add_page.form["email"] = email_address add_page.form["email"] = email_address
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) 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 # user was invited, create them
new_user = User.objects.create(username=email_address, email=email_address) new_user = User.objects.create(username=email_address, email=email_address)
@ -2061,6 +2244,7 @@ class TestDomainNameservers(TestDomainOverview):
# attempt to submit the form without two hosts, both subdomains, # attempt to submit the form without two hosts, both subdomains,
# only one has ips # only one has ips
nameservers_page.form["form-1-server"] = "ns2.igorville.gov" nameservers_page.form["form-1-server"] = "ns2.igorville.gov"
with less_console_noise(): # swallow log warning message with less_console_noise(): # swallow log warning message
result = nameservers_page.form.submit() result = nameservers_page.form.submit()
# form submission was a post with an error, response should be a 200 # form submission was a post with an error, response should be a 200
@ -2396,8 +2580,10 @@ class TestDomainSecurityEmail(TestDomainOverview):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
security_email_page.form["security_email"] = "mayor@igorville.gov" security_email_page.form["security_email"] = "mayor@igorville.gov"
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
with less_console_noise(): # swallow log warning message mock_client = MagicMock()
result = security_email_page.form.submit() 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.status_code, 302)
self.assertEqual( self.assertEqual(
result["Location"], result["Location"],
@ -2743,9 +2929,12 @@ class TestApplicationStatus(TestWithUser, WebTest):
self.assertContains(detail_page, "Admin Tester") self.assertContains(detail_page, "Admin Tester")
self.assertContains(detail_page, "Status:") self.assertContains(detail_page, "Status:")
# click the "Withdraw request" button # click the "Withdraw request" button
withdraw_page = detail_page.click("Withdraw request") mock_client = MockSESClient()
self.assertContains(withdraw_page, "Withdraw request for") with boto3_mocking.clients.handler_for("sesv2", mock_client):
home_page = withdraw_page.click("Withdraw request") 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 # confirm that it has redirected, and the status has been updated to withdrawn
self.assertRedirects( self.assertRedirects(
home_page, home_page,

View file

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

View file

@ -206,16 +206,16 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView):
# if accessing this class directly, redirect to the first step # if accessing this class directly, redirect to the first step
# in other words, if `ApplicationWizard` is called as view # in other words, if `ApplicationWizard` is called as view
# directly by some redirect or url handler, we'll send users # directly by some redirect or url handler, we'll send users
# to the first step in the processes; subclasses will NOT # either to an acknowledgement page or to the first step in
# be redirected. The purpose of this is to allow code to # the processes (if an edit rather than a new request); subclasses
# will NOT be redirected. The purpose of this is to allow code to
# send users "to the application wizard" without needing to # send users "to the application wizard" without needing to
# know which view is first in the list of steps. # know which view is first in the list of steps.
if self.__class__ == ApplicationWizard: if self.__class__ == ApplicationWizard:
# if starting a new application, clear the storage
if request.path_info == self.NEW_URL_NAME: if request.path_info == self.NEW_URL_NAME:
del self.storage return render(request, "application_intro.html")
else:
return self.goto(self.steps.first) return self.goto(self.steps.first)
self.steps.current = current_url self.steps.current = current_url
context = self.get_context_data() context = self.get_context_data()
@ -370,13 +370,20 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView):
def post(self, request, *args, **kwargs) -> HttpResponse: def post(self, request, *args, **kwargs) -> HttpResponse:
"""This method handles POST requests.""" """This method handles POST requests."""
# if accessing this class directly, redirect to the first step
if self.__class__ == ApplicationWizard:
return self.goto(self.steps.first)
# which button did the user press? # which button did the user press?
button: str = request.POST.get("submit_button", "") button: str = request.POST.get("submit_button", "")
# if user has acknowledged the intro message
if button == "intro_acknowledge":
if request.path_info == self.NEW_URL_NAME:
del self.storage
return self.goto(self.steps.first)
# if accessing this class directly, redirect to the first step
if self.__class__ == ApplicationWizard:
return self.goto(self.steps.first)
forms = self.get_forms(use_post=True) forms = self.get_forms(use_post=True)
if self.is_valid(forms): if self.is_valid(forms):
# always save progress # always save progress

View file

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