merge main again

This commit is contained in:
David Kennedy 2024-02-26 15:18:44 -05:00
commit caa1a9ef84
No known key found for this signature in database
GPG key ID: 6528A5386E66B96B
7 changed files with 128 additions and 40 deletions

View file

@ -286,6 +286,7 @@ AWS_MAX_ATTEMPTS = 3
BOTO_CONFIG = Config(retries={"mode": AWS_RETRY_MODE, "max_attempts": AWS_MAX_ATTEMPTS}) BOTO_CONFIG = Config(retries={"mode": AWS_RETRY_MODE, "max_attempts": AWS_MAX_ATTEMPTS})
# email address to use for various automated correspondence # email address to use for various automated correspondence
# also used as a default to and bcc email
DEFAULT_FROM_EMAIL = "help@get.gov <help@get.gov>" DEFAULT_FROM_EMAIL = "help@get.gov <help@get.gov>"
# connect to an (external) SMTP server for sending email # connect to an (external) SMTP server for sending email

View file

@ -4,6 +4,7 @@ from typing import Union
import logging import logging
from django.apps import apps from django.apps import apps
from django.conf import settings
from django.db import models from django.db import models
from django_fsm import FSMField, transition # type: ignore from django_fsm import FSMField, transition # type: ignore
from django.utils import timezone from django.utils import timezone
@ -588,7 +589,9 @@ class DomainApplication(TimeStampedModel):
logger.error(err) logger.error(err)
logger.error(f"Can't query an approved domain while attempting {called_from}") logger.error(f"Can't query an approved domain while attempting {called_from}")
def _send_status_update_email(self, new_status, email_template, email_template_subject, send_email=True): def _send_status_update_email(
self, new_status, email_template, email_template_subject, send_email=True, bcc_address=""
):
"""Send a status 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
@ -613,6 +616,7 @@ class DomainApplication(TimeStampedModel):
email_template_subject, email_template_subject,
self.submitter.email, self.submitter.email,
context={"application": self}, context={"application": self},
bcc_address=bcc_address,
) )
logger.info(f"The {new_status} email sent to: {self.submitter.email}") logger.info(f"The {new_status} email sent to: {self.submitter.email}")
except EmailSendingError: except EmailSendingError:
@ -654,11 +658,17 @@ class DomainApplication(TimeStampedModel):
# Limit email notifications to transitions from Started and Withdrawn # Limit email notifications to transitions from Started and Withdrawn
limited_statuses = [self.ApplicationStatus.STARTED, self.ApplicationStatus.WITHDRAWN] limited_statuses = [self.ApplicationStatus.STARTED, self.ApplicationStatus.WITHDRAWN]
bcc_address = ""
if settings.IS_PRODUCTION:
bcc_address = settings.DEFAULT_FROM_EMAIL
if self.status in limited_statuses: if self.status in limited_statuses:
self._send_status_update_email( self._send_status_update_email(
"submission confirmation", "submission confirmation",
"emails/submission_confirmation.txt", "emails/submission_confirmation.txt",
"emails/submission_confirmation_subject.txt", "emails/submission_confirmation_subject.txt",
True,
bcc_address,
) )
@transition( @transition(

View file

@ -1,5 +1,5 @@
from datetime import date from datetime import date
from django.test import TestCase, RequestFactory, Client from django.test import TestCase, RequestFactory, Client, override_settings
from django.contrib.admin.sites import AdminSite from django.contrib.admin.sites import AdminSite
from contextlib import ExitStack from contextlib import ExitStack
from django_webtest import WebTest # type: ignore from django_webtest import WebTest # type: ignore
@ -608,7 +608,9 @@ class TestDomainApplicationAdmin(MockEppLib):
# Use the model admin's save_model method # Use the model admin's save_model method
self.admin.save_model(request, application, form=None, change=True) self.admin.save_model(request, application, form=None, change=True)
def assert_email_is_accurate(self, expected_string, email_index, email_address): def assert_email_is_accurate(
self, expected_string, email_index, email_address, test_that_no_bcc=False, bcc_email_address=""
):
"""Helper method for the email test cases. """Helper method for the email test cases.
email_index is the index of the email in mock_client.""" email_index is the index of the email in mock_client."""
@ -628,12 +630,26 @@ class TestDomainApplicationAdmin(MockEppLib):
self.assertEqual(to_email, email_address) self.assertEqual(to_email, email_address)
self.assertIn(expected_string, email_body) self.assertIn(expected_string, email_body)
if test_that_no_bcc:
_ = ""
with self.assertRaises(KeyError):
with less_console_noise():
_ = kwargs["Destination"]["BccAddresses"][0]
self.assertEqual(_, "")
if bcc_email_address:
bcc_email = kwargs["Destination"]["BccAddresses"][0]
self.assertEqual(bcc_email, bcc_email_address)
def test_save_model_sends_submitted_email(self): def test_save_model_sends_submitted_email(self):
"""When transitioning to submitted from started or withdrawn on a domain request, """When transitioning to submitted from started or withdrawn on a domain request,
an email is sent out. an email is sent out.
When transitioning to submitted from dns needed or in review on a domain request, When transitioning to submitted from dns needed or in review on a domain request,
no email is sent out.""" no email is sent out.
Also test that the default email set in settings is NOT BCCd on non-prod whenever
an email does go out."""
with less_console_noise(): with less_console_noise():
# Ensure there is no user with this email # Ensure there is no user with this email
@ -645,7 +661,64 @@ class TestDomainApplicationAdmin(MockEppLib):
# Test Submitted Status from started # Test Submitted Status from started
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.SUBMITTED) self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.SUBMITTED)
self.assert_email_is_accurate("We received your .gov domain request.", 0, EMAIL) self.assert_email_is_accurate("We received your .gov domain request.", 0, EMAIL, True)
self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
# Test Withdrawn Status
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.WITHDRAWN)
self.assert_email_is_accurate(
"Your .gov domain request has been withdrawn and will not be reviewed by our team.", 1, EMAIL, True
)
self.assertEqual(len(self.mock_client.EMAILS_SENT), 2)
# Test Submitted Status Again (from withdrawn)
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.SUBMITTED)
self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
# Move it to IN_REVIEW
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.IN_REVIEW)
self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
# Test Submitted Status Again from in IN_REVIEW, no new email should be sent
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.SUBMITTED)
self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
# Move it to IN_REVIEW
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.IN_REVIEW)
self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
# Move it to ACTION_NEEDED
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.ACTION_NEEDED)
self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
# Test Submitted Status Again from in ACTION_NEEDED, no new email should be sent
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.SUBMITTED)
self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
@override_settings(IS_PRODUCTION=True)
def test_save_model_sends_submitted_email_with_bcc_on_prod(self):
"""When transitioning to submitted from started or withdrawn on a domain request,
an email is sent out.
When transitioning to submitted from dns needed or in review on a domain request,
no email is sent out.
Also test that the default email set in settings IS BCCd on prod whenever
an email does go out."""
with less_console_noise():
# Ensure there is no user with this email
EMAIL = "mayor@igorville.gov"
User.objects.filter(email=EMAIL).delete()
BCC_EMAIL = settings.DEFAULT_FROM_EMAIL
# Create a sample application
application = completed_application()
# Test Submitted Status from started
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.SUBMITTED)
self.assert_email_is_accurate("We received your .gov domain request.", 0, EMAIL, False, BCC_EMAIL)
self.assertEqual(len(self.mock_client.EMAILS_SENT), 1) self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
# Test Withdrawn Status # Test Withdrawn Status
@ -657,6 +730,7 @@ class TestDomainApplicationAdmin(MockEppLib):
# Test Submitted Status Again (from withdrawn) # Test Submitted Status Again (from withdrawn)
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.SUBMITTED) self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.SUBMITTED)
self.assert_email_is_accurate("We received your .gov domain request.", 0, EMAIL, False, BCC_EMAIL)
self.assertEqual(len(self.mock_client.EMAILS_SENT), 3) self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
# Move it to IN_REVIEW # Move it to IN_REVIEW

View file

@ -1,31 +0,0 @@
from django.test import Client, TestCase, override_settings
from django.contrib.auth import get_user_model
class MyTestCase(TestCase):
def setUp(self):
self.client = Client()
username = "test_user"
first_name = "First"
last_name = "Last"
email = "info@example.com"
self.user = get_user_model().objects.create(
username=username, first_name=first_name, last_name=last_name, email=email
)
self.client.force_login(self.user)
def tearDown(self):
super().tearDown()
self.user.delete()
@override_settings(IS_PRODUCTION=True)
def test_production_environment(self):
"""No banner on prod."""
home_page = self.client.get("/")
self.assertNotContains(home_page, "You are on a test site.")
@override_settings(IS_PRODUCTION=False)
def test_non_production_environment(self):
"""Banner on non-prod."""
home_page = self.client.get("/")
self.assertContains(home_page, "You are on a test site.")

View file

@ -1,4 +1,4 @@
from django.test import Client, TestCase from django.test import Client, TestCase, override_settings
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from .common import MockEppLib # type: ignore from .common import MockEppLib # type: ignore
@ -50,3 +50,32 @@ class TestWithUser(MockEppLib):
DomainApplication.objects.all().delete() DomainApplication.objects.all().delete()
DomainInformation.objects.all().delete() DomainInformation.objects.all().delete()
self.user.delete() self.user.delete()
class TestEnvironmentVariablesEffects(TestCase):
def setUp(self):
self.client = Client()
username = "test_user"
first_name = "First"
last_name = "Last"
email = "info@example.com"
self.user = get_user_model().objects.create(
username=username, first_name=first_name, last_name=last_name, email=email
)
self.client.force_login(self.user)
def tearDown(self):
super().tearDown()
self.user.delete()
@override_settings(IS_PRODUCTION=True)
def test_production_environment(self):
"""No banner on prod."""
home_page = self.client.get("/")
self.assertNotContains(home_page, "You are on a test site.")
@override_settings(IS_PRODUCTION=False)
def test_non_production_environment(self):
"""Banner on non-prod."""
home_page = self.client.get("/")
self.assertContains(home_page, "You are on a test site.")

View file

@ -15,7 +15,7 @@ class EmailSendingError(RuntimeError):
pass pass
def send_templated_email(template_name: str, subject_template_name: str, to_address: str, context={}): def send_templated_email(template_name: str, subject_template_name: str, to_address: str, bcc_address="", context={}):
"""Send an email built from a template to one email address. """Send an email built from a template to one email address.
template_name and subject_template_name are relative to the same template template_name and subject_template_name are relative to the same template
@ -40,10 +40,14 @@ def send_templated_email(template_name: str, subject_template_name: str, to_addr
except Exception as exc: except Exception as exc:
raise EmailSendingError("Could not access the SES client.") from exc raise EmailSendingError("Could not access the SES client.") from exc
destination = {"ToAddresses": [to_address]}
if bcc_address:
destination["BccAddresses"] = [bcc_address]
try: try:
ses_client.send_email( ses_client.send_email(
FromEmailAddress=settings.DEFAULT_FROM_EMAIL, FromEmailAddress=settings.DEFAULT_FROM_EMAIL,
Destination={"ToAddresses": [to_address]}, Destination=destination,
Content={ Content={
"Simple": { "Simple": {
"Subject": {"Data": subject}, "Subject": {"Data": subject},

View file

@ -14,6 +14,7 @@ from django.http import HttpResponseRedirect
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse from django.urls import reverse
from django.views.generic.edit import FormMixin from django.views.generic.edit import FormMixin
from django.conf import settings
from registrar.models import ( from registrar.models import (
Domain, Domain,
@ -707,7 +708,7 @@ class DomainAddUserView(DomainFormBaseView):
adding a success message to the view if the email sending succeeds""" adding a success message to the view if the email sending succeeds"""
# Set a default email address to send to for staff # Set a default email address to send to for staff
requestor_email = "help@get.gov" requestor_email = settings.DEFAULT_FROM_EMAIL
# Check if the email requestor has a valid email address # Check if the email requestor has a valid email address
if not requestor.is_staff and requestor.email is not None and requestor.email.strip() != "": if not requestor.is_staff and requestor.email is not None and requestor.email.strip() != "":