diff --git a/src/registrar/admin.py b/src/registrar/admin.py index c282b3495..fda1db672 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1973,18 +1973,30 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): # If the status is not mapped properly, saving could cause # weird issues down the line. Instead, we should block this. + # NEEDS A UNIT TEST should_proceed = False - return should_proceed + return (obj, should_proceed) - request_is_not_approved = obj.status != models.DomainRequest.DomainRequestStatus.APPROVED - if request_is_not_approved and not obj.domain_is_not_active(): - # If an admin tried to set an approved domain request to - # another status and the related domain is already - # active, shortcut the action and throw a friendly - # error message. This action would still not go through - # shortcut or not as the rules are duplicated on the model, - # but the error would be an ugly Django error screen. + obj_is_not_approved = obj.status != models.DomainRequest.DomainRequestStatus.APPROVED + if obj_is_not_approved and not obj.domain_is_not_active(): + # REDUNDANT CHECK / ERROR SCREEN AVOIDANCE: + # This action (moving a request from approved to + # another status) when the domain is already active (READY), + # would still not go through even without this check as the rules are + # duplicated in the model and the error is raised from the model. + # This avoids an ugly Django error screen. error_message = "This action is not permitted. The domain is already active." + elif ( + original_obj.status != models.DomainRequest.DomainRequestStatus.APPROVED + and obj.status == models.DomainRequest.DomainRequestStatus.APPROVED + and original_obj.requested_domain is not None + and Domain.objects.filter(name=original_obj.requested_domain.name).exists() + ): + # REDUNDANT CHECK: + # This action (approving a request when the domain exists) + # would still not go through even without this check as the rules are + # duplicated in the model and the error is raised from the model. + error_message = FSMDomainRequestError.get_error_message(FSMErrorCodes.APPROVE_DOMAIN_IN_USE) elif obj.status == models.DomainRequest.DomainRequestStatus.REJECTED and not obj.rejection_reason: # This condition should never be triggered. # The opposite of this condition is acceptable (rejected -> other status and rejection_reason) diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js index d5f6e5818..01b370366 100644 --- a/src/registrar/assets/js/get-gov-admin.js +++ b/src/registrar/assets/js/get-gov-admin.js @@ -848,18 +848,6 @@ document.addEventListener('DOMContentLoaded', function() { } return ''; } - // Extract the submitter name, title, email, and phone number - const submitterDiv = document.querySelector('.form-row.field-submitter'); - const submitterNameElement = document.getElementById('id_submitter'); - // We have to account for different superuser and analyst markups - const submitterName = submitterNameElement - ? submitterNameElement.options[submitterNameElement.selectedIndex].text - : submitterDiv.querySelector('a').text; - const submitterTitle = extractTextById('contact_info_title', submitterDiv); - const submitterEmail = extractTextById('contact_info_email', submitterDiv); - const submitterPhone = extractTextById('contact_info_phone', submitterDiv); - let submitterInfo = `${submitterName}${submitterTitle}${submitterEmail}${submitterPhone}`; - //------ Senior Official const seniorOfficialDiv = document.querySelector('.form-row.field-senior_official'); @@ -876,7 +864,6 @@ document.addEventListener('DOMContentLoaded', function() { `Current Websites: ${existingWebsites.join(', ')}` + `Rationale:` + `Alternative Domains: ${alternativeDomains.join(', ')}` + - `Submitter: ${submitterInfo}` + `Senior Official: ${seniorOfficialInfo}` + `Other Employees: ${otherContactsSummary}`; diff --git a/src/registrar/assets/sass/_theme/_admin.scss b/src/registrar/assets/sass/_theme/_admin.scss index 5cea72c4c..b6bc0d296 100644 --- a/src/registrar/assets/sass/_theme/_admin.scss +++ b/src/registrar/assets/sass/_theme/_admin.scss @@ -385,6 +385,7 @@ a.button, font-kerning: auto; font-family: inherit; font-weight: normal; + text-decoration: none !important; } .button svg, .button span, @@ -392,6 +393,9 @@ a.button, .usa-button--dja span { vertical-align: middle; } +.usa-button--dja.usa-button--unstyled { + color: var(--link-fg); +} .usa-button--dja:not(.usa-button--unstyled, .usa-button--outline, .usa-modal__close, .usa-button--secondary) { background: var(--button-bg); } @@ -421,11 +425,34 @@ input[type=submit].button--dja-toolbar { input[type=submit].button--dja-toolbar:focus, input[type=submit].button--dja-toolbar:hover { border-color: var(--body-quiet-color); } -// Targets the DJA buttom with a nested icon -button .usa-icon, -.button .usa-icon, -.button--clipboard .usa-icon { - vertical-align: middle; +.admin-icon-group { + position: relative; + display: inline; + align-items: center; + + input { + // Allow for padding around the copy button + padding-right: 35px !important; + } + + button { + width: max-content; + } + + @media (max-width: 1000px) { + button { + display: block; + } + } + + span { + padding-left: 0.05rem; + } + +} +.usa-button__small-text, +.usa-button__small-text span { + font-size: 13px; } .module--custom { @@ -673,71 +700,10 @@ address.dja-address-contact-list { } } -// Make the clipboard button "float" inside of the input box -.admin-icon-group { - position: relative; - display: inline; - align-items: center; - - input { - // Allow for padding around the copy button - padding-right: 35px !important; - // Match the height of other inputs - min-height: 2.25rem !important; - } - - button { - line-height: 14px; - width: max-content; - font-size: unset; - text-decoration: none !important; - } - - @media (max-width: 1000px) { - button { - display: block; - padding-top: 8px; - } - } - - span { - padding-left: 0.1rem; - } - -} - -.admin-icon-group.admin-icon-group__clipboard-link { - position: relative; - display: inline; - align-items: center; - - - .usa-button--icon { - position: absolute; - right: auto; - left: 4px; - height: 100%; - top: -1px; - } - button { - font-size: unset !important; - display: inline-flex; - padding-top: 4px; - line-height: 14px; - width: max-content; - font-size: unset; - text-decoration: none !important; - } -} - .no-outline-on-click:focus { outline: none !important; } -.usa-button__small-text { - font-size: small; -} - // Get rid of padding on all help texts form .aligned p.help, form .aligned div.help { padding-left: 0px !important; @@ -887,6 +853,9 @@ div.dja__model-description{ padding-top: 0 !important; } +.padding-bottom-0 { + padding-bottom: 0 !important; +} .flex-container { @media screen and (min-width: 700px) and (max-width: 1150px) { diff --git a/src/registrar/templates/admin/change_form_object_tools.html b/src/registrar/templates/admin/change_form_object_tools.html index 198140c19..66011a3c4 100644 --- a/src/registrar/templates/admin/change_form_object_tools.html +++ b/src/registrar/templates/admin/change_form_object_tools.html @@ -20,10 +20,11 @@ {% if opts.model_name == 'domainrequest' %}
Other contact information | +Other contact information | |||||||
---|---|---|---|---|---|---|---|---|
{{ contact.phone }} | - - + {% endif %} | {% endfor %} diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index cdc3c97de..9c5e3b582 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -654,7 +654,7 @@ class TestDomainInformationAdmin(TestCase): self.test_helper.assert_response_contains_distinct_values(response, expected_other_employees_fields) # Test for the copy link - self.assertContains(response, "button--clipboard", count=3) + self.assertContains(response, "copy-to-clipboard", count=3) # cleanup this test domain_info.delete() diff --git a/src/registrar/tests/test_admin_domain.py b/src/registrar/tests/test_admin_domain.py index a9b94781f..f02b59a91 100644 --- a/src/registrar/tests/test_admin_domain.py +++ b/src/registrar/tests/test_admin_domain.py @@ -535,7 +535,7 @@ class TestDomainAdminWithClient(TestCase): self.assertContains(response, "Testy Tester") # Test for the copy link - self.assertContains(response, "button--clipboard") + self.assertContains(response, "copy-to-clipboard") # cleanup from this test domain.delete() diff --git a/src/registrar/tests/test_admin_request.py b/src/registrar/tests/test_admin_request.py index d5ffa4bc7..cdff63124 100644 --- a/src/registrar/tests/test_admin_request.py +++ b/src/registrar/tests/test_admin_request.py @@ -1527,7 +1527,7 @@ class TestDomainRequestAdmin(MockEppLib): self.test_helper.assert_response_contains_distinct_values(response, expected_other_employees_fields) # Test for the copy link - self.assertContains(response, "button--clipboard", count=4) + self.assertContains(response, "copy-to-clipboard", count=4) # Test that Creator counts display properly self.assertNotContains(response, "Approved domains") @@ -1863,6 +1863,58 @@ class TestDomainRequestAdmin(MockEppLib): def test_side_effects_when_saving_approved_to_ineligible(self): self.trigger_saving_approved_to_another_state(False, DomainRequest.DomainRequestStatus.INELIGIBLE) + @less_console_noise + def test_error_when_saving_to_approved_and_domain_exists(self): + """Redundant admin check on model transition not allowed.""" + Domain.objects.create(name="wabbitseason.gov") + + new_request = completed_domain_request( + status=DomainRequest.DomainRequestStatus.SUBMITTED, name="wabbitseason.gov" + ) + + # Create a request object with a superuser + request = self.factory.post("/admin/registrar/domainrequest/{}/change/".format(new_request.pk)) + request.user = self.superuser + + request.session = {} + + # Use ExitStack to combine patch contexts + with ExitStack() as stack: + # Patch django.contrib.messages.error + stack.enter_context(patch.object(messages, "error")) + + new_request.status = DomainRequest.DomainRequestStatus.APPROVED + + self.admin.save_model(request, new_request, None, True) + + messages.error.assert_called_once_with( + request, + "Cannot approve. Requested domain is already in use.", + ) + + @less_console_noise + def test_no_error_when_saving_to_approved_and_domain_exists(self): + """The negative of the redundant admin check on model transition not allowed.""" + new_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.SUBMITTED) + + # Create a request object with a superuser + request = self.factory.post("/admin/registrar/domainrequest/{}/change/".format(new_request.pk)) + request.user = self.superuser + + request.session = {} + + # Use ExitStack to combine patch contexts + with ExitStack() as stack: + # Patch Domain.is_active and django.contrib.messages.error simultaneously + stack.enter_context(patch.object(messages, "error")) + + new_request.status = DomainRequest.DomainRequestStatus.APPROVED + + self.admin.save_model(request, new_request, None, True) + + # Assert that the error message was never called + messages.error.assert_not_called() + def test_has_correct_filters(self): """ This test verifies that DomainRequestAdmin has the correct filters set up. diff --git a/src/registrar/tests/test_models.py b/src/registrar/tests/test_models.py index 725073ce5..96ca3871f 100644 --- a/src/registrar/tests/test_models.py +++ b/src/registrar/tests/test_models.py @@ -1,7 +1,5 @@ from django.forms import ValidationError from django.test import TestCase -from django.db.utils import IntegrityError -from django.db import transaction from unittest.mock import patch from django.test import RequestFactory @@ -20,23 +18,18 @@ from registrar.models import ( UserPortfolioPermission, AllowedEmail, ) - import boto3_mocking from registrar.models.portfolio import Portfolio from registrar.models.portfolio_invitation import PortfolioInvitation from registrar.models.transition_domain import TransitionDomain from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices from registrar.models.verified_by_staff import VerifiedByStaff # type: ignore -from registrar.utility.constants import BranchChoices from .common import ( MockSESClient, - less_console_noise, completed_domain_request, - set_domain_request_investigators, create_test_user, ) -from django_fsm import TransitionNotAllowed from waffle.testutils import override_flag import logging diff --git a/src/registrar/tests/test_models_requests.py b/src/registrar/tests/test_models_requests.py new file mode 100644 index 000000000..9e86f5f9c --- /dev/null +++ b/src/registrar/tests/test_models_requests.py @@ -0,0 +1,1029 @@ +from django.test import TestCase +from django.db.utils import IntegrityError +from django.db import transaction +from unittest.mock import patch + + +from registrar.models import ( + Contact, + DomainRequest, + DomainInformation, + User, + Website, + Domain, + DraftDomain, + FederalAgency, + AllowedEmail, +) + +import boto3_mocking +from registrar.utility.constants import BranchChoices +from registrar.utility.errors import FSMDomainRequestError + +from .common import ( + MockSESClient, + less_console_noise, + completed_domain_request, + set_domain_request_investigators, +) +from django_fsm import TransitionNotAllowed + +from api.tests.common import less_console_noise_decorator + + +@boto3_mocking.patching +class TestDomainRequest(TestCase): + @less_console_noise_decorator + def setUp(self): + + self.dummy_user, _ = Contact.objects.get_or_create( + email="mayor@igorville.com", first_name="Hello", last_name="World" + ) + self.dummy_user_2, _ = User.objects.get_or_create( + username="intern@igorville.com", email="intern@igorville.com", first_name="Lava", last_name="World" + ) + self.started_domain_request = completed_domain_request( + status=DomainRequest.DomainRequestStatus.STARTED, + name="started.gov", + ) + self.submitted_domain_request = completed_domain_request( + status=DomainRequest.DomainRequestStatus.SUBMITTED, + name="submitted.gov", + ) + self.in_review_domain_request = completed_domain_request( + status=DomainRequest.DomainRequestStatus.IN_REVIEW, + name="in-review.gov", + ) + self.action_needed_domain_request = completed_domain_request( + status=DomainRequest.DomainRequestStatus.ACTION_NEEDED, + name="action-needed.gov", + ) + self.approved_domain_request = completed_domain_request( + status=DomainRequest.DomainRequestStatus.APPROVED, + name="approved.gov", + ) + self.withdrawn_domain_request = completed_domain_request( + status=DomainRequest.DomainRequestStatus.WITHDRAWN, + name="withdrawn.gov", + ) + self.rejected_domain_request = completed_domain_request( + status=DomainRequest.DomainRequestStatus.REJECTED, + name="rejected.gov", + ) + self.ineligible_domain_request = completed_domain_request( + status=DomainRequest.DomainRequestStatus.INELIGIBLE, + name="ineligible.gov", + ) + + # Store all domain request statuses in a variable for ease of use + self.all_domain_requests = [ + self.started_domain_request, + self.submitted_domain_request, + self.in_review_domain_request, + self.action_needed_domain_request, + self.approved_domain_request, + self.withdrawn_domain_request, + self.rejected_domain_request, + self.ineligible_domain_request, + ] + + self.mock_client = MockSESClient() + + def tearDown(self): + super().tearDown() + DomainInformation.objects.all().delete() + DomainRequest.objects.all().delete() + DraftDomain.objects.all().delete() + Domain.objects.all().delete() + User.objects.all().delete() + self.mock_client.EMAILS_SENT.clear() + + def assertNotRaises(self, exception_type): + """Helper method for testing allowed transitions.""" + with less_console_noise(): + return self.assertRaises(Exception, None, exception_type) + + @less_console_noise_decorator + def test_request_is_withdrawable(self): + """Tests the is_withdrawable function""" + domain_request_1 = completed_domain_request( + status=DomainRequest.DomainRequestStatus.SUBMITTED, + name="city2.gov", + ) + domain_request_2 = completed_domain_request( + status=DomainRequest.DomainRequestStatus.IN_REVIEW, + name="city3.gov", + ) + domain_request_3 = completed_domain_request( + status=DomainRequest.DomainRequestStatus.ACTION_NEEDED, + name="city4.gov", + ) + domain_request_4 = completed_domain_request( + status=DomainRequest.DomainRequestStatus.REJECTED, + name="city5.gov", + ) + self.assertTrue(domain_request_1.is_withdrawable()) + self.assertTrue(domain_request_2.is_withdrawable()) + self.assertTrue(domain_request_3.is_withdrawable()) + self.assertFalse(domain_request_4.is_withdrawable()) + + @less_console_noise_decorator + def test_request_is_awaiting_review(self): + """Tests the is_awaiting_review function""" + domain_request_1 = completed_domain_request( + status=DomainRequest.DomainRequestStatus.SUBMITTED, + name="city2.gov", + ) + domain_request_2 = completed_domain_request( + status=DomainRequest.DomainRequestStatus.IN_REVIEW, + name="city3.gov", + ) + domain_request_3 = completed_domain_request( + status=DomainRequest.DomainRequestStatus.ACTION_NEEDED, + name="city4.gov", + ) + domain_request_4 = completed_domain_request( + status=DomainRequest.DomainRequestStatus.REJECTED, + name="city5.gov", + ) + self.assertTrue(domain_request_1.is_awaiting_review()) + self.assertTrue(domain_request_2.is_awaiting_review()) + self.assertFalse(domain_request_3.is_awaiting_review()) + self.assertFalse(domain_request_4.is_awaiting_review()) + + @less_console_noise_decorator + def test_federal_agency_set_to_non_federal_on_approve(self): + """Ensures that when the federal_agency field is 'none' when .approve() is called, + the field is set to the 'Non-Federal Agency' record""" + domain_request = completed_domain_request( + status=DomainRequest.DomainRequestStatus.IN_REVIEW, + name="city2.gov", + federal_agency=None, + ) + + # Ensure that the federal agency is None + self.assertEqual(domain_request.federal_agency, None) + + # Approve the request + domain_request.approve() + self.assertEqual(domain_request.status, DomainRequest.DomainRequestStatus.APPROVED) + + # After approval, it should be "Non-Federal agency" + expected_federal_agency = FederalAgency.objects.filter(agency="Non-Federal Agency").first() + self.assertEqual(domain_request.federal_agency, expected_federal_agency) + + def test_empty_create_fails(self): + """Can't create a completely empty domain request.""" + with less_console_noise(): + with transaction.atomic(): + with self.assertRaisesRegex(IntegrityError, "creator"): + DomainRequest.objects.create() + + @less_console_noise_decorator + def test_minimal_create(self): + """Can create with just a creator.""" + user, _ = User.objects.get_or_create(username="testy") + domain_request = DomainRequest.objects.create(creator=user) + self.assertEqual(domain_request.status, DomainRequest.DomainRequestStatus.STARTED) + + @less_console_noise_decorator + def test_full_create(self): + """Can create with all fields.""" + user, _ = User.objects.get_or_create(username="testy") + contact = Contact.objects.create() + com_website, _ = Website.objects.get_or_create(website="igorville.com") + gov_website, _ = Website.objects.get_or_create(website="igorville.gov") + domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov") + domain_request = DomainRequest.objects.create( + creator=user, + investigator=user, + generic_org_type=DomainRequest.OrganizationChoices.FEDERAL, + federal_type=BranchChoices.EXECUTIVE, + is_election_board=False, + organization_name="Test", + address_line1="100 Main St.", + address_line2="APT 1A", + state_territory="CA", + zipcode="12345-6789", + senior_official=contact, + requested_domain=domain, + purpose="Igorville rules!", + anything_else="All of Igorville loves the dotgov program.", + is_policy_acknowledged=True, + ) + domain_request.current_websites.add(com_website) + domain_request.alternative_domains.add(gov_website) + domain_request.other_contacts.add(contact) + domain_request.save() + + @less_console_noise_decorator + def test_domain_info(self): + """Can create domain info with all fields.""" + user, _ = User.objects.get_or_create(username="testy") + contact = Contact.objects.create() + domain, _ = Domain.objects.get_or_create(name="igorville.gov") + information = DomainInformation.objects.create( + creator=user, + generic_org_type=DomainInformation.OrganizationChoices.FEDERAL, + federal_type=BranchChoices.EXECUTIVE, + is_election_board=False, + organization_name="Test", + address_line1="100 Main St.", + address_line2="APT 1A", + state_territory="CA", + zipcode="12345-6789", + senior_official=contact, + purpose="Igorville rules!", + anything_else="All of Igorville loves the dotgov program.", + is_policy_acknowledged=True, + domain=domain, + ) + information.other_contacts.add(contact) + information.save() + self.assertEqual(information.domain.id, domain.id) + self.assertEqual(information.id, domain.domain_info.id) + + @less_console_noise_decorator + def test_status_fsm_submit_fail(self): + user, _ = User.objects.get_or_create(username="testy") + domain_request = DomainRequest.objects.create(creator=user) + + with boto3_mocking.clients.handler_for("sesv2", self.mock_client): + with less_console_noise(): + with self.assertRaises(ValueError): + # can't submit a domain request with a null domain name + domain_request.submit() + + @less_console_noise_decorator + def test_status_fsm_submit_succeed(self): + user, _ = User.objects.get_or_create(username="testy") + site = DraftDomain.objects.create(name="igorville.gov") + domain_request = DomainRequest.objects.create(creator=user, requested_domain=site) + + # no email sent to creator so this emits a log warning + + with boto3_mocking.clients.handler_for("sesv2", self.mock_client): + with less_console_noise(): + domain_request.submit() + self.assertEqual(domain_request.status, domain_request.DomainRequestStatus.SUBMITTED) + + @less_console_noise_decorator + def check_email_sent( + self, domain_request, msg, action, expected_count, expected_content=None, expected_email="mayor@igorville.com" + ): + """Check if an email was sent after performing an action.""" + email_allowed, _ = AllowedEmail.objects.get_or_create(email=expected_email) + with self.subTest(msg=msg, action=action): + with boto3_mocking.clients.handler_for("sesv2", self.mock_client): + # Perform the specified action + action_method = getattr(domain_request, action) + action_method() + + # Check if an email was sent + sent_emails = [ + email + for email in MockSESClient.EMAILS_SENT + if expected_email in email["kwargs"]["Destination"]["ToAddresses"] + ] + self.assertEqual(len(sent_emails), expected_count) + + if expected_content: + email_content = sent_emails[0]["kwargs"]["Content"]["Simple"]["Body"]["Text"]["Data"] + self.assertIn(expected_content, email_content) + + email_allowed.delete() + + @less_console_noise_decorator + def test_submit_from_started_sends_email_to_creator(self): + """tests that we send an email to the creator""" + msg = "Create a domain request and submit it and see if email was sent when the feature flag is on." + domain_request = completed_domain_request(user=self.dummy_user_2) + self.check_email_sent( + domain_request, msg, "submit", 1, expected_content="Lava", expected_email="intern@igorville.com" + ) + + @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") + 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) + + @less_console_noise_decorator + def test_submit_from_action_needed_does_not_send_email(self): + msg = "Create a domain request with ACTION_NEEDED status and submit it, check if email was not sent." + domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.ACTION_NEEDED) + self.check_email_sent(domain_request, msg, "submit", 0) + + @less_console_noise_decorator + def test_submit_from_in_review_does_not_send_email(self): + msg = "Create a withdrawn domain request and submit it and see if email was sent." + domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW) + self.check_email_sent(domain_request, msg, "submit", 0) + + @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") + 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") + 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 + ) + + @less_console_noise_decorator + def test_reject_sends_email(self): + msg = "Create a domain request and reject it and see if email was sent." + user, _ = User.objects.get_or_create(username="testy") + domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.APPROVED, user=user) + self.check_email_sent(domain_request, msg, "reject", 1, expected_content="Hi", expected_email=user.email) + + @less_console_noise_decorator + def test_reject_with_prejudice_does_not_send_email(self): + msg = "Create a domain request and reject it with prejudice and see if email was sent." + domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.APPROVED) + self.check_email_sent(domain_request, msg, "reject_with_prejudice", 0) + + @less_console_noise_decorator + def assert_fsm_transition_raises_error(self, test_cases, method_to_run): + """Given a list of test cases, check if each transition throws the intended error""" + with boto3_mocking.clients.handler_for("sesv2", self.mock_client), less_console_noise(): + for domain_request, exception_type in test_cases: + with self.subTest(domain_request=domain_request, exception_type=exception_type): + with self.assertRaises(exception_type): + # Retrieve the method by name from the domain_request object and call it + method = getattr(domain_request, method_to_run) + # Call the method + method() + + @less_console_noise_decorator + def assert_fsm_transition_does_not_raise_error(self, test_cases, method_to_run): + """Given a list of test cases, ensure that none of them throw transition errors""" + with boto3_mocking.clients.handler_for("sesv2", self.mock_client), less_console_noise(): + for domain_request, exception_type in test_cases: + with self.subTest(domain_request=domain_request, exception_type=exception_type): + try: + # Retrieve the method by name from the DomainRequest object and call it + method = getattr(domain_request, method_to_run) + # Call the method + method() + except exception_type: + self.fail(f"{exception_type} was raised, but it was not expected.") + + @less_console_noise_decorator + def test_submit_transition_allowed_with_no_investigator(self): + """ + Tests for attempting to transition without an investigator. + For submit, this should be valid in all cases. + """ + + test_cases = [ + (self.started_domain_request, TransitionNotAllowed), + (self.in_review_domain_request, TransitionNotAllowed), + (self.action_needed_domain_request, TransitionNotAllowed), + (self.withdrawn_domain_request, TransitionNotAllowed), + ] + + # Set all investigators to none + set_domain_request_investigators(self.all_domain_requests, None) + + self.assert_fsm_transition_does_not_raise_error(test_cases, "submit") + + @less_console_noise_decorator + def test_submit_transition_allowed_with_investigator_not_staff(self): + """ + Tests for attempting to transition with an investigator user that is not staff. + For submit, this should be valid in all cases. + """ + + test_cases = [ + (self.in_review_domain_request, TransitionNotAllowed), + (self.action_needed_domain_request, TransitionNotAllowed), + ] + + # Set all investigators to a user with no staff privs + user, _ = User.objects.get_or_create(username="pancakesyrup", is_staff=False) + set_domain_request_investigators(self.all_domain_requests, user) + + self.assert_fsm_transition_does_not_raise_error(test_cases, "submit") + + @less_console_noise_decorator + def test_submit_transition_allowed(self): + """ + Test that calling submit from allowable statuses does raises TransitionNotAllowed. + """ + test_cases = [ + (self.started_domain_request, TransitionNotAllowed), + (self.in_review_domain_request, TransitionNotAllowed), + (self.action_needed_domain_request, TransitionNotAllowed), + (self.withdrawn_domain_request, TransitionNotAllowed), + ] + + self.assert_fsm_transition_does_not_raise_error(test_cases, "submit") + + @less_console_noise_decorator + def test_submit_transition_allowed_twice(self): + """ + Test that rotating between submit and in_review doesn't throw an error + """ + with boto3_mocking.clients.handler_for("sesv2", self.mock_client): + try: + # Make a submission + self.in_review_domain_request.submit() + + # Rerun the old method to get back to the original state + self.in_review_domain_request.in_review() + + # Make another submission + self.in_review_domain_request.submit() + except TransitionNotAllowed: + self.fail("TransitionNotAllowed was raised, but it was not expected.") + + self.assertEqual(self.in_review_domain_request.status, DomainRequest.DomainRequestStatus.SUBMITTED) + + @less_console_noise_decorator + def test_submit_transition_not_allowed(self): + """ + Test that calling submit against transition rules raises TransitionNotAllowed. + """ + test_cases = [ + (self.submitted_domain_request, TransitionNotAllowed), + (self.approved_domain_request, TransitionNotAllowed), + (self.rejected_domain_request, TransitionNotAllowed), + (self.ineligible_domain_request, TransitionNotAllowed), + ] + + self.assert_fsm_transition_raises_error(test_cases, "submit") + + @less_console_noise_decorator + def test_in_review_transition_allowed(self): + """ + Test that calling in_review from allowable statuses does raises TransitionNotAllowed. + """ + test_cases = [ + (self.submitted_domain_request, TransitionNotAllowed), + (self.action_needed_domain_request, TransitionNotAllowed), + (self.approved_domain_request, TransitionNotAllowed), + (self.rejected_domain_request, TransitionNotAllowed), + (self.ineligible_domain_request, TransitionNotAllowed), + ] + + self.assert_fsm_transition_does_not_raise_error(test_cases, "in_review") + + @less_console_noise_decorator + def test_in_review_transition_not_allowed_with_no_investigator(self): + """ + Tests for attempting to transition without an investigator + """ + + test_cases = [ + (self.action_needed_domain_request, TransitionNotAllowed), + (self.approved_domain_request, TransitionNotAllowed), + (self.rejected_domain_request, TransitionNotAllowed), + (self.ineligible_domain_request, TransitionNotAllowed), + ] + + # Set all investigators to none + set_domain_request_investigators(self.all_domain_requests, None) + + self.assert_fsm_transition_raises_error(test_cases, "in_review") + + @less_console_noise_decorator + def test_in_review_transition_not_allowed_with_investigator_not_staff(self): + """ + Tests for attempting to transition with an investigator that is not staff. + This should throw an exception. + """ + + test_cases = [ + (self.action_needed_domain_request, TransitionNotAllowed), + (self.approved_domain_request, TransitionNotAllowed), + (self.rejected_domain_request, TransitionNotAllowed), + (self.ineligible_domain_request, TransitionNotAllowed), + ] + + # Set all investigators to a user with no staff privs + user, _ = User.objects.get_or_create(username="pancakesyrup", is_staff=False) + set_domain_request_investigators(self.all_domain_requests, user) + + self.assert_fsm_transition_raises_error(test_cases, "in_review") + + @less_console_noise_decorator + def test_in_review_transition_not_allowed(self): + """ + Test that calling in_review against transition rules raises TransitionNotAllowed. + """ + test_cases = [ + (self.started_domain_request, TransitionNotAllowed), + (self.in_review_domain_request, TransitionNotAllowed), + (self.withdrawn_domain_request, TransitionNotAllowed), + ] + + self.assert_fsm_transition_raises_error(test_cases, "in_review") + + @less_console_noise_decorator + def test_action_needed_transition_allowed(self): + """ + Test that calling action_needed from allowable statuses does raises TransitionNotAllowed. + """ + test_cases = [ + (self.in_review_domain_request, TransitionNotAllowed), + (self.approved_domain_request, TransitionNotAllowed), + (self.rejected_domain_request, TransitionNotAllowed), + (self.ineligible_domain_request, TransitionNotAllowed), + ] + + self.assert_fsm_transition_does_not_raise_error(test_cases, "action_needed") + + @less_console_noise_decorator + def test_action_needed_transition_not_allowed_with_no_investigator(self): + """ + Tests for attempting to transition without an investigator + """ + + test_cases = [ + (self.in_review_domain_request, TransitionNotAllowed), + (self.approved_domain_request, TransitionNotAllowed), + (self.rejected_domain_request, TransitionNotAllowed), + (self.ineligible_domain_request, TransitionNotAllowed), + ] + + # Set all investigators to none + set_domain_request_investigators(self.all_domain_requests, None) + + self.assert_fsm_transition_raises_error(test_cases, "action_needed") + + @less_console_noise_decorator + def test_action_needed_transition_not_allowed_with_investigator_not_staff(self): + """ + Tests for attempting to transition with an investigator that is not staff + """ + + test_cases = [ + (self.in_review_domain_request, TransitionNotAllowed), + (self.approved_domain_request, TransitionNotAllowed), + (self.rejected_domain_request, TransitionNotAllowed), + (self.ineligible_domain_request, TransitionNotAllowed), + ] + + # Set all investigators to a user with no staff privs + user, _ = User.objects.get_or_create(username="pancakesyrup", is_staff=False) + set_domain_request_investigators(self.all_domain_requests, user) + + self.assert_fsm_transition_raises_error(test_cases, "action_needed") + + @less_console_noise_decorator + def test_action_needed_transition_not_allowed(self): + """ + Test that calling action_needed against transition rules raises TransitionNotAllowed. + """ + test_cases = [ + (self.started_domain_request, TransitionNotAllowed), + (self.submitted_domain_request, TransitionNotAllowed), + (self.action_needed_domain_request, TransitionNotAllowed), + (self.withdrawn_domain_request, TransitionNotAllowed), + ] + + self.assert_fsm_transition_raises_error(test_cases, "action_needed") + + @less_console_noise_decorator + def test_approved_transition_allowed(self): + """ + Test that calling action_needed from allowable statuses does raises TransitionNotAllowed. + """ + test_cases = [ + (self.submitted_domain_request, TransitionNotAllowed), + (self.in_review_domain_request, TransitionNotAllowed), + (self.action_needed_domain_request, TransitionNotAllowed), + (self.rejected_domain_request, TransitionNotAllowed), + ] + + self.assert_fsm_transition_does_not_raise_error(test_cases, "approve") + + @less_console_noise_decorator + def test_approved_transition_not_allowed_with_no_investigator(self): + """ + Tests for attempting to transition without an investigator + """ + + test_cases = [ + (self.in_review_domain_request, TransitionNotAllowed), + (self.action_needed_domain_request, TransitionNotAllowed), + (self.rejected_domain_request, TransitionNotAllowed), + ] + + # Set all investigators to none + set_domain_request_investigators(self.all_domain_requests, None) + + self.assert_fsm_transition_raises_error(test_cases, "approve") + + @less_console_noise_decorator + def test_approved_transition_not_allowed_with_investigator_not_staff(self): + """ + Tests for attempting to transition with an investigator that is not staff + """ + + test_cases = [ + (self.in_review_domain_request, TransitionNotAllowed), + (self.action_needed_domain_request, TransitionNotAllowed), + (self.rejected_domain_request, TransitionNotAllowed), + ] + + # Set all investigators to a user with no staff privs + user, _ = User.objects.get_or_create(username="pancakesyrup", is_staff=False) + set_domain_request_investigators(self.all_domain_requests, user) + + self.assert_fsm_transition_raises_error(test_cases, "approve") + + @less_console_noise_decorator + 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): + self.submitted_domain_request.approve(send_email=False) + + # Assert that no emails were sent + self.assertEqual(len(self.mock_client.EMAILS_SENT), 0) + + @less_console_noise_decorator + def test_approved_transition_not_allowed(self): + """ + Test that calling approve against transition rules raises TransitionNotAllowed. + """ + test_cases = [ + (self.started_domain_request, TransitionNotAllowed), + (self.approved_domain_request, TransitionNotAllowed), + (self.withdrawn_domain_request, TransitionNotAllowed), + (self.ineligible_domain_request, TransitionNotAllowed), + ] + self.assert_fsm_transition_raises_error(test_cases, "approve") + + @less_console_noise_decorator + def test_approved_transition_not_allowed_when_domain_already_approved(self): + """ + Test that calling approve whith an already approved requested domain raises + TransitionNotAllowed. + """ + Domain.objects.all().create(name=self.submitted_domain_request.requested_domain.name) + test_cases = [ + (self.submitted_domain_request, FSMDomainRequestError), + ] + self.assert_fsm_transition_raises_error(test_cases, "approve") + + @less_console_noise_decorator + def test_withdraw_transition_allowed(self): + """ + Test that calling action_needed from allowable statuses does raises TransitionNotAllowed. + """ + test_cases = [ + (self.submitted_domain_request, TransitionNotAllowed), + (self.in_review_domain_request, TransitionNotAllowed), + (self.action_needed_domain_request, TransitionNotAllowed), + ] + + self.assert_fsm_transition_does_not_raise_error(test_cases, "withdraw") + + @less_console_noise_decorator + def test_withdraw_transition_allowed_with_no_investigator(self): + """ + Tests for attempting to transition without an investigator. + For withdraw, this should be valid in all cases. + """ + + test_cases = [ + (self.submitted_domain_request, TransitionNotAllowed), + (self.in_review_domain_request, TransitionNotAllowed), + (self.action_needed_domain_request, TransitionNotAllowed), + ] + + # Set all investigators to none + set_domain_request_investigators(self.all_domain_requests, None) + + self.assert_fsm_transition_does_not_raise_error(test_cases, "withdraw") + + @less_console_noise_decorator + def test_withdraw_transition_allowed_with_investigator_not_staff(self): + """ + Tests for attempting to transition when investigator is not staff. + For withdraw, this should be valid in all cases. + """ + + test_cases = [ + (self.submitted_domain_request, TransitionNotAllowed), + (self.in_review_domain_request, TransitionNotAllowed), + (self.action_needed_domain_request, TransitionNotAllowed), + ] + + # Set all investigators to a user with no staff privs + user, _ = User.objects.get_or_create(username="pancakesyrup", is_staff=False) + set_domain_request_investigators(self.all_domain_requests, user) + + self.assert_fsm_transition_does_not_raise_error(test_cases, "withdraw") + + @less_console_noise_decorator + def test_withdraw_transition_not_allowed(self): + """ + Test that calling action_needed against transition rules raises TransitionNotAllowed. + """ + test_cases = [ + (self.started_domain_request, TransitionNotAllowed), + (self.approved_domain_request, TransitionNotAllowed), + (self.withdrawn_domain_request, TransitionNotAllowed), + (self.rejected_domain_request, TransitionNotAllowed), + (self.ineligible_domain_request, TransitionNotAllowed), + ] + + self.assert_fsm_transition_raises_error(test_cases, "withdraw") + + @less_console_noise_decorator + def test_reject_transition_allowed(self): + """ + Test that calling action_needed from allowable statuses does raises TransitionNotAllowed. + """ + test_cases = [ + (self.in_review_domain_request, TransitionNotAllowed), + (self.action_needed_domain_request, TransitionNotAllowed), + (self.approved_domain_request, TransitionNotAllowed), + ] + + self.assert_fsm_transition_does_not_raise_error(test_cases, "reject") + + @less_console_noise_decorator + def test_reject_transition_not_allowed_with_no_investigator(self): + """ + Tests for attempting to transition without an investigator + """ + + test_cases = [ + (self.in_review_domain_request, TransitionNotAllowed), + (self.action_needed_domain_request, TransitionNotAllowed), + (self.approved_domain_request, TransitionNotAllowed), + ] + + # Set all investigators to none + set_domain_request_investigators(self.all_domain_requests, None) + + self.assert_fsm_transition_raises_error(test_cases, "reject") + + @less_console_noise_decorator + def test_reject_transition_not_allowed_with_investigator_not_staff(self): + """ + Tests for attempting to transition when investigator is not staff + """ + + test_cases = [ + (self.in_review_domain_request, TransitionNotAllowed), + (self.action_needed_domain_request, TransitionNotAllowed), + (self.approved_domain_request, TransitionNotAllowed), + ] + + # Set all investigators to a user with no staff privs + user, _ = User.objects.get_or_create(username="pancakesyrup", is_staff=False) + set_domain_request_investigators(self.all_domain_requests, user) + + self.assert_fsm_transition_raises_error(test_cases, "reject") + + @less_console_noise_decorator + def test_reject_transition_not_allowed(self): + """ + Test that calling action_needed against transition rules raises TransitionNotAllowed. + """ + test_cases = [ + (self.started_domain_request, TransitionNotAllowed), + (self.submitted_domain_request, TransitionNotAllowed), + (self.withdrawn_domain_request, TransitionNotAllowed), + (self.rejected_domain_request, TransitionNotAllowed), + (self.ineligible_domain_request, TransitionNotAllowed), + ] + + self.assert_fsm_transition_raises_error(test_cases, "reject") + + @less_console_noise_decorator + def test_reject_with_prejudice_transition_allowed(self): + """ + Test that calling action_needed from allowable statuses does raises TransitionNotAllowed. + """ + test_cases = [ + (self.in_review_domain_request, TransitionNotAllowed), + (self.action_needed_domain_request, TransitionNotAllowed), + (self.approved_domain_request, TransitionNotAllowed), + (self.rejected_domain_request, TransitionNotAllowed), + ] + + self.assert_fsm_transition_does_not_raise_error(test_cases, "reject_with_prejudice") + + @less_console_noise_decorator + def test_reject_with_prejudice_transition_not_allowed_with_no_investigator(self): + """ + Tests for attempting to transition without an investigator + """ + + test_cases = [ + (self.in_review_domain_request, TransitionNotAllowed), + (self.action_needed_domain_request, TransitionNotAllowed), + (self.approved_domain_request, TransitionNotAllowed), + (self.rejected_domain_request, TransitionNotAllowed), + ] + + # Set all investigators to none + set_domain_request_investigators(self.all_domain_requests, None) + + self.assert_fsm_transition_raises_error(test_cases, "reject_with_prejudice") + + @less_console_noise_decorator + def test_reject_with_prejudice_not_allowed_with_investigator_not_staff(self): + """ + Tests for attempting to transition when investigator is not staff + """ + + test_cases = [ + (self.in_review_domain_request, TransitionNotAllowed), + (self.action_needed_domain_request, TransitionNotAllowed), + (self.approved_domain_request, TransitionNotAllowed), + (self.rejected_domain_request, TransitionNotAllowed), + ] + + # Set all investigators to a user with no staff privs + user, _ = User.objects.get_or_create(username="pancakesyrup", is_staff=False) + set_domain_request_investigators(self.all_domain_requests, user) + + self.assert_fsm_transition_raises_error(test_cases, "reject_with_prejudice") + + @less_console_noise_decorator + def test_reject_with_prejudice_transition_not_allowed(self): + """ + Test that calling action_needed against transition rules raises TransitionNotAllowed. + """ + test_cases = [ + (self.started_domain_request, TransitionNotAllowed), + (self.submitted_domain_request, TransitionNotAllowed), + (self.withdrawn_domain_request, TransitionNotAllowed), + (self.ineligible_domain_request, TransitionNotAllowed), + ] + + self.assert_fsm_transition_raises_error(test_cases, "reject_with_prejudice") + + @less_console_noise_decorator + def test_transition_not_allowed_approved_in_review_when_domain_is_active(self): + """Create a domain request with status approved, create a matching domain that + is active, and call in_review against transition rules""" + + domain = Domain.objects.create(name=self.approved_domain_request.requested_domain.name) + self.approved_domain_request.approved_domain = domain + self.approved_domain_request.save() + + # Define a custom implementation for is_active + def custom_is_active(self): + return True # Override to return True + + with boto3_mocking.clients.handler_for("sesv2", self.mock_client): + # Use patch to temporarily replace is_active with the custom implementation + with patch.object(Domain, "is_active", custom_is_active): + # Now, when you call is_active on Domain, it will return True + with self.assertRaises(TransitionNotAllowed): + self.approved_domain_request.in_review() + + @less_console_noise_decorator + def test_transition_not_allowed_approved_action_needed_when_domain_is_active(self): + """Create a domain request with status approved, create a matching domain that + is active, and call action_needed against transition rules""" + + domain = Domain.objects.create(name=self.approved_domain_request.requested_domain.name) + self.approved_domain_request.approved_domain = domain + self.approved_domain_request.save() + + # Define a custom implementation for is_active + def custom_is_active(self): + return True # Override to return True + + with boto3_mocking.clients.handler_for("sesv2", self.mock_client): + # Use patch to temporarily replace is_active with the custom implementation + with patch.object(Domain, "is_active", custom_is_active): + # Now, when you call is_active on Domain, it will return True + with self.assertRaises(TransitionNotAllowed): + self.approved_domain_request.action_needed() + + @less_console_noise_decorator + def test_transition_not_allowed_approved_rejected_when_domain_is_active(self): + """Create a domain request with status approved, create a matching domain that + is active, and call reject against transition rules""" + + domain = Domain.objects.create(name=self.approved_domain_request.requested_domain.name) + self.approved_domain_request.approved_domain = domain + self.approved_domain_request.save() + + # Define a custom implementation for is_active + def custom_is_active(self): + return True # Override to return True + + with boto3_mocking.clients.handler_for("sesv2", self.mock_client): + # Use patch to temporarily replace is_active with the custom implementation + with patch.object(Domain, "is_active", custom_is_active): + # Now, when you call is_active on Domain, it will return True + with self.assertRaises(TransitionNotAllowed): + self.approved_domain_request.reject() + + @less_console_noise_decorator + def test_transition_not_allowed_approved_ineligible_when_domain_is_active(self): + """Create a domain request with status approved, create a matching domain that + is active, and call reject_with_prejudice against transition rules""" + + domain = Domain.objects.create(name=self.approved_domain_request.requested_domain.name) + self.approved_domain_request.approved_domain = domain + self.approved_domain_request.save() + + # Define a custom implementation for is_active + def custom_is_active(self): + return True # Override to return True + + with boto3_mocking.clients.handler_for("sesv2", self.mock_client): + # Use patch to temporarily replace is_active with the custom implementation + with patch.object(Domain, "is_active", custom_is_active): + # Now, when you call is_active on Domain, it will return True + with self.assertRaises(TransitionNotAllowed): + self.approved_domain_request.reject_with_prejudice() + + @less_console_noise_decorator + def test_approve_from_rejected_clears_rejection_reason(self): + """When transitioning from rejected to approved on a domain request, + the rejection_reason is cleared.""" + + # Create a sample domain request + domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.REJECTED) + domain_request.rejection_reason = DomainRequest.RejectionReasons.DOMAIN_PURPOSE + + # Approve + with boto3_mocking.clients.handler_for("sesv2", self.mock_client): + domain_request.approve() + + self.assertEqual(domain_request.status, DomainRequest.DomainRequestStatus.APPROVED) + self.assertEqual(domain_request.rejection_reason, None) + + @less_console_noise_decorator + def test_in_review_from_rejected_clears_rejection_reason(self): + """When transitioning from rejected to in_review on a domain request, + the rejection_reason is cleared.""" + + # Create a sample domain request + domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.REJECTED) + domain_request.domain_is_not_active = True + domain_request.rejection_reason = DomainRequest.RejectionReasons.DOMAIN_PURPOSE + + # Approve + with boto3_mocking.clients.handler_for("sesv2", self.mock_client): + domain_request.in_review() + + self.assertEqual(domain_request.status, DomainRequest.DomainRequestStatus.IN_REVIEW) + self.assertEqual(domain_request.rejection_reason, None) + + @less_console_noise_decorator + def test_action_needed_from_rejected_clears_rejection_reason(self): + """When transitioning from rejected to action_needed on a domain request, + the rejection_reason is cleared.""" + + # Create a sample domain request + domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.REJECTED) + domain_request.domain_is_not_active = True + domain_request.rejection_reason = DomainRequest.RejectionReasons.DOMAIN_PURPOSE + + # Approve + with boto3_mocking.clients.handler_for("sesv2", self.mock_client): + domain_request.action_needed() + + self.assertEqual(domain_request.status, DomainRequest.DomainRequestStatus.ACTION_NEEDED) + self.assertEqual(domain_request.rejection_reason, None) + + @less_console_noise_decorator + def test_has_rationale_returns_true(self): + """has_rationale() returns true when a domain request has no_other_contacts_rationale""" + self.started_domain_request.no_other_contacts_rationale = "You talkin' to me?" + self.started_domain_request.save() + self.assertEquals(self.started_domain_request.has_rationale(), True) + + @less_console_noise_decorator + def test_has_rationale_returns_false(self): + """has_rationale() returns false when a domain request has no no_other_contacts_rationale""" + self.assertEquals(self.started_domain_request.has_rationale(), False) + + @less_console_noise_decorator + def test_has_other_contacts_returns_true(self): + """has_other_contacts() returns true when a domain request has other_contacts""" + # completed_domain_request has other contacts by default + self.assertEquals(self.started_domain_request.has_other_contacts(), True) + + @less_console_noise_decorator + def test_has_other_contacts_returns_false(self): + """has_other_contacts() returns false when a domain request has no other_contacts""" + domain_request = completed_domain_request( + status=DomainRequest.DomainRequestStatus.STARTED, name="no-others.gov", has_other_contacts=False + ) + self.assertEquals(domain_request.has_other_contacts(), False) diff --git a/src/registrar/tests/test_views_request.py b/src/registrar/tests/test_views_request.py index 8530859e2..ff2e61939 100644 --- a/src/registrar/tests/test_views_request.py +++ b/src/registrar/tests/test_views_request.py @@ -82,7 +82,6 @@ class DomainRequestTests(TestWithUser, WebTest): response = self.app.get(f"/domain-request/{domain_request.id}") # Ensure that the date is still set to None self.assertIsNone(domain_request.last_status_update) - print(response) # We should still grab a date for this field in this event - but it should come from the audit log instead self.assertContains(response, "Started on:") self.assertContains(response, fixed_date.strftime("%B %-d, %Y"))