diff --git a/src/registrar/templates/emails/status_change_rejected.txt b/src/registrar/templates/emails/status_change_rejected.txt
index 5d6f01ee7..4beacf25a 100644
--- a/src/registrar/templates/emails/status_change_rejected.txt
+++ b/src/registrar/templates/emails/status_change_rejected.txt
@@ -19,7 +19,7 @@ Learn more about:
- What you can and can’t do with .gov domains
If you have questions or comments, reply to this email.
-{% elif application.status == 'requestor' %}
+{% elif application.rejection_reason == 'requestor' %}
Your domain request was rejected because we don’t believe you’re eligible to request a
.gov domain on behalf of {{ application.organization_name }}. You must be a government employee, or be
working on behalf of a government organization, to request a .gov domain.
@@ -28,7 +28,7 @@ working on behalf of a government organization, to request a .gov domain.
DEMONSTRATE ELIGIBILITY
If you can provide more information that demonstrates your eligibility, or you want to
discuss further, reply to this email.
-{% elif application.status == 'second_domain_reasoning' %}
+{% elif application.rejection_reason == 'second_domain_reasoning' %}
Your domain request was rejected because {{ application.organization_name }} has a .gov domain. Our
practice is to approve one domain per online service per government organization. We
evaluate additional requests on a case-by-case basis. You did not provide sufficient
@@ -38,10 +38,10 @@ Read more about our practice of approving one domain per online service
.
If you have questions or comments, reply to this email.
-{% elif application.status == 'contacts_or_organization_legitimacy' %}
+{% elif application.rejection_reason == 'contacts_or_organization_legitimacy' %}
Your domain request was rejected because we could not verify the organizational
contacts you provided. If you have questions or comments, reply to this email.
-{% elif application.status == 'organization_eligibility' %}
+{% elif application.rejection_reason == 'organization_eligibility' %}
Your domain request was rejected because we determined that {{ application.organization_name }} is not
eligible for a .gov domain. .Gov domains are only available to official U.S.-based
government organizations.
@@ -53,7 +53,7 @@ This can include links to (or copies of) your authorizing legislation, your foun
charter or bylaws, or other similar documentation. Without this, we can’t approve a
.gov domain for your organization. Learn more about eligibility for .gov domains
.
-{% elif application.status == 'naming_requirements' %}
+{% elif application.rejection_reason == 'naming_requirements' %}
Your domain request was rejected because it does not meet our naming requirements.
Domains should uniquely identify a government organization and be clear to the
general public. Learn more about naming requirements for your type of organization
diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py
index f90b18584..6142c2aee 100644
--- a/src/registrar/tests/test_admin.py
+++ b/src/registrar/tests/test_admin.py
@@ -412,7 +412,7 @@ class TestDomainApplicationAdmin(MockEppLib):
# Now let's make sure the long description does not exist
self.assertNotContains(response, "Federal: an agency of the U.S. government")
- def transition_state_and_send_email(self, application, status):
+ def transition_state_and_send_email(self, application, status, rejection_reason=None):
"""Helper method for the email test cases."""
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
@@ -420,12 +420,15 @@ class TestDomainApplicationAdmin(MockEppLib):
# Create a mock request
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
- # Modify the application's property
+ # Modify the application's properties
application.status = status
+ application.rejection_reason = rejection_reason
# Use the model admin's save_model method
self.admin.save_model(request, application, form=None, change=True)
+ logger.info(f'application.rejection_reason {application.rejection_reason}')
+
def assert_email_is_accurate(self, expected_string, email_index, email_address):
"""Helper method for the email test cases.
email_index is the index of the email in mock_client."""
@@ -512,7 +515,7 @@ class TestDomainApplicationAdmin(MockEppLib):
self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
# Test Withdrawn Status
- self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.REJECTED)
+ self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.REJECTED, DomainApplication.RejectionReasons.DOMAIN_PURPOSE)
self.assert_email_is_accurate("Your .gov domain request has been rejected.", 1, EMAIL)
self.assertEqual(len(self.mock_client.EMAILS_SENT), 2)
@@ -520,9 +523,9 @@ class TestDomainApplicationAdmin(MockEppLib):
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.APPROVED)
self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
- def test_save_model_sends_rejected_email(self):
- """When transitioning to rejected on a domain request,
- an email is sent out every time."""
+ def test_save_model_sends_rejected_email_domain_purpose(self):
+ """When transitioning to rejected on a domain request, an email is sent
+ explaining why when the reason is domain purpose."""
# Ensure there is no user with this email
EMAIL = "mayor@igorville.gov"
@@ -531,19 +534,137 @@ class TestDomainApplicationAdmin(MockEppLib):
# Create a sample application
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
- # Test Submitted Status
- self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.REJECTED)
- self.assert_email_is_accurate("Your .gov domain request has been rejected.", 0, EMAIL)
+ # Reject for reason DOMAIN_PURPOSE and test email
+ self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.REJECTED, DomainApplication.RejectionReasons.DOMAIN_PURPOSE)
+ self.assert_email_is_accurate("Your domain request was rejected because the purpose you provided did not meet our \nrequirements.", 0, EMAIL)
self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
- # Test Withdrawn Status
+ # Approve
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.APPROVED)
self.assert_email_is_accurate("Congratulations! Your .gov domain request has been approved.", 1, EMAIL)
self.assertEqual(len(self.mock_client.EMAILS_SENT), 2)
- # Test Submitted Status Again (No new email should be sent)
- self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.REJECTED)
- self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
+ def test_save_model_sends_rejected_email_requestor(self):
+ """When transitioning to rejected on a domain request, an email is sent
+ explaining why when the reason is requestor."""
+
+ # Ensure there is no user with this email
+ EMAIL = "mayor@igorville.gov"
+ User.objects.filter(email=EMAIL).delete()
+
+ # Create a sample application
+ application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
+
+ # Reject for reason REQUESTOR and test email including dynamic organization name
+ self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.REJECTED, DomainApplication.RejectionReasons.REQUESTOR)
+ self.assert_email_is_accurate("Your domain request was rejected because we don’t believe you’re eligible to request a .gov domain on behalf of Testorg", 0, EMAIL)
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
+
+ # Approve
+ self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.APPROVED)
+ self.assert_email_is_accurate("Congratulations! Your .gov domain request has been approved.", 1, EMAIL)
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 2)
+
+ def test_save_model_sends_rejected_email_requestor(self):
+ """When transitioning to rejected on a domain request, an email is sent
+ explaining why when the reason is second domain."""
+
+ # Ensure there is no user with this email
+ EMAIL = "mayor@igorville.gov"
+ User.objects.filter(email=EMAIL).delete()
+
+ # Create a sample application
+ application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
+
+ # Reject for reason SECOND_DOMAIN_REASONING and test email including dynamic organization name
+ self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.REJECTED, DomainApplication.RejectionReasons.SECOND_DOMAIN_REASONING)
+ self.assert_email_is_accurate("Your domain request was rejected because Testorg has a .gov domain.", 0, EMAIL)
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
+
+ # Approve
+ self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.APPROVED)
+ self.assert_email_is_accurate("Congratulations! Your .gov domain request has been approved.", 1, EMAIL)
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 2)
+
+ def test_save_model_sends_rejected_email_contacts_or_org_legitimacy(self):
+ """When transitioning to rejected on a domain request, an email is sent
+ explaining why when the reason is contacts or org legitimacy."""
+
+ # Ensure there is no user with this email
+ EMAIL = "mayor@igorville.gov"
+ User.objects.filter(email=EMAIL).delete()
+
+ # Create a sample application
+ application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
+
+ # Reject for reason CONTACTS_OR_ORGANIZATION_LEGITIMACY and test email including dynamic organization name
+ self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.REJECTED, DomainApplication.RejectionReasons.CONTACTS_OR_ORGANIZATION_LEGITIMACY)
+ self.assert_email_is_accurate("Your domain request was rejected because we could not verify the organizational \ncontacts you provided. If you have questions or comments, reply to this email.", 0, EMAIL)
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
+
+ # Approve
+ self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.APPROVED)
+ self.assert_email_is_accurate("Congratulations! Your .gov domain request has been approved.", 1, EMAIL)
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 2)
+
+ def test_save_model_sends_rejected_email_org_eligibility(self):
+ """When transitioning to rejected on a domain request, an email is sent
+ explaining why when the reason is org eligibility."""
+
+ # Ensure there is no user with this email
+ EMAIL = "mayor@igorville.gov"
+ User.objects.filter(email=EMAIL).delete()
+
+ # Create a sample application
+ application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
+
+ # Reject for reason ORGANIZATION_ELIGIBILITY and test email including dynamic organization name
+ self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.REJECTED, DomainApplication.RejectionReasons.ORGANIZATION_ELIGIBILITY)
+ self.assert_email_is_accurate("Your domain request was rejected because we determined that Testorg is not \neligible for a .gov domain.", 0, EMAIL)
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
+
+ # Approve
+ self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.APPROVED)
+ self.assert_email_is_accurate("Congratulations! Your .gov domain request has been approved.", 1, EMAIL)
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 2)
+
+ def test_save_model_sends_rejected_email_naming(self):
+ """When transitioning to rejected on a domain request, an email is sent
+ explaining why when the reason is naming."""
+
+ # Ensure there is no user with this email
+ EMAIL = "mayor@igorville.gov"
+ User.objects.filter(email=EMAIL).delete()
+
+ # Create a sample application
+ application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
+
+ # Reject for reason NAMING_REQUIREMENTS and test email including dynamic organization name
+ self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.REJECTED, DomainApplication.RejectionReasons.NAMING_REQUIREMENTS)
+ self.assert_email_is_accurate("Your domain request was rejected because it does not meet our naming requirements.", 0, EMAIL)
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
+
+ # Approve
+ self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.APPROVED)
+ self.assert_email_is_accurate("Congratulations! Your .gov domain request has been approved.", 1, EMAIL)
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 2)
+
+ def test_save_model_clear_rejected_reason(self):
+ """When transitioning from rejected on a domain request,
+ the rejected_reason is cleared."""
+
+ # Create a sample application
+ application = completed_application(status=DomainApplication.ApplicationStatus.REJECTED)
+ application.rejected_reason = DomainApplication.RejectionReasons.DOMAIN_PURPOSE
+ application.save()
+
+ # Approve
+ with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
+ application.approve()
+
+ application.refresh_from_db()
+ self.assertEqual(application.rejected_reason, None)
+
def test_save_model_sends_withdrawn_email(self):
"""When transitioning to withdrawn on a domain request,
@@ -633,6 +754,7 @@ class TestDomainApplicationAdmin(MockEppLib):
"created_at",
"updated_at",
"status",
+ "rejection_reason",
"creator",
"investigator",
"organization_type",
@@ -744,7 +866,7 @@ class TestDomainApplicationAdmin(MockEppLib):
"Cannot edit an application with a restricted creator.",
)
- def trigger_saving_approved_to_another_state(self, domain_is_active, another_state):
+ def trigger_saving_approved_to_another_state(self, domain_is_active, another_state, rejection_reason=None):
"""Helper method that triggers domain request state changes from approved to another state,
with an associated domain that can be either active (READY) or not.
@@ -773,6 +895,8 @@ class TestDomainApplicationAdmin(MockEppLib):
stack.enter_context(patch.object(messages, "error"))
application.status = another_state
+ application.rejection_reason = rejection_reason
+
self.admin.save_model(request, application, None, True)
# Assert that the error message was called with the correct argument
@@ -814,7 +938,7 @@ class TestDomainApplicationAdmin(MockEppLib):
self.trigger_saving_approved_to_another_state(False, DomainApplication.ApplicationStatus.ACTION_NEEDED)
def test_side_effects_when_saving_approved_to_rejected(self):
- self.trigger_saving_approved_to_another_state(False, DomainApplication.ApplicationStatus.REJECTED)
+ self.trigger_saving_approved_to_another_state(False, DomainApplication.ApplicationStatus.REJECTED, DomainApplication.RejectionReasons.CONTACTS_OR_ORGANIZATION_LEGITIMACY)
def test_side_effects_when_saving_approved_to_ineligible(self):
self.trigger_saving_approved_to_another_state(False, DomainApplication.ApplicationStatus.INELIGIBLE)