mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-06-03 19:17:42 +02:00
Merge branch 'main' into dk/661-other-contacts
This commit is contained in:
commit
185c1f0cb6
15 changed files with 791 additions and 330 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()
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Generated by Django 4.2.7 on 2023-12-23 01:31
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("registrar", "0060_domain_deleted_domain_first_ready"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="domain",
|
||||||
|
name="security_contact_registry_id",
|
||||||
|
field=models.TextField(
|
||||||
|
editable=False,
|
||||||
|
help_text="Duplication of registry's security contact id for when registry unavailable",
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -31,7 +31,7 @@ from epplibwrapper import (
|
||||||
|
|
||||||
from registrar.models.utility.contact_error import ContactError, ContactErrorCodes
|
from registrar.models.utility.contact_error import ContactError, ContactErrorCodes
|
||||||
|
|
||||||
from django.db.models import DateField
|
from django.db.models import DateField, TextField
|
||||||
from .utility.domain_field import DomainField
|
from .utility.domain_field import DomainField
|
||||||
from .utility.domain_helper import DomainHelper
|
from .utility.domain_helper import DomainHelper
|
||||||
from .utility.time_stamped_model import TimeStampedModel
|
from .utility.time_stamped_model import TimeStampedModel
|
||||||
|
@ -974,6 +974,12 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
help_text=("Duplication of registry's expiration date saved for ease of reporting"),
|
help_text=("Duplication of registry's expiration date saved for ease of reporting"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
security_contact_registry_id = TextField(
|
||||||
|
null=True,
|
||||||
|
help_text=("Duplication of registry's security contact id for when registry unavailable"),
|
||||||
|
editable=False,
|
||||||
|
)
|
||||||
|
|
||||||
deleted = DateField(
|
deleted = DateField(
|
||||||
null=True,
|
null=True,
|
||||||
editable=False,
|
editable=False,
|
||||||
|
@ -1127,9 +1133,15 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
# Grab from cache
|
# Grab from cache
|
||||||
contacts = self._get_property(desired_property)
|
contacts = self._get_property(desired_property)
|
||||||
except KeyError as error:
|
except KeyError as error:
|
||||||
|
# if contact type is security, attempt to retrieve registry id
|
||||||
|
# for the security contact from domain.security_contact_registry_id
|
||||||
|
if contact_type_choice == PublicContact.ContactTypeChoices.SECURITY and self.security_contact_registry_id:
|
||||||
|
logger.info(f"Could not access registry, using fallback value of {self.security_contact_registry_id}")
|
||||||
|
contacts = {PublicContact.ContactTypeChoices.SECURITY: self.security_contact_registry_id}
|
||||||
|
else:
|
||||||
logger.error(f"Could not find {contact_type_choice}: {error}")
|
logger.error(f"Could not find {contact_type_choice}: {error}")
|
||||||
return None
|
return None
|
||||||
else:
|
|
||||||
cached_contact = self.get_contact_in_keys(contacts, contact_type_choice)
|
cached_contact = self.get_contact_in_keys(contacts, contact_type_choice)
|
||||||
if cached_contact is None:
|
if cached_contact is None:
|
||||||
# TODO - #1103
|
# TODO - #1103
|
||||||
|
@ -1630,6 +1642,8 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
self._update_hosts_and_contacts(cleaned, fetch_hosts, fetch_contacts)
|
self._update_hosts_and_contacts(cleaned, fetch_hosts, fetch_contacts)
|
||||||
if fetch_hosts:
|
if fetch_hosts:
|
||||||
self._update_hosts_and_ips_in_db(cleaned)
|
self._update_hosts_and_ips_in_db(cleaned)
|
||||||
|
if fetch_contacts:
|
||||||
|
self._update_security_contact_in_db(cleaned)
|
||||||
self._update_dates(cleaned)
|
self._update_dates(cleaned)
|
||||||
|
|
||||||
self._cache = cleaned
|
self._cache = cleaned
|
||||||
|
@ -1739,6 +1753,23 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
for ip_address in cleaned_ips:
|
for ip_address in cleaned_ips:
|
||||||
HostIP.objects.get_or_create(address=ip_address, host=host_in_db)
|
HostIP.objects.get_or_create(address=ip_address, host=host_in_db)
|
||||||
|
|
||||||
|
def _update_security_contact_in_db(self, cleaned):
|
||||||
|
"""Update security contact registry id in database if retrieved from registry.
|
||||||
|
If no value is retrieved from registry, set to empty string in db.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
self: the domain to be updated with security from cleaned
|
||||||
|
cleaned: dict containing contact registry ids. Security contact is of type
|
||||||
|
PublicContact.ContactTypeChoices.SECURITY
|
||||||
|
"""
|
||||||
|
cleaned_contacts = cleaned["contacts"]
|
||||||
|
security_contact_registry_id = ""
|
||||||
|
security_contact = cleaned_contacts[PublicContact.ContactTypeChoices.SECURITY]
|
||||||
|
if security_contact:
|
||||||
|
security_contact_registry_id = security_contact
|
||||||
|
self.security_contact_registry_id = security_contact_registry_id
|
||||||
|
self.save()
|
||||||
|
|
||||||
def _update_dates(self, cleaned):
|
def _update_dates(self, cleaned):
|
||||||
"""Update dates (expiration and creation) from cleaned"""
|
"""Update dates (expiration and creation) from cleaned"""
|
||||||
requires_save = False
|
requires_save = False
|
||||||
|
|
|
@ -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(
|
||||||
|
|
34
src/registrar/templates/application_intro.html
Normal file
34
src/registrar/templates/application_intro.html
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static form_helpers url_helpers %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<main id="main-content" class="grid-container">
|
||||||
|
<div class="grid-col desktop:grid-offset-2 desktop:grid-col-8">
|
||||||
|
|
||||||
|
<form class="usa-form usa-form--extra-large" method="post" novalidate>
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<h1>You’re about to start your .gov domain request.</h1>
|
||||||
|
<p>You don’t have to complete the process in one session. You can save what you enter and come back to it when you’re ready.</p>
|
||||||
|
<p>We’ll use the information you provide to verify your organization’s eligibility for a .gov domain. We’ll also verify that the domain you request meets our guidelines.</p>
|
||||||
|
<h2>Time to complete the form</h2>
|
||||||
|
<p>If you have <a href="{% public_site_url 'domains/before/#information-you%E2%80%99ll-need-to-complete-the-domain-request-form' %}" target="_blank" class="usa-link">all the information you need</a>,
|
||||||
|
completing your domain request might take around 15 minutes.</p>
|
||||||
|
<p><a href="{% public_site_url 'contact/' %}" target="_blank" rel="noopener noreferrer" class="usa-link">Contact us if you need help with your request</a>.</p>
|
||||||
|
|
||||||
|
{% block form_buttons %}
|
||||||
|
<div class="stepnav">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
name="submit_button"
|
||||||
|
value="intro_acknowledge"
|
||||||
|
class="usa-button"
|
||||||
|
>Continue</button>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{% endblock %}
|
|
@ -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,6 +59,9 @@ 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)
|
||||||
|
mock_client = MockSESClient()
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||||
|
with less_console_noise():
|
||||||
application.approve()
|
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,10 +425,8 @@ 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():
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
|
||||||
# Create a sample application
|
# Create a sample application
|
||||||
application = completed_application()
|
application = completed_application()
|
||||||
|
|
||||||
|
@ -437,8 +440,8 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
self.admin.save_model(request, application, form=None, change=True)
|
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,10 +463,8 @@ 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():
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
|
||||||
# Create a sample application
|
# Create a sample application
|
||||||
application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED)
|
||||||
|
|
||||||
|
@ -478,8 +478,8 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
self.admin.save_model(request, application, form=None, change=True)
|
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,10 +501,8 @@ 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():
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
|
||||||
# Create a sample application
|
# Create a sample application
|
||||||
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
|
|
||||||
|
@ -519,8 +516,8 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
self.admin.save_model(request, application, form=None, change=True)
|
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,6 +545,8 @@ 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))
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
# Modify the application's property
|
# Modify the application's property
|
||||||
application.status = DomainApplication.ApplicationStatus.APPROVED
|
application.status = DomainApplication.ApplicationStatus.APPROVED
|
||||||
|
|
||||||
|
@ -563,10 +562,8 @@ 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():
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
|
||||||
# Create a sample application
|
# Create a sample application
|
||||||
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
|
|
||||||
|
@ -580,8 +577,8 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
self.admin.save_model(request, application, form=None, change=True)
|
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,10 +600,8 @@ 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():
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
|
||||||
# Create a sample application
|
# Create a sample application
|
||||||
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
|
|
||||||
|
@ -621,8 +615,8 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
self.admin.save_model(request, application, form=None, change=True)
|
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,6 +644,8 @@ 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))
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
# Modify the application's property
|
# Modify the application's property
|
||||||
application.status = DomainApplication.ApplicationStatus.INELIGIBLE
|
application.status = DomainApplication.ApplicationStatus.INELIGIBLE
|
||||||
|
|
||||||
|
@ -661,6 +657,8 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
|
|
||||||
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)
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
application.creator.status = User.RESTRICTED
|
application.creator.status = User.RESTRICTED
|
||||||
application.creator.save()
|
application.creator.save()
|
||||||
|
|
||||||
|
@ -740,6 +738,8 @@ 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)
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
application.creator.status = User.RESTRICTED
|
application.creator.status = User.RESTRICTED
|
||||||
application.creator.save()
|
application.creator.save()
|
||||||
|
|
||||||
|
@ -763,6 +763,8 @@ 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)
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
application.creator.status = User.RESTRICTED
|
application.creator.status = User.RESTRICTED
|
||||||
application.creator.save()
|
application.creator.save()
|
||||||
|
|
||||||
|
@ -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,6 +803,8 @@ 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"))
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
# Simulate saving the model
|
# Simulate saving the model
|
||||||
application.status = DomainApplication.ApplicationStatus.REJECTED
|
application.status = DomainApplication.ApplicationStatus.REJECTED
|
||||||
self.admin.save_model(request, application, None, True)
|
self.admin.save_model(request, application, None, True)
|
||||||
|
@ -831,7 +836,8 @@ 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):
|
||||||
|
with less_console_noise():
|
||||||
# Simulate saving the model
|
# Simulate saving the model
|
||||||
application.status = DomainApplication.ApplicationStatus.REJECTED
|
application.status = DomainApplication.ApplicationStatus.REJECTED
|
||||||
self.admin.save_model(request, application, None, True)
|
self.admin.save_model(request, application, None, True)
|
||||||
|
@ -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,6 +20,7 @@ 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):
|
||||||
|
with less_console_noise():
|
||||||
application.submit()
|
application.submit()
|
||||||
|
|
||||||
# check that an email was sent
|
# check that an email was sent
|
||||||
|
@ -56,6 +57,7 @@ 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):
|
||||||
|
with less_console_noise():
|
||||||
application.submit()
|
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"]
|
||||||
|
@ -68,6 +70,7 @@ 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):
|
||||||
|
with less_console_noise():
|
||||||
application.submit()
|
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"]
|
||||||
|
@ -81,6 +84,7 @@ 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):
|
||||||
|
with less_console_noise():
|
||||||
application.submit()
|
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"]
|
||||||
|
@ -94,6 +98,7 @@ 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):
|
||||||
|
with less_console_noise():
|
||||||
application.submit()
|
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"]
|
||||||
|
@ -106,6 +111,7 @@ 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):
|
||||||
|
with less_console_noise():
|
||||||
application.submit()
|
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"]
|
||||||
|
@ -118,6 +124,7 @@ 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):
|
||||||
|
with less_console_noise():
|
||||||
application.submit()
|
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"]
|
||||||
|
@ -130,6 +137,7 @@ 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):
|
||||||
|
with less_console_noise():
|
||||||
application.submit()
|
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"]
|
||||||
|
@ -142,6 +150,7 @@ 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):
|
||||||
|
with less_console_noise():
|
||||||
application.submit()
|
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"]
|
||||||
|
@ -154,6 +163,7 @@ 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):
|
||||||
|
with less_console_noise():
|
||||||
application.submit()
|
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"]
|
||||||
|
@ -165,6 +175,7 @@ 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):
|
||||||
|
with less_console_noise():
|
||||||
application.submit()
|
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"]
|
||||||
|
|
|
@ -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,6 +134,9 @@ 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 boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
# can't submit an application with a null domain name
|
# can't submit an application with a null domain name
|
||||||
application.submit()
|
application.submit()
|
||||||
|
@ -138,7 +145,10 @@ class TestDomainApplication(TestCase):
|
||||||
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 boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
application.submit()
|
application.submit()
|
||||||
self.assertEqual(application.status, application.ApplicationStatus.SUBMITTED)
|
self.assertEqual(application.status, application.ApplicationStatus.SUBMITTED)
|
||||||
|
@ -154,6 +164,9 @@ class TestDomainApplication(TestCase):
|
||||||
submitter=contact,
|
submitter=contact,
|
||||||
)
|
)
|
||||||
application.save()
|
application.save()
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
application.submit()
|
application.submit()
|
||||||
|
|
||||||
# check to see if an email was sent
|
# check to see if an email was sent
|
||||||
|
@ -179,6 +192,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.withdrawn_application, TransitionNotAllowed),
|
(self.withdrawn_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for application, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(application=application, exception_type=exception_type):
|
||||||
try:
|
try:
|
||||||
|
@ -197,6 +212,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.ineligible_application, TransitionNotAllowed),
|
(self.ineligible_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for application, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(application=application, exception_type=exception_type):
|
||||||
with self.assertRaises(exception_type):
|
with self.assertRaises(exception_type):
|
||||||
|
@ -214,6 +231,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.ineligible_application, TransitionNotAllowed),
|
(self.ineligible_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for application, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(application=application, exception_type=exception_type):
|
||||||
try:
|
try:
|
||||||
|
@ -231,6 +250,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.withdrawn_application, TransitionNotAllowed),
|
(self.withdrawn_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for application, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(application=application, exception_type=exception_type):
|
||||||
with self.assertRaises(exception_type):
|
with self.assertRaises(exception_type):
|
||||||
|
@ -247,6 +268,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.ineligible_application, TransitionNotAllowed),
|
(self.ineligible_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for application, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(application=application, exception_type=exception_type):
|
||||||
try:
|
try:
|
||||||
|
@ -265,6 +288,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.withdrawn_application, TransitionNotAllowed),
|
(self.withdrawn_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for application, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(application=application, exception_type=exception_type):
|
||||||
with self.assertRaises(exception_type):
|
with self.assertRaises(exception_type):
|
||||||
|
@ -281,6 +306,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.rejected_application, TransitionNotAllowed),
|
(self.rejected_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for application, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(application=application, exception_type=exception_type):
|
||||||
try:
|
try:
|
||||||
|
@ -288,6 +315,19 @@ class TestDomainApplication(TestCase):
|
||||||
except TransitionNotAllowed:
|
except TransitionNotAllowed:
|
||||||
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
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):
|
||||||
"""
|
"""
|
||||||
Test that calling action_needed against transition rules raises TransitionNotAllowed.
|
Test that calling action_needed against transition rules raises TransitionNotAllowed.
|
||||||
|
@ -299,6 +339,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.ineligible_application, TransitionNotAllowed),
|
(self.ineligible_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for application, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(application=application, exception_type=exception_type):
|
||||||
with self.assertRaises(exception_type):
|
with self.assertRaises(exception_type):
|
||||||
|
@ -314,6 +356,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.action_needed_application, TransitionNotAllowed),
|
(self.action_needed_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for application, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(application=application, exception_type=exception_type):
|
||||||
try:
|
try:
|
||||||
|
@ -333,6 +377,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.ineligible_application, TransitionNotAllowed),
|
(self.ineligible_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for application, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(application=application, exception_type=exception_type):
|
||||||
with self.assertRaises(exception_type):
|
with self.assertRaises(exception_type):
|
||||||
|
@ -348,6 +394,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.approved_application, TransitionNotAllowed),
|
(self.approved_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for application, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(application=application, exception_type=exception_type):
|
||||||
try:
|
try:
|
||||||
|
@ -367,6 +415,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.ineligible_application, TransitionNotAllowed),
|
(self.ineligible_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for application, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(application=application, exception_type=exception_type):
|
||||||
with self.assertRaises(exception_type):
|
with self.assertRaises(exception_type):
|
||||||
|
@ -383,6 +433,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.rejected_application, TransitionNotAllowed),
|
(self.rejected_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for application, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(application=application, exception_type=exception_type):
|
||||||
try:
|
try:
|
||||||
|
@ -401,6 +453,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.ineligible_application, TransitionNotAllowed),
|
(self.ineligible_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for application, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(application=application, exception_type=exception_type):
|
||||||
with self.assertRaises(exception_type):
|
with self.assertRaises(exception_type):
|
||||||
|
@ -418,6 +472,8 @@ 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
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
# Use patch to temporarily replace is_active with the custom implementation
|
# Use patch to temporarily replace is_active with the custom implementation
|
||||||
with patch.object(Domain, "is_active", custom_is_active):
|
with patch.object(Domain, "is_active", custom_is_active):
|
||||||
# Now, when you call is_active on Domain, it will return True
|
# Now, when you call is_active on Domain, it will return True
|
||||||
|
@ -436,6 +492,8 @@ 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
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
# Use patch to temporarily replace is_active with the custom implementation
|
# Use patch to temporarily replace is_active with the custom implementation
|
||||||
with patch.object(Domain, "is_active", custom_is_active):
|
with patch.object(Domain, "is_active", custom_is_active):
|
||||||
# Now, when you call is_active on Domain, it will return True
|
# Now, when you call is_active on Domain, it will return True
|
||||||
|
@ -466,13 +524,24 @@ class TestDomainApplication(TestCase):
|
||||||
|
|
||||||
|
|
||||||
class TestPermissions(TestCase):
|
class TestPermissions(TestCase):
|
||||||
|
|
||||||
"""Test the User-Domain-Role connection."""
|
"""Test the User-Domain-Role connection."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.mock_client = MockSESClient()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super().tearDown()
|
||||||
|
self.mock_client.EMAILS_SENT.clear()
|
||||||
|
|
||||||
|
@boto3_mocking.patching
|
||||||
def test_approval_creates_role(self):
|
def test_approval_creates_role(self):
|
||||||
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
|
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
|
||||||
user, _ = User.objects.get_or_create()
|
user, _ = User.objects.get_or_create()
|
||||||
application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain)
|
application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain)
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
# skip using the submit method
|
# skip using the submit method
|
||||||
application.status = DomainApplication.ApplicationStatus.SUBMITTED
|
application.status = DomainApplication.ApplicationStatus.SUBMITTED
|
||||||
application.approve()
|
application.approve()
|
||||||
|
@ -486,10 +555,22 @@ 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)
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
# skip using the submit method
|
# skip using the submit method
|
||||||
application.status = DomainApplication.ApplicationStatus.SUBMITTED
|
application.status = DomainApplication.ApplicationStatus.SUBMITTED
|
||||||
application.approve()
|
application.approve()
|
||||||
|
@ -594,6 +675,7 @@ 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:
|
||||||
|
with less_console_noise():
|
||||||
DomainInvitation.objects.get_or_create(email=caps_email, domain=self.domain)
|
DomainInvitation.objects.get_or_create(email=caps_email, domain=self.domain)
|
||||||
self.user.check_domain_invitations_on_login()
|
self.user.check_domain_invitations_on_login()
|
||||||
# if check_domain_invitations_on_login properly matches exactly one
|
# if check_domain_invitations_on_login properly matches exactly one
|
||||||
|
|
|
@ -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,6 +264,10 @@ 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)
|
||||||
|
|
||||||
|
mock_client = MockSESClient()
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||||
|
with less_console_noise():
|
||||||
# skip using the submit method
|
# skip using the submit method
|
||||||
application.status = DomainApplication.ApplicationStatus.SUBMITTED
|
application.status = DomainApplication.ApplicationStatus.SUBMITTED
|
||||||
# transition to approve state
|
# transition to approve state
|
||||||
|
@ -739,6 +745,50 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
self.mockedSendFunction.assert_has_calls(expected_calls, any_order=True)
|
self.mockedSendFunction.assert_has_calls(expected_calls, any_order=True)
|
||||||
self.assertEqual(PublicContact.objects.filter(domain=self.domain).count(), 1)
|
self.assertEqual(PublicContact.objects.filter(domain=self.domain).count(), 1)
|
||||||
|
|
||||||
|
def test_security_email_returns_on_registry_error(self):
|
||||||
|
"""
|
||||||
|
Scenario: Security email previously set through EPP and stored in registrar's database.
|
||||||
|
Registry is unavailable and throws exception when attempting to build cache from
|
||||||
|
registry. Security email retrieved from database.
|
||||||
|
"""
|
||||||
|
# Use self.domain_contact which has been initialized with existing contacts, including securityContact
|
||||||
|
|
||||||
|
# call get_security_email to initially set the security_contact_registry_id in the domain model
|
||||||
|
self.domain_contact.get_security_email()
|
||||||
|
# invalidate the cache so the next time get_security_email is called, it has to attempt to populate cache
|
||||||
|
self.domain_contact._invalidate_cache()
|
||||||
|
|
||||||
|
# mock that registry throws an error on the EPP send
|
||||||
|
def side_effect(_request, cleaned):
|
||||||
|
raise RegistryError(code=ErrorCode.COMMAND_FAILED)
|
||||||
|
|
||||||
|
patcher = patch("registrar.models.domain.registry.send")
|
||||||
|
mocked_send = patcher.start()
|
||||||
|
mocked_send.side_effect = side_effect
|
||||||
|
|
||||||
|
# when get_security_email is called, the registry error will force the security contact
|
||||||
|
# to be retrieved using the security_contact_registry_id in the domain model
|
||||||
|
security_email = self.domain_contact.get_security_email()
|
||||||
|
|
||||||
|
# assert that the proper security contact was retrieved by testing the email matches expected value
|
||||||
|
self.assertEqual(security_email, "security@mail.gov")
|
||||||
|
patcher.stop()
|
||||||
|
|
||||||
|
def test_security_email_stored_on_fetch_cache(self):
|
||||||
|
"""
|
||||||
|
Scenario: Security email is stored in db when security contact is retrieved from fetch_cache.
|
||||||
|
Verify the success of this by asserting get_or_create calls to db.
|
||||||
|
The mocked data for the EPP calls for the freeman.gov domain returns a security
|
||||||
|
contact with registry id of securityContact when InfoContact is called
|
||||||
|
"""
|
||||||
|
# Use self.domain_contact which has been initialized with existing contacts, including securityContact
|
||||||
|
|
||||||
|
# force fetch_cache to be called, which will return above documented mocked hosts
|
||||||
|
self.domain_contact.get_security_email()
|
||||||
|
|
||||||
|
# assert that the security_contact_registry_id in the db matches "securityContact"
|
||||||
|
self.assertEqual(self.domain_contact.security_contact_registry_id, "securityContact")
|
||||||
|
|
||||||
def test_not_disclosed_on_other_contacts(self):
|
def test_not_disclosed_on_other_contacts(self):
|
||||||
"""
|
"""
|
||||||
Scenario: Registrant creates a new domain with multiple contacts
|
Scenario: Registrant creates a new domain with multiple contacts
|
||||||
|
|
|
@ -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,10 +707,13 @@ 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.
|
||||||
|
mock_client = MockSESClient()
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||||
with patch(
|
with patch(
|
||||||
"registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa
|
"registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa
|
||||||
return_value=True,
|
return_value=True,
|
||||||
):
|
):
|
||||||
|
with patch("registrar.utility.email.send_templated_email", return_value=None):
|
||||||
call_command(
|
call_command(
|
||||||
"master_domain_migrations",
|
"master_domain_migrations",
|
||||||
runMigrations=True,
|
runMigrations=True,
|
||||||
|
@ -717,6 +721,7 @@ class TestMigrations(TestCase):
|
||||||
migrationJSON=self.migration_json_filename,
|
migrationJSON=self.migration_json_filename,
|
||||||
disablePrompts=True,
|
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,6 +1033,9 @@ 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()
|
||||||
|
|
||||||
|
mock_client = MockSESClient()
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||||
# also have to re-point the logging handlers to output_stream
|
# also have to re-point the logging handlers to output_stream
|
||||||
with less_console_noise(output_stream):
|
with less_console_noise(output_stream):
|
||||||
call_command("send_domain_invitations", "testuser@gmail.com", stdout=output_stream)
|
call_command("send_domain_invitations", "testuser@gmail.com", stdout=output_stream)
|
||||||
|
@ -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,6 +1055,9 @@ 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()
|
||||||
|
|
||||||
|
mock_client = MockSESClient()
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||||
# also have to re-point the logging handlers to output_stream
|
# also have to re-point the logging handlers to output_stream
|
||||||
with less_console_noise(output_stream):
|
with less_console_noise(output_stream):
|
||||||
call_command(
|
call_command(
|
||||||
|
|
|
@ -5,7 +5,7 @@ from django.conf import settings
|
||||||
from django.test import Client, TestCase
|
from django.test import Client, TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from .common import MockEppLib, completed_application, create_user # type: ignore
|
from .common import MockEppLib, MockSESClient, completed_application, create_user # type: ignore
|
||||||
from django_webtest import WebTest # type: ignore
|
from django_webtest import WebTest # type: ignore
|
||||||
import boto3_mocking # type: ignore
|
import boto3_mocking # type: ignore
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ class LoggedInTests(TestWithUser):
|
||||||
response = self.client.get("/register/", follow=True)
|
response = self.client.get("/register/", follow=True)
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
response,
|
response,
|
||||||
"What kind of U.S.-based government organization do you represent?",
|
"You’re about to start your .gov domain request.",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_domain_application_form_with_ineligible_user(self):
|
def test_domain_application_form_with_ineligible_user(self):
|
||||||
|
@ -139,24 +139,70 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
||||||
self.app.set_user(self.user.username)
|
self.app.set_user(self.user.username)
|
||||||
self.TITLES = ApplicationWizard.TITLES
|
self.TITLES = ApplicationWizard.TITLES
|
||||||
|
|
||||||
|
def test_application_form_intro_acknowledgement(self):
|
||||||
|
"""Tests that user is presented with intro acknowledgement page"""
|
||||||
|
intro_page = self.app.get(reverse("application:"))
|
||||||
|
self.assertContains(intro_page, "You’re about to start your .gov domain request")
|
||||||
|
|
||||||
|
def test_application_form_intro_is_skipped_when_edit_access(self):
|
||||||
|
"""Tests that user is NOT presented with intro acknowledgement page when accessed through 'edit'"""
|
||||||
|
completed_application(status=DomainApplication.ApplicationStatus.STARTED, user=self.user)
|
||||||
|
home_page = self.app.get("/")
|
||||||
|
self.assertContains(home_page, "city.gov")
|
||||||
|
# click the "Edit" link
|
||||||
|
detail_page = home_page.click("Edit", index=0)
|
||||||
|
# Check that the response is a redirect
|
||||||
|
self.assertEqual(detail_page.status_code, 302)
|
||||||
|
# You can access the 'Location' header to get the redirect URL
|
||||||
|
redirect_url = detail_page.url
|
||||||
|
self.assertEqual(redirect_url, "/register/organization_type/")
|
||||||
|
|
||||||
def test_application_form_empty_submit(self):
|
def test_application_form_empty_submit(self):
|
||||||
# 302 redirect to the first form
|
"""Tests empty submit on the first page after the acknowledgement page"""
|
||||||
page = self.app.get(reverse("application:")).follow()
|
intro_page = self.app.get(reverse("application:"))
|
||||||
|
# django-webtest does not handle cookie-based sessions well because it keeps
|
||||||
|
# resetting the session key on each new request, thus destroying the concept
|
||||||
|
# of a "session". We are going to do it manually, saving the session ID here
|
||||||
|
# and then setting the cookie on each request.
|
||||||
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
|
intro_form = intro_page.forms[0]
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
intro_result = intro_form.submit()
|
||||||
|
|
||||||
|
# follow first redirect
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
type_page = intro_result.follow()
|
||||||
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
# submitting should get back the same page if the required field is empty
|
# submitting should get back the same page if the required field is empty
|
||||||
result = page.forms[0].submit()
|
result = type_page.forms[0].submit()
|
||||||
self.assertIn("What kind of U.S.-based government organization do you represent?", result)
|
self.assertIn("What kind of U.S.-based government organization do you represent?", result)
|
||||||
|
|
||||||
def test_application_multiple_applications_exist(self):
|
def test_application_multiple_applications_exist(self):
|
||||||
"""Test that an info message appears when user has multiple applications already"""
|
"""Test that an info message appears when user has multiple applications already"""
|
||||||
# create and submit an application
|
# create and submit an application
|
||||||
application = completed_application(user=self.user)
|
application = completed_application(user=self.user)
|
||||||
|
mock_client = MockSESClient()
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||||
|
with less_console_noise():
|
||||||
application.submit()
|
application.submit()
|
||||||
application.save()
|
application.save()
|
||||||
|
|
||||||
# now, attempt to create another one
|
# now, attempt to create another one
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
page = self.app.get("/register/").follow()
|
intro_page = self.app.get(reverse("application:"))
|
||||||
self.assertContains(page, "You cannot submit this request yet")
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
intro_form = intro_page.forms[0]
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
intro_result = intro_form.submit()
|
||||||
|
|
||||||
|
# follow first redirect
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
type_page = intro_result.follow()
|
||||||
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
|
self.assertContains(type_page, "You cannot submit this request yet")
|
||||||
|
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_application_form_submission(self):
|
def test_application_form_submission(self):
|
||||||
|
@ -175,13 +221,22 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
||||||
SKIPPED_PAGES = 3
|
SKIPPED_PAGES = 3
|
||||||
num_pages = len(self.TITLES) - SKIPPED_PAGES
|
num_pages = len(self.TITLES) - SKIPPED_PAGES
|
||||||
|
|
||||||
type_page = self.app.get(reverse("application:")).follow()
|
intro_page = self.app.get(reverse("application:"))
|
||||||
# django-webtest does not handle cookie-based sessions well because it keeps
|
# django-webtest does not handle cookie-based sessions well because it keeps
|
||||||
# resetting the session key on each new request, thus destroying the concept
|
# resetting the session key on each new request, thus destroying the concept
|
||||||
# of a "session". We are going to do it manually, saving the session ID here
|
# of a "session". We are going to do it manually, saving the session ID here
|
||||||
# and then setting the cookie on each request.
|
# and then setting the cookie on each request.
|
||||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
|
intro_form = intro_page.forms[0]
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
intro_result = intro_form.submit()
|
||||||
|
|
||||||
|
# follow first redirect
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
type_page = intro_result.follow()
|
||||||
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
# ---- TYPE PAGE ----
|
# ---- TYPE PAGE ----
|
||||||
type_form = type_page.forms[0]
|
type_form = type_page.forms[0]
|
||||||
type_form["organization_type-organization_type"] = "federal"
|
type_form["organization_type-organization_type"] = "federal"
|
||||||
|
@ -543,13 +598,22 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
||||||
|
|
||||||
def test_application_form_conditional_federal(self):
|
def test_application_form_conditional_federal(self):
|
||||||
"""Federal branch question is shown for federal organizations."""
|
"""Federal branch question is shown for federal organizations."""
|
||||||
type_page = self.app.get(reverse("application:")).follow()
|
intro_page = self.app.get(reverse("application:"))
|
||||||
# django-webtest does not handle cookie-based sessions well because it keeps
|
# django-webtest does not handle cookie-based sessions well because it keeps
|
||||||
# resetting the session key on each new request, thus destroying the concept
|
# resetting the session key on each new request, thus destroying the concept
|
||||||
# of a "session". We are going to do it manually, saving the session ID here
|
# of a "session". We are going to do it manually, saving the session ID here
|
||||||
# and then setting the cookie on each request.
|
# and then setting the cookie on each request.
|
||||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
|
intro_form = intro_page.forms[0]
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
intro_result = intro_form.submit()
|
||||||
|
|
||||||
|
# follow first redirect
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
type_page = intro_result.follow()
|
||||||
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
# ---- TYPE PAGE ----
|
# ---- TYPE PAGE ----
|
||||||
|
|
||||||
# the conditional step titles shouldn't appear initially
|
# the conditional step titles shouldn't appear initially
|
||||||
|
@ -589,13 +653,22 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
||||||
|
|
||||||
def test_application_form_conditional_elections(self):
|
def test_application_form_conditional_elections(self):
|
||||||
"""Election question is shown for other organizations."""
|
"""Election question is shown for other organizations."""
|
||||||
type_page = self.app.get(reverse("application:")).follow()
|
intro_page = self.app.get(reverse("application:"))
|
||||||
# django-webtest does not handle cookie-based sessions well because it keeps
|
# django-webtest does not handle cookie-based sessions well because it keeps
|
||||||
# resetting the session key on each new request, thus destroying the concept
|
# resetting the session key on each new request, thus destroying the concept
|
||||||
# of a "session". We are going to do it manually, saving the session ID here
|
# of a "session". We are going to do it manually, saving the session ID here
|
||||||
# and then setting the cookie on each request.
|
# and then setting the cookie on each request.
|
||||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
|
intro_form = intro_page.forms[0]
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
intro_result = intro_form.submit()
|
||||||
|
|
||||||
|
# follow first redirect
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
type_page = intro_result.follow()
|
||||||
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
# ---- TYPE PAGE ----
|
# ---- TYPE PAGE ----
|
||||||
|
|
||||||
# the conditional step titles shouldn't appear initially
|
# the conditional step titles shouldn't appear initially
|
||||||
|
@ -634,13 +707,22 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
||||||
|
|
||||||
def test_application_form_section_skipping(self):
|
def test_application_form_section_skipping(self):
|
||||||
"""Can skip forward and back in sections"""
|
"""Can skip forward and back in sections"""
|
||||||
type_page = self.app.get(reverse("application:")).follow()
|
intro_page = self.app.get(reverse("application:"))
|
||||||
# django-webtest does not handle cookie-based sessions well because it keeps
|
# django-webtest does not handle cookie-based sessions well because it keeps
|
||||||
# resetting the session key on each new request, thus destroying the concept
|
# resetting the session key on each new request, thus destroying the concept
|
||||||
# of a "session". We are going to do it manually, saving the session ID here
|
# of a "session". We are going to do it manually, saving the session ID here
|
||||||
# and then setting the cookie on each request.
|
# and then setting the cookie on each request.
|
||||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
|
intro_form = intro_page.forms[0]
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
intro_result = intro_form.submit()
|
||||||
|
|
||||||
|
# follow first redirect
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
type_page = intro_result.follow()
|
||||||
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
type_form = type_page.forms[0]
|
type_form = type_page.forms[0]
|
||||||
type_form["organization_type-organization_type"] = "federal"
|
type_form["organization_type-organization_type"] = "federal"
|
||||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
@ -662,13 +744,22 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
||||||
|
|
||||||
def test_application_form_nonfederal(self):
|
def test_application_form_nonfederal(self):
|
||||||
"""Non-federal organizations don't have to provide their federal agency."""
|
"""Non-federal organizations don't have to provide their federal agency."""
|
||||||
type_page = self.app.get(reverse("application:")).follow()
|
intro_page = self.app.get(reverse("application:"))
|
||||||
# django-webtest does not handle cookie-based sessions well because it keeps
|
# django-webtest does not handle cookie-based sessions well because it keeps
|
||||||
# resetting the session key on each new request, thus destroying the concept
|
# resetting the session key on each new request, thus destroying the concept
|
||||||
# of a "session". We are going to do it manually, saving the session ID here
|
# of a "session". We are going to do it manually, saving the session ID here
|
||||||
# and then setting the cookie on each request.
|
# and then setting the cookie on each request.
|
||||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
|
intro_form = intro_page.forms[0]
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
intro_result = intro_form.submit()
|
||||||
|
|
||||||
|
# follow first redirect
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
type_page = intro_result.follow()
|
||||||
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
type_form = type_page.forms[0]
|
type_form = type_page.forms[0]
|
||||||
type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.INTERSTATE
|
type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.INTERSTATE
|
||||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
@ -698,13 +789,22 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
||||||
|
|
||||||
def test_application_about_your_organization_special(self):
|
def test_application_about_your_organization_special(self):
|
||||||
"""Special districts have to answer an additional question."""
|
"""Special districts have to answer an additional question."""
|
||||||
type_page = self.app.get(reverse("application:")).follow()
|
intro_page = self.app.get(reverse("application:"))
|
||||||
# django-webtest does not handle cookie-based sessions well because it keeps
|
# django-webtest does not handle cookie-based sessions well because it keeps
|
||||||
# resetting the session key on each new request, thus destroying the concept
|
# resetting the session key on each new request, thus destroying the concept
|
||||||
# of a "session". We are going to do it manually, saving the session ID here
|
# of a "session". We are going to do it manually, saving the session ID here
|
||||||
# and then setting the cookie on each request.
|
# and then setting the cookie on each request.
|
||||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
|
intro_form = intro_page.forms[0]
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
intro_result = intro_form.submit()
|
||||||
|
|
||||||
|
# follow first redirect
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
type_page = intro_result.follow()
|
||||||
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
type_form = type_page.forms[0]
|
type_form = type_page.forms[0]
|
||||||
type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.SPECIAL_DISTRICT
|
type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.SPECIAL_DISTRICT
|
||||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
@ -1068,13 +1168,22 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
||||||
|
|
||||||
def test_application_about_your_organiztion_interstate(self):
|
def test_application_about_your_organiztion_interstate(self):
|
||||||
"""Special districts have to answer an additional question."""
|
"""Special districts have to answer an additional question."""
|
||||||
type_page = self.app.get(reverse("application:")).follow()
|
intro_page = self.app.get(reverse("application:"))
|
||||||
# django-webtest does not handle cookie-based sessions well because it keeps
|
# django-webtest does not handle cookie-based sessions well because it keeps
|
||||||
# resetting the session key on each new request, thus destroying the concept
|
# resetting the session key on each new request, thus destroying the concept
|
||||||
# of a "session". We are going to do it manually, saving the session ID here
|
# of a "session". We are going to do it manually, saving the session ID here
|
||||||
# and then setting the cookie on each request.
|
# and then setting the cookie on each request.
|
||||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
|
intro_form = intro_page.forms[0]
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
intro_result = intro_form.submit()
|
||||||
|
|
||||||
|
# follow first redirect
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
type_page = intro_result.follow()
|
||||||
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
type_form = type_page.forms[0]
|
type_form = type_page.forms[0]
|
||||||
type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.INTERSTATE
|
type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.INTERSTATE
|
||||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
@ -1087,12 +1196,22 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
||||||
|
|
||||||
def test_application_tribal_government(self):
|
def test_application_tribal_government(self):
|
||||||
"""Tribal organizations have to answer an additional question."""
|
"""Tribal organizations have to answer an additional question."""
|
||||||
type_page = self.app.get(reverse("application:")).follow()
|
intro_page = self.app.get(reverse("application:"))
|
||||||
# django-webtest does not handle cookie-based sessions well because it keeps
|
# django-webtest does not handle cookie-based sessions well because it keeps
|
||||||
# resetting the session key on each new request, thus destroying the concept
|
# resetting the session key on each new request, thus destroying the concept
|
||||||
# of a "session". We are going to do it manually, saving the session ID here
|
# of a "session". We are going to do it manually, saving the session ID here
|
||||||
# and then setting the cookie on each request.
|
# and then setting the cookie on each request.
|
||||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
|
intro_form = intro_page.forms[0]
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
intro_result = intro_form.submit()
|
||||||
|
|
||||||
|
# follow first redirect
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
type_page = intro_result.follow()
|
||||||
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
type_form = type_page.forms[0]
|
type_form = type_page.forms[0]
|
||||||
type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.TRIBAL
|
type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.TRIBAL
|
||||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
@ -1107,13 +1226,22 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
||||||
self.assertContains(tribal_government_page, self.TITLES[Step.TRIBAL_GOVERNMENT])
|
self.assertContains(tribal_government_page, self.TITLES[Step.TRIBAL_GOVERNMENT])
|
||||||
|
|
||||||
def test_application_ao_dynamic_text(self):
|
def test_application_ao_dynamic_text(self):
|
||||||
type_page = self.app.get(reverse("application:")).follow()
|
intro_page = self.app.get(reverse("application:"))
|
||||||
# django-webtest does not handle cookie-based sessions well because it keeps
|
# django-webtest does not handle cookie-based sessions well because it keeps
|
||||||
# resetting the session key on each new request, thus destroying the concept
|
# resetting the session key on each new request, thus destroying the concept
|
||||||
# of a "session". We are going to do it manually, saving the session ID here
|
# of a "session". We are going to do it manually, saving the session ID here
|
||||||
# and then setting the cookie on each request.
|
# and then setting the cookie on each request.
|
||||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
|
intro_form = intro_page.forms[0]
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
intro_result = intro_form.submit()
|
||||||
|
|
||||||
|
# follow first redirect
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
type_page = intro_result.follow()
|
||||||
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
# ---- TYPE PAGE ----
|
# ---- TYPE PAGE ----
|
||||||
type_form = type_page.forms[0]
|
type_form = type_page.forms[0]
|
||||||
type_form["organization_type-organization_type"] = "federal"
|
type_form["organization_type-organization_type"] = "federal"
|
||||||
|
@ -1169,12 +1297,22 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
||||||
self.assertContains(ao_page, "Domain requests from cities")
|
self.assertContains(ao_page, "Domain requests from cities")
|
||||||
|
|
||||||
def test_application_dotgov_domain_dynamic_text(self):
|
def test_application_dotgov_domain_dynamic_text(self):
|
||||||
type_page = self.app.get(reverse("application:")).follow()
|
intro_page = self.app.get(reverse("application:"))
|
||||||
# django-webtest does not handle cookie-based sessions well because it keeps
|
# django-webtest does not handle cookie-based sessions well because it keeps
|
||||||
# resetting the session key on each new request, thus destroying the concept
|
# resetting the session key on each new request, thus destroying the concept
|
||||||
# of a "session". We are going to do it manually, saving the session ID here
|
# of a "session". We are going to do it manually, saving the session ID here
|
||||||
# and then setting the cookie on each request.
|
# and then setting the cookie on each request.
|
||||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
|
intro_form = intro_page.forms[0]
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
intro_result = intro_form.submit()
|
||||||
|
|
||||||
|
# follow first redirect
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
type_page = intro_result.follow()
|
||||||
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
# ---- TYPE PAGE ----
|
# ---- TYPE PAGE ----
|
||||||
type_form = type_page.forms[0]
|
type_form = type_page.forms[0]
|
||||||
type_form["organization_type-organization_type"] = "federal"
|
type_form["organization_type-organization_type"] = "federal"
|
||||||
|
@ -1418,8 +1556,22 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
||||||
Make sure the long name is displaying in the application form,
|
Make sure the long name is displaying in the application form,
|
||||||
org step
|
org step
|
||||||
"""
|
"""
|
||||||
request = self.app.get(reverse("application:")).follow()
|
intro_page = self.app.get(reverse("application:"))
|
||||||
self.assertContains(request, "Federal: an agency of the U.S. government")
|
# django-webtest does not handle cookie-based sessions well because it keeps
|
||||||
|
# resetting the session key on each new request, thus destroying the concept
|
||||||
|
# of a "session". We are going to do it manually, saving the session ID here
|
||||||
|
# and then setting the cookie on each request.
|
||||||
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
|
intro_form = intro_page.forms[0]
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
intro_result = intro_form.submit()
|
||||||
|
|
||||||
|
# follow first redirect
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
type_page = intro_result.follow()
|
||||||
|
|
||||||
|
self.assertContains(type_page, "Federal: an agency of the U.S. government")
|
||||||
|
|
||||||
def test_long_org_name_in_application_manage(self):
|
def test_long_org_name_in_application_manage(self):
|
||||||
"""
|
"""
|
||||||
|
@ -1693,6 +1845,7 @@ class TestDomainManagers(TestDomainOverview):
|
||||||
response = self.client.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
|
response = self.client.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
|
||||||
self.assertContains(response, "Add a domain manager")
|
self.assertContains(response, "Add a domain manager")
|
||||||
|
|
||||||
|
@boto3_mocking.patching
|
||||||
def test_domain_user_add_form(self):
|
def test_domain_user_add_form(self):
|
||||||
"""Adding an existing user works."""
|
"""Adding an existing user works."""
|
||||||
other_user, _ = get_user_model().objects.get_or_create(email="mayor@igorville.gov")
|
other_user, _ = get_user_model().objects.get_or_create(email="mayor@igorville.gov")
|
||||||
|
@ -1702,6 +1855,10 @@ 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)
|
||||||
|
|
||||||
|
mock_client = MockSESClient()
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||||
|
with less_console_noise():
|
||||||
success_result = add_page.form.submit()
|
success_result = add_page.form.submit()
|
||||||
|
|
||||||
self.assertEqual(success_result.status_code, 302)
|
self.assertEqual(success_result.status_code, 302)
|
||||||
|
@ -1731,7 +1888,12 @@ class TestDomainManagers(TestDomainOverview):
|
||||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
add_page.form["email"] = email_address
|
add_page.form["email"] = email_address
|
||||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
|
||||||
|
mock_client = MockSESClient()
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||||
|
with less_console_noise():
|
||||||
success_result = add_page.form.submit()
|
success_result = add_page.form.submit()
|
||||||
|
|
||||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
success_page = success_result.follow()
|
success_page = success_result.follow()
|
||||||
|
|
||||||
|
@ -1757,7 +1919,12 @@ class TestDomainManagers(TestDomainOverview):
|
||||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
add_page.form["email"] = caps_email_address
|
add_page.form["email"] = caps_email_address
|
||||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
|
||||||
|
mock_client = MockSESClient()
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||||
|
with less_console_noise():
|
||||||
success_result = add_page.form.submit()
|
success_result = add_page.form.submit()
|
||||||
|
|
||||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
success_page = success_result.follow()
|
success_page = success_result.follow()
|
||||||
|
|
||||||
|
@ -1777,6 +1944,7 @@ 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):
|
||||||
|
with less_console_noise():
|
||||||
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
|
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
|
||||||
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
|
||||||
|
@ -1803,6 +1971,7 @@ 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):
|
||||||
|
with less_console_noise():
|
||||||
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
|
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
|
||||||
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
|
||||||
|
@ -1841,6 +2010,7 @@ 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):
|
||||||
|
with less_console_noise():
|
||||||
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
|
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
|
||||||
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
|
||||||
|
@ -1883,6 +2053,7 @@ 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):
|
||||||
|
with less_console_noise():
|
||||||
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
|
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
|
||||||
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
|
||||||
|
@ -1922,10 +2093,10 @@ 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:
|
||||||
|
with less_console_noise():
|
||||||
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
|
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
|
||||||
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
|
||||||
|
@ -1959,6 +2130,7 @@ 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:
|
||||||
|
with less_console_noise():
|
||||||
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
|
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
|
||||||
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
|
||||||
|
@ -1977,7 +2149,11 @@ class TestDomainManagers(TestDomainOverview):
|
||||||
"""Posting to the delete view deletes an invitation."""
|
"""Posting to the delete view deletes an invitation."""
|
||||||
email_address = "mayor@igorville.gov"
|
email_address = "mayor@igorville.gov"
|
||||||
invitation, _ = DomainInvitation.objects.get_or_create(domain=self.domain, email=email_address)
|
invitation, _ = DomainInvitation.objects.get_or_create(domain=self.domain, email=email_address)
|
||||||
|
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}))
|
self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id}))
|
||||||
|
mock_client.EMAILS_SENT.clear()
|
||||||
with self.assertRaises(DomainInvitation.DoesNotExist):
|
with self.assertRaises(DomainInvitation.DoesNotExist):
|
||||||
DomainInvitation.objects.get(id=invitation.id)
|
DomainInvitation.objects.get(id=invitation.id)
|
||||||
|
|
||||||
|
@ -1989,8 +2165,11 @@ class TestDomainManagers(TestDomainOverview):
|
||||||
other_user = User()
|
other_user = User()
|
||||||
other_user.save()
|
other_user.save()
|
||||||
self.client.force_login(other_user)
|
self.client.force_login(other_user)
|
||||||
|
mock_client = MagicMock()
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||||
with less_console_noise(): # permission denied makes console errors
|
with less_console_noise(): # permission denied makes console errors
|
||||||
result = self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id}))
|
result = self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id}))
|
||||||
|
|
||||||
self.assertEqual(result.status_code, 403)
|
self.assertEqual(result.status_code, 403)
|
||||||
|
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
|
@ -2006,6 +2185,10 @@ 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)
|
||||||
|
|
||||||
|
mock_client = MagicMock()
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||||
|
with less_console_noise():
|
||||||
add_page.form.submit()
|
add_page.form.submit()
|
||||||
|
|
||||||
# user was invited, create them
|
# user was invited, create them
|
||||||
|
@ -2061,6 +2244,7 @@ class TestDomainNameservers(TestDomainOverview):
|
||||||
# attempt to submit the form without two hosts, both subdomains,
|
# attempt to submit the form without two hosts, both subdomains,
|
||||||
# only one has ips
|
# only one has ips
|
||||||
nameservers_page.form["form-1-server"] = "ns2.igorville.gov"
|
nameservers_page.form["form-1-server"] = "ns2.igorville.gov"
|
||||||
|
|
||||||
with less_console_noise(): # swallow log warning message
|
with less_console_noise(): # swallow log warning message
|
||||||
result = nameservers_page.form.submit()
|
result = nameservers_page.form.submit()
|
||||||
# form submission was a post with an error, response should be a 200
|
# form submission was a post with an error, response should be a 200
|
||||||
|
@ -2396,6 +2580,8 @@ 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)
|
||||||
|
mock_client = MagicMock()
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||||
with less_console_noise(): # swallow log warning message
|
with less_console_noise(): # swallow log warning message
|
||||||
result = security_email_page.form.submit()
|
result = security_email_page.form.submit()
|
||||||
self.assertEqual(result.status_code, 302)
|
self.assertEqual(result.status_code, 302)
|
||||||
|
@ -2743,6 +2929,9 @@ 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
|
||||||
|
mock_client = MockSESClient()
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||||
|
with less_console_noise():
|
||||||
withdraw_page = detail_page.click("Withdraw request")
|
withdraw_page = detail_page.click("Withdraw request")
|
||||||
self.assertContains(withdraw_page, "Withdraw request for")
|
self.assertContains(withdraw_page, "Withdraw request for")
|
||||||
home_page = withdraw_page.click("Withdraw request")
|
home_page = withdraw_page.click("Withdraw request")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -206,15 +206,15 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView):
|
||||||
# if accessing this class directly, redirect to the first step
|
# if accessing this class directly, redirect to the first step
|
||||||
# in other words, if `ApplicationWizard` is called as view
|
# in other words, if `ApplicationWizard` is called as view
|
||||||
# directly by some redirect or url handler, we'll send users
|
# directly by some redirect or url handler, we'll send users
|
||||||
# to the first step in the processes; subclasses will NOT
|
# either to an acknowledgement page or to the first step in
|
||||||
# be redirected. The purpose of this is to allow code to
|
# the processes (if an edit rather than a new request); subclasses
|
||||||
|
# will NOT be redirected. The purpose of this is to allow code to
|
||||||
# send users "to the application wizard" without needing to
|
# send users "to the application wizard" without needing to
|
||||||
# know which view is first in the list of steps.
|
# know which view is first in the list of steps.
|
||||||
if self.__class__ == ApplicationWizard:
|
if self.__class__ == ApplicationWizard:
|
||||||
# if starting a new application, clear the storage
|
|
||||||
if request.path_info == self.NEW_URL_NAME:
|
if request.path_info == self.NEW_URL_NAME:
|
||||||
del self.storage
|
return render(request, "application_intro.html")
|
||||||
|
else:
|
||||||
return self.goto(self.steps.first)
|
return self.goto(self.steps.first)
|
||||||
|
|
||||||
self.steps.current = current_url
|
self.steps.current = current_url
|
||||||
|
@ -370,13 +370,20 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView):
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs) -> HttpResponse:
|
def post(self, request, *args, **kwargs) -> HttpResponse:
|
||||||
"""This method handles POST requests."""
|
"""This method handles POST requests."""
|
||||||
# if accessing this class directly, redirect to the first step
|
|
||||||
if self.__class__ == ApplicationWizard:
|
|
||||||
return self.goto(self.steps.first)
|
|
||||||
|
|
||||||
# which button did the user press?
|
# which button did the user press?
|
||||||
button: str = request.POST.get("submit_button", "")
|
button: str = request.POST.get("submit_button", "")
|
||||||
|
|
||||||
|
# if user has acknowledged the intro message
|
||||||
|
if button == "intro_acknowledge":
|
||||||
|
if request.path_info == self.NEW_URL_NAME:
|
||||||
|
del self.storage
|
||||||
|
return self.goto(self.steps.first)
|
||||||
|
|
||||||
|
# if accessing this class directly, redirect to the first step
|
||||||
|
if self.__class__ == ApplicationWizard:
|
||||||
|
return self.goto(self.steps.first)
|
||||||
|
|
||||||
forms = self.get_forms(use_post=True)
|
forms = self.get_forms(use_post=True)
|
||||||
if self.is_valid(forms):
|
if self.is_valid(forms):
|
||||||
# always save progress
|
# always save progress
|
||||||
|
|
|
@ -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