mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-22 02:36:02 +02:00
Merge branch 'main' into za/1484-domain-manager-delete
This commit is contained in:
commit
25303c92f1
11 changed files with 469 additions and 294 deletions
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,24 +492,37 @@ 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()
|
||||||
|
|
||||||
|
|
||||||
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")
|
||||||
|
@ -464,13 +533,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")
|
||||||
|
@ -572,11 +653,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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
@ -149,8 +149,11 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
||||||
"""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():
|
||||||
|
@ -1440,6 +1443,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")
|
||||||
|
@ -1449,7 +1453,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(
|
||||||
|
@ -1478,7 +1486,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()
|
||||||
|
|
||||||
|
@ -1504,7 +1517,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()
|
||||||
|
|
||||||
|
@ -1524,11 +1542,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(
|
||||||
|
@ -1550,11 +1569,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(
|
||||||
|
@ -1588,11 +1608,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(
|
||||||
|
@ -1630,11 +1651,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(
|
||||||
|
@ -1669,15 +1691,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."
|
||||||
|
|
||||||
|
@ -1706,11 +1728,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."
|
||||||
|
|
||||||
|
@ -1724,7 +1747,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)
|
||||||
|
|
||||||
|
@ -1736,8 +1763,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
|
||||||
|
@ -1753,7 +1783,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)
|
||||||
|
@ -1808,6 +1842,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
|
||||||
|
@ -2143,8 +2178,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"],
|
||||||
|
@ -2490,9 +2527,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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue