Skip to content

Commit

Permalink
Merge pull request #2827 from cisagov/ms/2307-send-notification-emails
Browse files Browse the repository at this point in the history
#2307: send notification emails on changes to domain - [MS]
  • Loading branch information
Matt-Spence authored Oct 21, 2024
2 parents beca7cb + 39ee21e commit 6ebafec
Show file tree
Hide file tree
Showing 11 changed files with 601 additions and 28 deletions.
2 changes: 1 addition & 1 deletion src/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ services:
# Run Django in debug mode on local
- DJANGO_DEBUG=True
# Set DJANGO_LOG_LEVEL in env
- DJANGO_LOG_LEVEL
- DJANGO_LOG_LEVEL=DEBUG
# Run Django without production flags
- IS_PRODUCTION=False
# Tell Django where it is being hosted
Expand Down
2 changes: 2 additions & 0 deletions src/registrar/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,8 +476,10 @@ class JsonServerFormatter(ServerFormatter):

def format(self, record):
formatted_record = super().format(record)

if not hasattr(record, "server_time"):
record.server_time = self.formatTime(record, self.datefmt)

log_entry = {"server_time": record.server_time, "level": record.levelname, "message": formatted_record}
return json.dumps(log_entry)

Expand Down
2 changes: 0 additions & 2 deletions src/registrar/models/domain_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -831,7 +831,6 @@ def _send_status_update_email(

if custom_email_content:
context["custom_email_content"] = custom_email_content

send_templated_email(
email_template,
email_template_subject,
Expand Down Expand Up @@ -877,7 +876,6 @@ def submit(self):
DraftDomain = apps.get_model("registrar.DraftDomain")
if not DraftDomain.string_could_be_domain(self.requested_domain.name):
raise ValueError("Requested domain is not a valid domain name.")

# if the domain has not been submitted before this must be the first time
if not self.first_submitted_date:
self.first_submitted_date = timezone.now().date()
Expand Down
2 changes: 1 addition & 1 deletion src/registrar/templates/domain_users.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ <h1>Domain managers</h1>

<p>
Domain managers can update all information related to a domain within the
.gov registrar, including including security email and DNS name servers.
.gov registrar, including security email and DNS name servers.
</p>

<ul class="usa-list">
Expand Down
31 changes: 31 additions & 0 deletions src/registrar/templates/emails/update_to_approved_domain.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #}

Hi,
An update was made to a domain you manage.

DOMAIN: {{domain}}
UPDATED BY: {{user}}
UPDATED ON: {{date}}
INFORMATION UPDATED: {{changes}}

You can view this update in the .gov registrar <https://manage.get.gov/>.

Get help with managing your .gov domain <https://get.gov/help/domain-management/>.

----------------------------------------------------------------

WHY DID YOU RECEIVE THIS EMAIL?
You’re listed as a domain manager for {{domain}}, so you’ll receive a notification whenever changes are made to that domain.
If you have questions or concerns, reach out to the person who made the change or reply to this email.

THANK YOU
.Gov helps the public identify official, trusted information. Thank you for using a .gov domain.

----------------------------------------------------------------

The .gov team
Contact us <https://get.gov/contact/>
Learn about .gov <https://get.gov>

The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency (CISA) <https://cisa.gov/>
{% endautoescape %}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
An update was made to {{domain}}
91 changes: 80 additions & 11 deletions src/registrar/tests/test_emails.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,58 @@ def test_disable_email_flag(self):
# Assert that an email wasn't sent
self.assertFalse(self.mock_client.send_email.called)

@boto3_mocking.patching
def test_email_with_cc(self):
"""Test sending email with cc works"""
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
send_templated_email(
"emails/update_to_approved_domain.txt",
"emails/update_to_approved_domain_subject.txt",
"[email protected]",
context={"domain": "test", "user": "test", "date": 1, "changes": "test"},
bcc_address=None,
cc_addresses=["[email protected]", "[email protected]"],
)

# check that an email was sent
self.assertTrue(self.mock_client.send_email.called)

# check the call sequence for the email
args, kwargs = self.mock_client.send_email.call_args
self.assertIn("Destination", kwargs)
self.assertIn("CcAddresses", kwargs["Destination"])

self.assertEqual(["[email protected]", "[email protected]"], kwargs["Destination"]["CcAddresses"])

@boto3_mocking.patching
@override_settings(IS_PRODUCTION=True)
def test_email_with_cc_in_prod(self):
"""Test sending email with cc works in prod"""
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
send_templated_email(
"emails/update_to_approved_domain.txt",
"emails/update_to_approved_domain_subject.txt",
"[email protected]",
context={"domain": "test", "user": "test", "date": 1, "changes": "test"},
bcc_address=None,
cc_addresses=["[email protected]", "[email protected]"],
)

# check that an email was sent
self.assertTrue(self.mock_client.send_email.called)

# check the call sequence for the email
args, kwargs = self.mock_client.send_email.call_args
self.assertIn("Destination", kwargs)
self.assertIn("CcAddresses", kwargs["Destination"])

self.assertEqual(["[email protected]", "[email protected]"], kwargs["Destination"]["CcAddresses"])

@boto3_mocking.patching
@less_console_noise_decorator
def test_submission_confirmation(self):
"""Submission confirmation email works."""
domain_request = completed_domain_request()
domain_request = completed_domain_request(user=User.objects.create(username="test", email="[email protected]"))

with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
domain_request.submit()
Expand Down Expand Up @@ -102,7 +149,9 @@ def test_submission_confirmation(self):
@less_console_noise_decorator
def test_submission_confirmation_no_current_website_spacing(self):
"""Test line spacing without current_website."""
domain_request = completed_domain_request(has_current_website=False)
domain_request = completed_domain_request(
has_current_website=False, user=User.objects.create(username="test", email="[email protected]")
)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
domain_request.submit()
_, kwargs = self.mock_client.send_email.call_args
Expand All @@ -115,7 +164,9 @@ def test_submission_confirmation_no_current_website_spacing(self):
@less_console_noise_decorator
def test_submission_confirmation_current_website_spacing(self):
"""Test line spacing with current_website."""
domain_request = completed_domain_request(has_current_website=True)
domain_request = completed_domain_request(
has_current_website=True, user=User.objects.create(username="test", email="[email protected]")
)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
domain_request.submit()
_, kwargs = self.mock_client.send_email.call_args
Expand All @@ -132,7 +183,11 @@ def test_submission_confirmation_other_contacts_spacing(self):

# Create fake creator
_creator = User.objects.create(
username="MrMeoward", first_name="Meoward", last_name="Jones", phone="(888) 888 8888"
username="MrMeoward",
first_name="Meoward",
last_name="Jones",
phone="(888) 888 8888",
email="[email protected]",
)

# Create a fake domain request
Expand All @@ -149,7 +204,9 @@ def test_submission_confirmation_other_contacts_spacing(self):
@less_console_noise_decorator
def test_submission_confirmation_no_other_contacts_spacing(self):
"""Test line spacing without other contacts."""
domain_request = completed_domain_request(has_other_contacts=False)
domain_request = completed_domain_request(
has_other_contacts=False, user=User.objects.create(username="test", email="[email protected]")
)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
domain_request.submit()
_, kwargs = self.mock_client.send_email.call_args
Expand All @@ -161,7 +218,9 @@ def test_submission_confirmation_no_other_contacts_spacing(self):
@less_console_noise_decorator
def test_submission_confirmation_alternative_govdomain_spacing(self):
"""Test line spacing with alternative .gov domain."""
domain_request = completed_domain_request(has_alternative_gov_domain=True)
domain_request = completed_domain_request(
has_alternative_gov_domain=True, user=User.objects.create(username="test", email="[email protected]")
)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
domain_request.submit()
_, kwargs = self.mock_client.send_email.call_args
Expand All @@ -174,7 +233,9 @@ def test_submission_confirmation_alternative_govdomain_spacing(self):
@less_console_noise_decorator
def test_submission_confirmation_no_alternative_govdomain_spacing(self):
"""Test line spacing without alternative .gov domain."""
domain_request = completed_domain_request(has_alternative_gov_domain=False)
domain_request = completed_domain_request(
has_alternative_gov_domain=False, user=User.objects.create(username="test", email="[email protected]")
)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
domain_request.submit()
_, kwargs = self.mock_client.send_email.call_args
Expand All @@ -187,7 +248,9 @@ def test_submission_confirmation_no_alternative_govdomain_spacing(self):
@less_console_noise_decorator
def test_submission_confirmation_about_your_organization_spacing(self):
"""Test line spacing with about your organization."""
domain_request = completed_domain_request(has_about_your_organization=True)
domain_request = completed_domain_request(
has_about_your_organization=True, user=User.objects.create(username="test", email="[email protected]")
)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
domain_request.submit()
_, kwargs = self.mock_client.send_email.call_args
Expand All @@ -200,7 +263,9 @@ def test_submission_confirmation_about_your_organization_spacing(self):
@less_console_noise_decorator
def test_submission_confirmation_no_about_your_organization_spacing(self):
"""Test line spacing without about your organization."""
domain_request = completed_domain_request(has_about_your_organization=False)
domain_request = completed_domain_request(
has_about_your_organization=False, user=User.objects.create(username="test", email="[email protected]")
)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
domain_request.submit()
_, kwargs = self.mock_client.send_email.call_args
Expand All @@ -213,7 +278,9 @@ def test_submission_confirmation_no_about_your_organization_spacing(self):
@less_console_noise_decorator
def test_submission_confirmation_anything_else_spacing(self):
"""Test line spacing with anything else."""
domain_request = completed_domain_request(has_anything_else=True)
domain_request = completed_domain_request(
has_anything_else=True, user=User.objects.create(username="test", email="[email protected]")
)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
domain_request.submit()
_, kwargs = self.mock_client.send_email.call_args
Expand All @@ -225,7 +292,9 @@ def test_submission_confirmation_anything_else_spacing(self):
@less_console_noise_decorator
def test_submission_confirmation_no_anything_else_spacing(self):
"""Test line spacing without anything else."""
domain_request = completed_domain_request(has_anything_else=False)
domain_request = completed_domain_request(
has_anything_else=False, user=User.objects.create(username="test", email="[email protected]")
)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
domain_request.submit()
_, kwargs = self.mock_client.send_email.call_args
Expand Down
8 changes: 4 additions & 4 deletions src/registrar/tests/test_models_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ def test_submit_from_started_sends_email_to_creator(self):
@less_console_noise_decorator
def test_submit_from_withdrawn_sends_email(self):
msg = "Create a withdrawn domain request and submit it and see if email was sent."
user, _ = User.objects.get_or_create(username="testy")
user, _ = User.objects.get_or_create(username="testy", email="[email protected]")
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.WITHDRAWN, user=user)
self.check_email_sent(domain_request, msg, "submit", 1, expected_content="Hi", expected_email=user.email)

Expand All @@ -324,22 +324,22 @@ def test_submit_from_in_review_does_not_send_email(self):
@less_console_noise_decorator
def test_approve_sends_email(self):
msg = "Create a domain request and approve it and see if email was sent."
user, _ = User.objects.get_or_create(username="testy")
user, _ = User.objects.get_or_create(username="testy", email="[email protected]")
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=user)
self.check_email_sent(domain_request, msg, "approve", 1, expected_content="approved", expected_email=user.email)

@less_console_noise_decorator
def test_withdraw_sends_email(self):
msg = "Create a domain request and withdraw it and see if email was sent."
user, _ = User.objects.get_or_create(username="testy")
user, _ = User.objects.get_or_create(username="testy", email="[email protected]")
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=user)
self.check_email_sent(
domain_request, msg, "withdraw", 1, expected_content="withdrawn", expected_email=user.email
)

def test_reject_sends_email(self):
"Create a domain request and reject it and see if email was sent."
user, _ = User.objects.get_or_create(username="testy")
user, _ = User.objects.get_or_create(username="testy", email="[email protected]")
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.APPROVED, user=user)
expected_email = user.email
email_allowed, _ = AllowedEmail.objects.get_or_create(email=expected_email)
Expand Down
Loading

0 comments on commit 6ebafec

Please sign in to comment.