'
- not_expected_content = "submit-row-wrapper--analyst-view>"
- self.assertContains(request, expected_content)
- self.assertContains(request, expected_content2)
- self.assertContains(request, expected_content3)
- self.assertNotContains(request, not_expected_content)
-
- @less_console_noise_decorator
- def test_sticky_submit_row_has_extra_class_for_analysts(self):
- """Test that the change_form template contains strings indicative of the customization
- of the sticky submit bar.
-
- Also test that it DOES contain a CSS class meant for analysts only when logged in as analyst."""
-
- # make sure there is no user with this email
- EMAIL = "mayor@igorville.gov"
- User.objects.filter(email=EMAIL).delete()
- self.client.force_login(self.staffuser)
-
- # Create a sample domain request
- domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
-
- # Create a mock request
- request = self.client.post("/admin/registrar/domainrequest/{}/change/".format(domain_request.pk))
-
- # Since we're using client to mock the request, we can only test against
- # non-interpolated values
- expected_content = "Requested domain:"
- expected_content2 = '
'
- self.assertContains(request, expected_content)
- self.assertContains(request, expected_content2)
- self.assertContains(request, expected_content3)
-
- def test_other_contacts_has_readonly_link(self):
- """Tests if the readonly other_contacts field has links"""
-
- # Create a fake domain request
- domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
-
- # Get the other contact
- other_contact = domain_request.other_contacts.all().first()
-
- p = "userpass"
- self.client.login(username="staffuser", password=p)
- response = self.client.get(
- "/admin/registrar/domainrequest/{}/change/".format(domain_request.pk),
- follow=True,
- )
-
- # Make sure the page loaded, and that we're on the right page
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, domain_request.requested_domain.name)
-
- # Check that the page contains the url we expect
- expected_href = reverse("admin:registrar_contact_change", args=[other_contact.id])
- self.assertContains(response, expected_href)
-
- # Check that the page contains the link we expect.
- # Since the url is dynamic (populated by JS), we can test for its existence
- # by checking for the end tag.
- expected_url = "Testy Tester"
- self.assertContains(response, expected_url)
-
- @less_console_noise_decorator
- def test_other_websites_has_readonly_link(self):
- """Tests if the readonly other_websites field has links"""
-
- # Create a fake domain request
- domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
-
- p = "userpass"
- self.client.login(username="staffuser", password=p)
- response = self.client.get(
- "/admin/registrar/domainrequest/{}/change/".format(domain_request.pk),
- follow=True,
- )
-
- # Make sure the page loaded, and that we're on the right page
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, domain_request.requested_domain.name)
-
- # Check that the page contains the link we expect.
- expected_url = '
city.com'
- self.assertContains(response, expected_url)
-
- @less_console_noise_decorator
- def test_contact_fields_have_detail_table(self):
- """Tests if the contact fields have the detail table which displays title, email, and phone"""
-
- # Create fake creator
- _creator = User.objects.create(
- username="MrMeoward",
- first_name="Meoward",
- last_name="Jones",
- )
-
- # Due to the relation between User <==> Contact,
- # the underlying contact has to be modified this way.
- _creator.contact.email = "meoward.jones@igorville.gov"
- _creator.contact.phone = "(555) 123 12345"
- _creator.contact.title = "Treat inspector"
- _creator.contact.save()
-
- # Create a fake domain request
- domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator)
-
- p = "userpass"
- self.client.login(username="staffuser", password=p)
- response = self.client.get(
- "/admin/registrar/domainrequest/{}/change/".format(domain_request.pk),
- follow=True,
- )
-
- # Make sure the page loaded, and that we're on the right page
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, domain_request.requested_domain.name)
-
- # == Check for the creator == #
-
- # Check for the right title, email, and phone number in the response.
- expected_creator_fields = [
- # Field, expected value
- ("title", "Treat inspector"),
- ("email", "meoward.jones@igorville.gov"),
- ("phone", "(555) 123 12345"),
- ]
- self.test_helper.assert_response_contains_distinct_values(response, expected_creator_fields)
-
- # Check for the field itself
- self.assertContains(response, "Meoward Jones")
-
- # == Check for the submitter == #
- self.assertContains(response, "mayor@igorville.gov", count=2)
- expected_submitter_fields = [
- # Field, expected value
- ("title", "Admin Tester"),
- ("phone", "(555) 555 5556"),
- ]
- self.test_helper.assert_response_contains_distinct_values(response, expected_submitter_fields)
- self.assertContains(response, "Testy2 Tester2")
-
- # == Check for the senior_official == #
- self.assertContains(response, "testy@town.com", count=2)
- expected_so_fields = [
- # Field, expected value
- ("phone", "(555) 555 5555"),
- ]
-
- self.test_helper.assert_response_contains_distinct_values(response, expected_so_fields)
- self.assertContains(response, "Chief Tester")
-
- # == Test the other_employees field == #
- self.assertContains(response, "testy2@town.com")
- expected_other_employees_fields = [
- # Field, expected value
- ("title", "Another Tester"),
- ("phone", "(555) 555 5557"),
- ]
- self.test_helper.assert_response_contains_distinct_values(response, expected_other_employees_fields)
-
- # Test for the copy link
- self.assertContains(response, "usa-button__clipboard", count=4)
-
- # Test that Creator counts display properly
- self.assertNotContains(response, "Approved domains")
- self.assertContains(response, "Active requests")
-
- def test_save_model_sets_restricted_status_on_user(self):
- with less_console_noise():
- # make sure there is no user with this email
- EMAIL = "mayor@igorville.gov"
- User.objects.filter(email=EMAIL).delete()
-
- # Create a sample domain request
- domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
-
- # Create a mock request
- request = self.factory.post(
- "/admin/registrar/domainrequest/{}/change/".format(domain_request.pk), follow=True
- )
-
- with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
- # Modify the domain request's property
- domain_request.status = DomainRequest.DomainRequestStatus.INELIGIBLE
-
- # Use the model admin's save_model method
- self.admin.save_model(request, domain_request, form=None, change=True)
-
- # Test that approved domain exists and equals requested domain
- self.assertEqual(domain_request.creator.status, "restricted")
-
- def test_user_sets_restricted_status_modal(self):
- """Tests the modal for when a user sets the status to restricted"""
- with less_console_noise():
- # make sure there is no user with this email
- EMAIL = "mayor@igorville.gov"
- User.objects.filter(email=EMAIL).delete()
-
- # Create a sample domain request
- domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
-
- p = "userpass"
- self.client.login(username="staffuser", password=p)
- response = self.client.get(
- "/admin/registrar/domainrequest/{}/change/".format(domain_request.pk),
- follow=True,
- )
-
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, domain_request.requested_domain.name)
-
- # Check that the modal has the right content
- # Check for the header
- self.assertContains(response, "Are you sure you want to select ineligible status?")
-
- # Check for some of its body
- self.assertContains(response, "When a domain request is in ineligible status")
-
- # Check for some of the button content
- self.assertContains(response, "Yes, select ineligible status")
-
- # Create a mock request
- request = self.factory.post(
- "/admin/registrar/domainrequest{}/change/".format(domain_request.pk), follow=True
- )
- with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
- # Modify the domain request's property
- domain_request.status = DomainRequest.DomainRequestStatus.INELIGIBLE
-
- # Use the model admin's save_model method
- self.admin.save_model(request, domain_request, form=None, change=True)
-
- # Test that approved domain exists and equals requested domain
- self.assertEqual(domain_request.creator.status, "restricted")
-
- # 'Get' to the domain request again
- response = self.client.get(
- "/admin/registrar/domainrequest/{}/change/".format(domain_request.pk),
- follow=True,
- )
-
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, domain_request.requested_domain.name)
-
- # The modal should be unchanged
- self.assertContains(response, "Are you sure you want to select ineligible status?")
- self.assertContains(response, "When a domain request is in ineligible status")
- self.assertContains(response, "Yes, select ineligible status")
-
- def test_readonly_when_restricted_creator(self):
- with less_console_noise():
- domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
- with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
- domain_request.creator.status = User.RESTRICTED
- domain_request.creator.save()
-
- request = self.factory.get("/")
- request.user = self.superuser
-
- readonly_fields = self.admin.get_readonly_fields(request, domain_request)
-
- expected_fields = [
- "other_contacts",
- "current_websites",
- "alternative_domains",
- "is_election_board",
- "federal_agency",
- "id",
- "created_at",
- "updated_at",
- "status",
- "rejection_reason",
- "action_needed_reason",
- "action_needed_reason_email",
- "federal_agency",
- "portfolio",
- "sub_organization",
- "creator",
- "investigator",
- "generic_org_type",
- "is_election_board",
- "organization_type",
- "federally_recognized_tribe",
- "state_recognized_tribe",
- "tribe_name",
- "federal_type",
- "organization_name",
- "address_line1",
- "address_line2",
- "city",
- "state_territory",
- "zipcode",
- "urbanization",
- "about_your_organization",
- "senior_official",
- "approved_domain",
- "requested_domain",
- "submitter",
- "purpose",
- "no_other_contacts_rationale",
- "anything_else",
- "has_anything_else_text",
- "cisa_representative_email",
- "cisa_representative_first_name",
- "cisa_representative_last_name",
- "has_cisa_representative",
- "is_policy_acknowledged",
- "submission_date",
- "notes",
- "alternative_domains",
- ]
-
- self.assertEqual(readonly_fields, expected_fields)
-
- def test_readonly_fields_for_analyst(self):
- with less_console_noise():
- request = self.factory.get("/") # Use the correct method and path
- request.user = self.staffuser
-
- readonly_fields = self.admin.get_readonly_fields(request)
-
- expected_fields = [
- "other_contacts",
- "current_websites",
- "alternative_domains",
- "is_election_board",
- "federal_agency",
- "creator",
- "about_your_organization",
- "requested_domain",
- "approved_domain",
- "alternative_domains",
- "purpose",
- "submitter",
- "no_other_contacts_rationale",
- "anything_else",
- "is_policy_acknowledged",
- "cisa_representative_first_name",
- "cisa_representative_last_name",
- "cisa_representative_email",
- ]
-
- self.assertEqual(readonly_fields, expected_fields)
-
- def test_readonly_fields_for_superuser(self):
- with less_console_noise():
- request = self.factory.get("/") # Use the correct method and path
- request.user = self.superuser
-
- readonly_fields = self.admin.get_readonly_fields(request)
-
- expected_fields = [
- "other_contacts",
- "current_websites",
- "alternative_domains",
- "is_election_board",
- "federal_agency",
- ]
-
- self.assertEqual(readonly_fields, expected_fields)
-
- def test_saving_when_restricted_creator(self):
- with less_console_noise():
- # Create an instance of the model
- domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
- with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
- domain_request.creator.status = User.RESTRICTED
- domain_request.creator.save()
-
- # Create a request object with a superuser
- request = self.factory.get("/")
- request.user = self.superuser
-
- with patch("django.contrib.messages.error") as mock_error:
- # Simulate saving the model
- self.admin.save_model(request, domain_request, None, False)
-
- # Assert that the error message was called with the correct argument
- mock_error.assert_called_once_with(
- request,
- "This action is not permitted for domain requests with a restricted creator.",
- )
-
- # Assert that the status has not changed
- self.assertEqual(domain_request.status, DomainRequest.DomainRequestStatus.IN_REVIEW)
-
- def test_change_view_with_restricted_creator(self):
- with less_console_noise():
- # Create an instance of the model
- domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
- with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
- domain_request.creator.status = User.RESTRICTED
- domain_request.creator.save()
-
- with patch("django.contrib.messages.warning") as mock_warning:
- # Create a request object with a superuser
- request = self.factory.get("/admin/your_app/domainrequest/{}/change/".format(domain_request.pk))
- request.user = self.superuser
-
- self.admin.display_restricted_warning(request, domain_request)
-
- # Assert that the error message was called with the correct argument
- mock_warning.assert_called_once_with(
- request,
- "Cannot edit a domain request with a restricted creator.",
- )
-
- 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.
-
- Used to test errors when saving a change with an active domain, also used to test side effects
- when saving a change goes through."""
-
- with less_console_noise():
- # Create an instance of the model
- domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.APPROVED)
- domain = Domain.objects.create(name=domain_request.requested_domain.name)
- domain_information = DomainInformation.objects.create(creator=self.superuser, domain=domain)
- domain_request.approved_domain = domain
- domain_request.save()
-
- # Create a request object with a superuser
- request = self.factory.post("/admin/registrar/domainrequest/{}/change/".format(domain_request.pk))
- request.user = self.superuser
-
- # Define a custom implementation for is_active
- def custom_is_active(self):
- return domain_is_active # Override to return True
-
- # 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(Domain, "is_active", custom_is_active))
- stack.enter_context(patch.object(messages, "error"))
-
- domain_request.status = another_state
-
- if another_state == DomainRequest.DomainRequestStatus.ACTION_NEEDED:
- domain_request.action_needed_reason = domain_request.ActionNeededReasons.OTHER
-
- domain_request.rejection_reason = rejection_reason
-
- self.admin.save_model(request, domain_request, None, True)
-
- # Assert that the error message was called with the correct argument
- if domain_is_active:
- messages.error.assert_called_once_with(
- request,
- "This action is not permitted. The domain " + "is already active.",
- )
- else:
- # Assert that the error message was never called
- messages.error.assert_not_called()
-
- self.assertEqual(domain_request.approved_domain, None)
-
- # Assert that Domain got Deleted
- with self.assertRaises(Domain.DoesNotExist):
- domain.refresh_from_db()
-
- # Assert that DomainInformation got Deleted
- with self.assertRaises(DomainInformation.DoesNotExist):
- domain_information.refresh_from_db()
-
- def test_error_when_saving_approved_to_in_review_and_domain_is_active(self):
- self.trigger_saving_approved_to_another_state(True, DomainRequest.DomainRequestStatus.IN_REVIEW)
-
- def test_error_when_saving_approved_to_action_needed_and_domain_is_active(self):
- self.trigger_saving_approved_to_another_state(True, DomainRequest.DomainRequestStatus.ACTION_NEEDED)
-
- def test_error_when_saving_approved_to_rejected_and_domain_is_active(self):
- self.trigger_saving_approved_to_another_state(True, DomainRequest.DomainRequestStatus.REJECTED)
-
- def test_error_when_saving_approved_to_ineligible_and_domain_is_active(self):
- self.trigger_saving_approved_to_another_state(True, DomainRequest.DomainRequestStatus.INELIGIBLE)
-
- def test_side_effects_when_saving_approved_to_in_review(self):
- self.trigger_saving_approved_to_another_state(False, DomainRequest.DomainRequestStatus.IN_REVIEW)
-
- def test_side_effects_when_saving_approved_to_action_needed(self):
- self.trigger_saving_approved_to_another_state(False, DomainRequest.DomainRequestStatus.ACTION_NEEDED)
-
- def test_side_effects_when_saving_approved_to_rejected(self):
- self.trigger_saving_approved_to_another_state(
- False,
- DomainRequest.DomainRequestStatus.REJECTED,
- DomainRequest.RejectionReasons.CONTACTS_OR_ORGANIZATION_LEGITIMACY,
- )
-
- def test_side_effects_when_saving_approved_to_ineligible(self):
- self.trigger_saving_approved_to_another_state(False, DomainRequest.DomainRequestStatus.INELIGIBLE)
-
- def test_has_correct_filters(self):
- """
- This test verifies that DomainRequestAdmin has the correct filters set up.
-
- It retrieves the current list of filters from DomainRequestAdmin
- and checks that it matches the expected list of filters.
- """
- with less_console_noise():
- request = self.factory.get("/")
- request.user = self.superuser
-
- # Grab the current list of table filters
- readonly_fields = self.admin.get_list_filter(request)
- expected_fields = (
- DomainRequestAdmin.StatusListFilter,
- "generic_org_type",
- "federal_type",
- DomainRequestAdmin.ElectionOfficeFilter,
- "rejection_reason",
- DomainRequestAdmin.InvestigatorFilter,
- )
-
- self.assertEqual(readonly_fields, expected_fields)
-
- def test_table_sorted_alphabetically(self):
- """
- This test verifies that the DomainRequestAdmin table is sorted alphabetically
- by the 'requested_domain__name' field.
-
- It creates a list of DomainRequest instances in a non-alphabetical order,
- then retrieves the queryset from the DomainRequestAdmin and checks
- that it matches the expected queryset,
- which is sorted alphabetically by the 'requested_domain__name' field.
- """
- with less_console_noise():
- # Creates a list of DomainRequests in scrambled order
- multiple_unalphabetical_domain_objects("domain_request")
-
- request = self.factory.get("/")
- request.user = self.superuser
-
- # Get the expected list of alphabetically sorted DomainRequests
- expected_order = DomainRequest.objects.order_by("requested_domain__name")
-
- # Get the returned queryset
- queryset = self.admin.get_queryset(request)
-
- # Check the order
- self.assertEqual(
- list(queryset),
- list(expected_order),
- )
-
- def test_displays_investigator_filter(self):
- """
- This test verifies that the investigator filter in the admin interface for
- the DomainRequest model displays correctly.
-
- It creates two DomainRequest instances, each with a different investigator.
- It then simulates a staff user logging in and applying the investigator filter
- on the DomainRequest admin page.
-
- We then test if the page displays the filter we expect, but we do not test
- if we get back the correct response in the table. This is to isolate if
- the filter displays correctly, when the filter isn't filtering correctly.
- """
-
- with less_console_noise():
- # Create a mock DomainRequest object, with a fake investigator
- domain_request: DomainRequest = generic_domain_object("domain_request", "SomeGuy")
- investigator_user = User.objects.filter(username=domain_request.investigator.username).get()
- investigator_user.is_staff = True
- investigator_user.save()
-
- p = "userpass"
- self.client.login(username="staffuser", password=p)
- response = self.client.get(
- "/admin/registrar/domainrequest/",
- {
- "investigator__id__exact": investigator_user.id,
- },
- follow=True,
- )
-
- # Then, test if the filter actually exists
- self.assertIn("filters", response.context)
-
- # Assert the content of filters and search_query
- filters = response.context["filters"]
-
- self.assertEqual(
- filters,
- [
- {
- "parameter_name": "investigator",
- "parameter_value": "SomeGuy first_name:investigator SomeGuy last_name:investigator",
- },
- ],
- )
-
- def test_investigator_dropdown_displays_only_staff(self):
- """
- This test verifies that the dropdown for the 'investigator' field in the DomainRequestAdmin
- interface only displays users who are marked as staff.
-
- It creates two DomainRequest instances, one with an investigator
- who is a staff user and another with an investigator who is not a staff user.
-
- It then retrieves the queryset for the 'investigator' dropdown from DomainRequestAdmin
- and checks that it matches the expected queryset, which only includes staff users.
- """
-
- with less_console_noise():
- # Create a mock DomainRequest object, with a fake investigator
- domain_request: DomainRequest = generic_domain_object("domain_request", "SomeGuy")
- investigator_user = User.objects.filter(username=domain_request.investigator.username).get()
- investigator_user.is_staff = True
- investigator_user.save()
-
- # Create a mock DomainRequest object, with a user that is not staff
- domain_request_2: DomainRequest = generic_domain_object("domain_request", "SomeOtherGuy")
- investigator_user_2 = User.objects.filter(username=domain_request_2.investigator.username).get()
- investigator_user_2.is_staff = False
- investigator_user_2.save()
-
- p = "userpass"
- self.client.login(username="staffuser", password=p)
-
- request = self.factory.post("/admin/registrar/domainrequest/{}/change/".format(domain_request.pk))
-
- # Get the actual field from the model's meta information
- investigator_field = DomainRequest._meta.get_field("investigator")
-
- # We should only be displaying staff users, in alphabetical order
- sorted_fields = ["first_name", "last_name", "email"]
- expected_dropdown = list(User.objects.filter(is_staff=True).order_by(*sorted_fields))
-
- # Grab the current dropdown. We do an API call to autocomplete to get this info.
- domain_request_queryset = self.admin.formfield_for_foreignkey(investigator_field, request).queryset
- user_request = self.factory.post(
- "/admin/autocomplete/?app_label=registrar&model_name=domainrequest&field_name=investigator"
- )
- user_admin = MyUserAdmin(User, self.site)
- user_queryset = user_admin.get_search_results(user_request, domain_request_queryset, None)[0]
- current_dropdown = list(user_queryset)
-
- self.assertEqual(expected_dropdown, current_dropdown)
-
- # Non staff users should not be in the list
- self.assertNotIn(domain_request_2, current_dropdown)
-
- def test_investigator_list_is_alphabetically_sorted(self):
- """
- This test verifies that filter list for the 'investigator'
- is displayed alphabetically
- """
- with less_console_noise():
- # Create a mock DomainRequest object, with a fake investigator
- domain_request: DomainRequest = generic_domain_object("domain_request", "SomeGuy")
- investigator_user = User.objects.filter(username=domain_request.investigator.username).get()
- investigator_user.is_staff = True
- investigator_user.save()
-
- domain_request_2: DomainRequest = generic_domain_object("domain_request", "AGuy")
- investigator_user_2 = User.objects.filter(username=domain_request_2.investigator.username).get()
- investigator_user_2.first_name = "AGuy"
- investigator_user_2.is_staff = True
- investigator_user_2.save()
-
- domain_request_3: DomainRequest = generic_domain_object("domain_request", "FinalGuy")
- investigator_user_3 = User.objects.filter(username=domain_request_3.investigator.username).get()
- investigator_user_3.first_name = "FinalGuy"
- investigator_user_3.is_staff = True
- investigator_user_3.save()
-
- p = "userpass"
- self.client.login(username="staffuser", password=p)
- request = RequestFactory().get("/")
-
- # These names have metadata embedded in them. :investigator implicitly tests if
- # these are actually from the attribute "investigator".
- expected_list = [
- "AGuy AGuy last_name:investigator",
- "FinalGuy FinalGuy last_name:investigator",
- "SomeGuy first_name:investigator SomeGuy last_name:investigator",
- ]
-
- # Get the actual sorted list of investigators from the lookups method
- actual_list = [item for _, item in self.admin.InvestigatorFilter.lookups(self, request, self.admin)]
-
- self.assertEqual(expected_list, actual_list)
-
- @less_console_noise_decorator
- def test_staff_can_see_cisa_region_federal(self):
- """Tests if staff can see CISA Region: N/A"""
-
- # Create a fake domain request
- _domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
-
- p = "userpass"
- self.client.login(username="staffuser", password=p)
- response = self.client.get(
- "/admin/registrar/domainrequest/{}/change/".format(_domain_request.pk),
- follow=True,
- )
-
- # Make sure the page loaded, and that we're on the right page
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, _domain_request.requested_domain.name)
-
- # Test if the page has the right CISA region
- expected_html = '
CISA region: N/A
'
- # Remove whitespace from expected_html
- expected_html = "".join(expected_html.split())
-
- # Remove whitespace from response content
- response_content = "".join(response.content.decode().split())
-
- # Check if response contains expected_html
- self.assertIn(expected_html, response_content)
-
- @less_console_noise_decorator
- def test_staff_can_see_cisa_region_non_federal(self):
- """Tests if staff can see the correct CISA region"""
-
- # Create a fake domain request. State will be NY (2).
- _domain_request = completed_domain_request(
- status=DomainRequest.DomainRequestStatus.IN_REVIEW, generic_org_type="interstate"
- )
-
- p = "userpass"
- self.client.login(username="staffuser", password=p)
- response = self.client.get(
- "/admin/registrar/domainrequest/{}/change/".format(_domain_request.pk),
- follow=True,
- )
-
- # Make sure the page loaded, and that we're on the right page
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, _domain_request.requested_domain.name)
-
- # Test if the page has the right CISA region
- expected_html = '
CISA region: 2
'
- # Remove whitespace from expected_html
- expected_html = "".join(expected_html.split())
-
- # Remove whitespace from response content
- response_content = "".join(response.content.decode().split())
-
- # Check if response contains expected_html
- self.assertIn(expected_html, response_content)
-
- def tearDown(self):
- super().tearDown()
- Domain.objects.all().delete()
- DomainInformation.objects.all().delete()
- DomainRequest.objects.all().delete()
- User.objects.all().delete()
- Contact.objects.all().delete()
- Website.objects.all().delete()
- self.mock_client.EMAILS_SENT.clear()
-
-
class TestDomainInvitationAdmin(TestCase):
"""Tests for the DomainInvitation page"""
diff --git a/src/registrar/tests/test_admin_domain.py b/src/registrar/tests/test_admin_domain.py
new file mode 100644
index 000000000..b5e3af01d
--- /dev/null
+++ b/src/registrar/tests/test_admin_domain.py
@@ -0,0 +1,815 @@
+from datetime import date
+from django.test import TestCase, RequestFactory, Client, override_settings
+from django.contrib.admin.sites import AdminSite
+from api.tests.common import less_console_noise_decorator
+from django_webtest import WebTest # type: ignore
+from django.contrib import messages
+from django.urls import reverse
+from registrar.admin import (
+ DomainAdmin,
+)
+from registrar.models import (
+ Domain,
+ DomainRequest,
+ DomainInformation,
+ User,
+ Host,
+)
+from .common import (
+ MockSESClient,
+ completed_domain_request,
+ less_console_noise,
+ create_superuser,
+ create_user,
+ create_ready_domain,
+ MockEppLib,
+ GenericTestHelper,
+)
+from unittest.mock import ANY, call, patch
+from unittest import skip
+
+import boto3_mocking # type: ignore
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+class TestDomainAdminAsStaff(MockEppLib):
+
+ def setUp(self):
+ self.client = Client(HTTP_HOST="localhost:8080")
+ self.staffuser = create_user()
+ self.client.force_login(self.staffuser)
+ super().setUp()
+
+ def tearDown(self):
+ super().tearDown()
+ Host.objects.all().delete()
+ Domain.objects.all().delete()
+ DomainInformation.objects.all().delete()
+ DomainRequest.objects.all().delete()
+ User.objects.all().delete()
+
+ @less_console_noise_decorator
+ def test_staff_can_see_cisa_region_federal(self):
+ """Tests if staff can see CISA Region: N/A"""
+
+ # Create a fake domain request
+ _domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
+ _domain_request.approve()
+
+ domain = _domain_request.approved_domain
+ response = self.client.get(
+ "/admin/registrar/domain/{}/change/".format(domain.pk),
+ follow=True,
+ )
+
+ # Make sure the page loaded, and that we're on the right page
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain.name)
+
+ # Test if the page has the right CISA region
+ expected_html = '
CISA region: N/A
'
+ # Remove whitespace from expected_html
+ expected_html = "".join(expected_html.split())
+
+ # Remove whitespace from response content
+ response_content = "".join(response.content.decode().split())
+
+ # Check if response contains expected_html
+ self.assertIn(expected_html, response_content)
+
+ @less_console_noise_decorator
+ def test_staff_can_see_cisa_region_non_federal(self):
+ """Tests if staff can see the correct CISA region"""
+
+ # Create a fake domain request. State will be NY (2).
+ _domain_request = completed_domain_request(
+ status=DomainRequest.DomainRequestStatus.IN_REVIEW, generic_org_type="interstate"
+ )
+
+ _domain_request.approve()
+
+ domain = _domain_request.approved_domain
+
+ response = self.client.get(
+ "/admin/registrar/domain/{}/change/".format(domain.pk),
+ follow=True,
+ )
+
+ # Make sure the page loaded, and that we're on the right page
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain.name)
+
+ # Test if the page has the right CISA region
+ expected_html = '
CISA region: 2
'
+ # Remove whitespace from expected_html
+ expected_html = "".join(expected_html.split())
+
+ # Remove whitespace from response content
+ response_content = "".join(response.content.decode().split())
+
+ # Check if response contains expected_html
+ self.assertIn(expected_html, response_content)
+
+ @less_console_noise_decorator
+ def test_analyst_can_see_inline_domain_information_in_domain_change_form(self):
+ """Tests if an analyst can still see the inline domain information form"""
+
+ # Create fake creator
+ _creator = User.objects.create(
+ username="MrMeoward",
+ first_name="Meoward",
+ last_name="Jones",
+ )
+
+ # Create a fake domain request
+ _domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator)
+
+ # Creates a Domain and DomainInformation object
+ _domain_request.approve()
+
+ domain_information = DomainInformation.objects.filter(domain_request=_domain_request).get()
+ domain_information.organization_name = "MonkeySeeMonkeyDo"
+ domain_information.save()
+
+ # We use filter here rather than just domain_information.domain just to get the latest data.
+ domain = Domain.objects.filter(domain_info=domain_information).get()
+
+ response = self.client.get(
+ "/admin/registrar/domain/{}/change/".format(domain.pk),
+ follow=True,
+ )
+
+ # Make sure the page loaded, and that we're on the right page
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain.name)
+
+ # Test for data. We only need to test one since its all interconnected.
+ expected_organization_name = "MonkeySeeMonkeyDo"
+ self.assertContains(response, expected_organization_name)
+
+ @skip("Why did this test stop working, and is is a good test")
+ def test_place_and_remove_hold(self):
+ domain = create_ready_domain()
+ # get admin page and assert Place Hold button
+ response = self.client.get(
+ "/admin/registrar/domain/{}/change/".format(domain.pk),
+ follow=True,
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain.name)
+ self.assertContains(response, "Place hold")
+ self.assertNotContains(response, "Remove hold")
+
+ # submit place_client_hold and assert Remove Hold button
+ response = self.client.post(
+ "/admin/registrar/domain/{}/change/".format(domain.pk),
+ {"_place_client_hold": "Place hold", "name": domain.name},
+ follow=True,
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain.name)
+ self.assertContains(response, "Remove hold")
+ self.assertNotContains(response, "Place hold")
+
+ # submit remove client hold and assert Place hold button
+ response = self.client.post(
+ "/admin/registrar/domain/{}/change/".format(domain.pk),
+ {"_remove_client_hold": "Remove hold", "name": domain.name},
+ follow=True,
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain.name)
+ self.assertContains(response, "Place hold")
+ self.assertNotContains(response, "Remove hold")
+
+ @less_console_noise_decorator
+ def test_deletion_is_successful(self):
+ """
+ Scenario: Domain deletion is unsuccessful
+ When the domain is deleted
+ Then a user-friendly success message is returned for displaying on the web
+ And `state` is set to `DELETED`
+ """
+ self.site = AdminSite()
+ self.admin = DomainAdmin(model=Domain, admin_site=self.site)
+ self.factory = RequestFactory()
+ domain = create_ready_domain()
+ # Put in client hold
+ domain.place_client_hold()
+ # Ensure everything is displaying correctly
+ response = self.client.get(
+ "/admin/registrar/domain/{}/change/".format(domain.pk),
+ follow=True,
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain.name)
+ self.assertContains(response, "Remove from registry")
+
+ # The contents of the modal should exist before and after the post.
+ # Check for the header
+ self.assertContains(response, "Are you sure you want to remove this domain from the registry?")
+
+ # Check for some of its body
+ self.assertContains(response, "When a domain is removed from the registry:")
+
+ # Check for some of the button content
+ self.assertContains(response, "Yes, remove from registry")
+
+ # Test the info dialog
+ request = self.factory.post(
+ "/admin/registrar/domain/{}/change/".format(domain.pk),
+ {"_delete_domain": "Remove from registry", "name": domain.name},
+ follow=True,
+ )
+ request.user = self.client
+ with patch("django.contrib.messages.add_message") as mock_add_message:
+ self.admin.do_delete_domain(request, domain)
+ mock_add_message.assert_called_once_with(
+ request,
+ messages.INFO,
+ "Domain city.gov has been deleted. Thanks!",
+ extra_tags="",
+ fail_silently=False,
+ )
+
+ # The modal should still exist
+ self.assertContains(response, "Are you sure you want to remove this domain from the registry?")
+ self.assertContains(response, "When a domain is removed from the registry:")
+ self.assertContains(response, "Yes, remove from registry")
+
+ self.assertEqual(domain.state, Domain.State.DELETED)
+
+
+ @less_console_noise_decorator
+ def test_deletion_ready_fsm_failure(self):
+ """
+ Scenario: Domain deletion is unsuccessful
+ When an error is returned from epplibwrapper
+ Then a user-friendly error message is returned for displaying on the web
+ And `state` is not set to `DELETED`
+ """
+ self.site = AdminSite()
+ self.admin = DomainAdmin(model=Domain, admin_site=self.site)
+ self.factory = RequestFactory()
+ domain = create_ready_domain()
+ p = "userpass"
+ self.client.login(username="staffuser", password=p)
+ # Ensure everything is displaying correctly
+ response = self.client.get(
+ "/admin/registrar/domain/{}/change/".format(domain.pk),
+ follow=True,
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain.name)
+ self.assertContains(response, "Remove from registry")
+ # Test the error
+ request = self.factory.post(
+ "/admin/registrar/domain/{}/change/".format(domain.pk),
+ {"_delete_domain": "Remove from registry", "name": domain.name},
+ follow=True,
+ )
+ request.user = self.client
+ with patch("django.contrib.messages.add_message") as mock_add_message:
+ self.admin.do_delete_domain(request, domain)
+ mock_add_message.assert_called_once_with(
+ request,
+ messages.ERROR,
+ "Error deleting this Domain: "
+ "Can't switch from state 'ready' to 'deleted'"
+ ", must be either 'dns_needed' or 'on_hold'",
+ extra_tags="",
+ fail_silently=False,
+ )
+
+ self.assertEqual(domain.state, Domain.State.READY)
+
+ @less_console_noise_decorator
+ def test_analyst_deletes_domain_idempotent(self):
+ """
+ Scenario: Analyst tries to delete an already deleted domain
+ Given `state` is already `DELETED`
+ When `domain.deletedInEpp()` is called
+ Then `commands.DeleteDomain` is sent to the registry
+ And Domain returns normally without an error dialog
+ """
+ self.site = AdminSite()
+ self.admin = DomainAdmin(model=Domain, admin_site=self.site)
+ self.factory = RequestFactory()
+ domain = create_ready_domain()
+ # Put in client hold
+ domain.place_client_hold()
+ p = "userpass"
+ self.client.login(username="staffuser", password=p)
+ # Ensure everything is displaying correctly
+ response = self.client.get(
+ "/admin/registrar/domain/{}/change/".format(domain.pk),
+ follow=True,
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain.name)
+ self.assertContains(response, "Remove from registry")
+ # Test the info dialog
+ request = self.factory.post(
+ "/admin/registrar/domain/{}/change/".format(domain.pk),
+ {"_delete_domain": "Remove from registry", "name": domain.name},
+ follow=True,
+ )
+ request.user = self.client
+ # Delete it once
+ with patch("django.contrib.messages.add_message") as mock_add_message:
+ self.admin.do_delete_domain(request, domain)
+ mock_add_message.assert_called_once_with(
+ request,
+ messages.INFO,
+ "Domain city.gov has been deleted. Thanks!",
+ extra_tags="",
+ fail_silently=False,
+ )
+
+ self.assertEqual(domain.state, Domain.State.DELETED)
+ # Try to delete it again
+ # Test the info dialog
+ request = self.factory.post(
+ "/admin/registrar/domain/{}/change/".format(domain.pk),
+ {"_delete_domain": "Remove from registry", "name": domain.name},
+ follow=True,
+ )
+ request.user = self.client
+ with patch("django.contrib.messages.add_message") as mock_add_message:
+ self.admin.do_delete_domain(request, domain)
+ mock_add_message.assert_called_once_with(
+ request,
+ messages.INFO,
+ "This domain is already deleted",
+ extra_tags="",
+ fail_silently=False,
+ )
+ self.assertEqual(domain.state, Domain.State.DELETED)
+
+
+class TestDomainAdminWClient(TestCase):
+
+ def setUp(self):
+ self.client = Client(HTTP_HOST="localhost:8080")
+ self.superuser = create_superuser()
+ self.client.force_login(self.superuser)
+ super().setUp()
+
+ def tearDown(self):
+ super().tearDown()
+ Host.objects.all().delete()
+ Domain.objects.all().delete()
+ DomainInformation.objects.all().delete()
+ DomainRequest.objects.all().delete()
+ User.objects.all().delete()
+
+ @less_console_noise_decorator
+ def test_has_model_description(self):
+ """Tests if this model has a model description on the table view"""
+ response = self.client.get(
+ "/admin/registrar/domain/",
+ follow=True,
+ )
+
+ # Make sure that the page is loaded correctly
+ self.assertEqual(response.status_code, 200)
+
+ # Test for a description snippet
+ self.assertContains(response, "This table contains all approved domains in the .gov registrar.")
+ self.assertContains(response, "Show more")
+
+ @less_console_noise_decorator
+ def test_contact_fields_on_domain_change_form_have_detail_table(self):
+ """Tests if the contact fields in the inlined Domain information have the detail table
+ which displays title, email, and phone"""
+
+ # Create fake creator
+ _creator = User.objects.create(
+ username="MrMeoward",
+ first_name="Meoward",
+ last_name="Jones",
+ )
+
+ # Due to the relation between User <==> Contact,
+ # the underlying contact has to be modified this way.
+ _creator.contact.email = "meoward.jones@igorville.gov"
+ _creator.contact.phone = "(555) 123 12345"
+ _creator.contact.title = "Treat inspector"
+ _creator.contact.save()
+
+ # Create a fake domain request
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator)
+ domain_request.approve()
+ _domain_info = DomainInformation.objects.filter(domain=domain_request.approved_domain).get()
+ domain = Domain.objects.filter(domain_info=_domain_info).get()
+
+ response = self.client.get(
+ "/admin/registrar/domain/{}/change/".format(domain.pk),
+ follow=True,
+ )
+
+ # Make sure the page loaded, and that we're on the right page
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain.name)
+
+ # Check that the fields have the right values.
+ # == Check for the creator == #
+
+ # Check for the right title, email, and phone number in the response.
+ # We only need to check for the end tag
+ # (Otherwise this test will fail if we change classes, etc)
+ self.assertContains(response, "Treat inspector")
+ self.assertContains(response, "meoward.jones@igorville.gov")
+ self.assertContains(response, "(555) 123 12345")
+
+ # Check for the field itself
+ self.assertContains(response, "Meoward Jones")
+
+ # == Check for the submitter == #
+ self.assertContains(response, "mayor@igorville.gov")
+
+ self.assertContains(response, "Admin Tester")
+ self.assertContains(response, "(555) 555 5556")
+ self.assertContains(response, "Testy2 Tester2")
+
+ # == Check for the senior_official == #
+ self.assertContains(response, "testy@town.com")
+ self.assertContains(response, "Chief Tester")
+ self.assertContains(response, "(555) 555 5555")
+
+ # Includes things like readonly fields
+ self.assertContains(response, "Testy Tester")
+
+ # == Test the other_employees field == #
+ self.assertContains(response, "testy2@town.com")
+ self.assertContains(response, "Another Tester")
+ self.assertContains(response, "(555) 555 5557")
+
+ # Test for the copy link
+ self.assertContains(response, "usa-button__clipboard")
+
+ @less_console_noise_decorator
+ def test_helper_text(self):
+ """
+ Tests for the correct helper text on this page
+ """
+
+ self.site = AdminSite()
+ self.admin = DomainAdmin(model=Domain, admin_site=self.site)
+ self.factory = RequestFactory()
+
+ # Create a ready domain with a preset expiration date
+ domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
+
+ p = "adminpass"
+ self.client.login(username="superuser", password=p)
+ response = self.client.get(
+ "/admin/registrar/domain/{}/change/".format(domain.pk),
+ follow=True,
+ )
+
+ # Make sure the page loaded, and that we're on the right page
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain.name)
+
+ # Contains some test tools
+ self.test_helper = GenericTestHelper(
+ factory=self.factory,
+ user=self.superuser,
+ admin=self.admin,
+ url=reverse("admin:registrar_domain_changelist"),
+ model=Domain,
+ client=self.client,
+ )
+ # These should exist in the response
+ expected_values = [
+ ("expiration_date", "Date the domain expires in the registry"),
+ ("first_ready_at", 'Date when this domain first moved into "ready" state; date will never change'),
+ ("deleted_at", 'Will appear blank unless the domain is in "deleted" state'),
+ ]
+ self.test_helper.assert_response_contains_distinct_values(response, expected_values)
+
+ @less_console_noise_decorator
+ def test_helper_text_state(self):
+ """
+ Tests for the correct state helper text on this page
+ """
+
+ # Add domain data
+ self.ready_domain, _ = Domain.objects.get_or_create(name="fakeready.gov", state=Domain.State.READY)
+ self.unknown_domain, _ = Domain.objects.get_or_create(name="fakeunknown.gov", state=Domain.State.UNKNOWN)
+ self.dns_domain, _ = Domain.objects.get_or_create(name="fakedns.gov", state=Domain.State.DNS_NEEDED)
+ self.hold_domain, _ = Domain.objects.get_or_create(name="fakehold.gov", state=Domain.State.ON_HOLD)
+ self.deleted_domain, _ = Domain.objects.get_or_create(name="fakedeleted.gov", state=Domain.State.DELETED)
+
+ # We don't need to check for all text content, just a portion of it
+ expected_unknown_domain_message = "The creator of the associated domain request has not logged in to"
+ expected_dns_message = "Before this domain can be used, name server addresses need"
+ expected_hold_message = "While on hold, this domain"
+ expected_deleted_message = "This domain was permanently removed from the registry."
+ expected_messages = [
+ (self.ready_domain, "This domain has name servers and is ready for use."),
+ (self.unknown_domain, expected_unknown_domain_message),
+ (self.dns_domain, expected_dns_message),
+ (self.hold_domain, expected_hold_message),
+ (self.deleted_domain, expected_deleted_message),
+ ]
+
+ for domain, message in expected_messages:
+ with self.subTest(domain_state=domain.state):
+ response = self.client.get(
+ "/admin/registrar/domain/{}/change/".format(domain.id),
+ )
+
+ # Make sure the page loaded, and that we're on the right page
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain.name)
+
+ # Check that the right help text exists
+ self.assertContains(response, message)
+
+ @less_console_noise_decorator
+ def test_admin_can_see_inline_domain_information_in_domain_change_form(self):
+ """Tests if an admin can still see the inline domain information form"""
+ # Create fake creator
+ _creator = User.objects.create(
+ username="MrMeoward",
+ first_name="Meoward",
+ last_name="Jones",
+ )
+
+ # Create a fake domain request
+ _domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator)
+
+ # Creates a Domain and DomainInformation object
+ _domain_request.approve()
+
+ domain_information = DomainInformation.objects.filter(domain_request=_domain_request).get()
+ domain_information.organization_name = "MonkeySeeMonkeyDo"
+ domain_information.save()
+
+ # We use filter here rather than just domain_information.domain just to get the latest data.
+ domain = Domain.objects.filter(domain_info=domain_information).get()
+
+ response = self.client.get(
+ "/admin/registrar/domain/{}/change/".format(domain.pk),
+ follow=True,
+ )
+
+ # Make sure the page loaded, and that we're on the right page
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain.name)
+
+ # Test for data. We only need to test one since its all interconnected.
+ expected_organization_name = "MonkeySeeMonkeyDo"
+ self.assertContains(response, expected_organization_name)
+
+ @less_console_noise_decorator
+ def test_custom_delete_confirmation_page_table(self):
+ """Tests if we override the delete confirmation page for custom content on the table"""
+ # Create a ready domain
+ domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
+
+ # Get the index. The post expects the index to be encoded as a string
+ index = f"{domain.id}"
+
+ self.site = AdminSite()
+ self.admin = DomainAdmin(model=Domain, admin_site=self.site)
+ self.factory = RequestFactory()
+
+ # Contains some test tools
+ self.test_helper = GenericTestHelper(
+ factory=self.factory,
+ user=self.superuser,
+ admin=self.admin,
+ url=reverse("admin:registrar_domain_changelist"),
+ model=Domain,
+ client=self.client,
+ )
+
+ # Simulate selecting a single record, then clicking "Delete selected domains"
+ response = self.test_helper.get_table_delete_confirmation_page("0", index)
+
+ # Check that our content exists
+ content_slice = "When a domain is deleted:"
+ self.assertContains(response, content_slice)
+
+ @less_console_noise_decorator
+ def test_short_org_name_in_domains_list(self):
+ """
+ Make sure the short name is displaying in admin on the list page
+ """
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
+ mock_client = MockSESClient()
+ with boto3_mocking.clients.handler_for("sesv2", mock_client):
+ domain_request.approve()
+
+ response = self.client.get("/admin/registrar/domain/")
+ # There are 4 template references to Federal (4) plus four references in the table
+ # for our actual domain_request
+ self.assertContains(response, "Federal", count=56)
+ # This may be a bit more robust
+ self.assertContains(response, '
Federal | ', count=1)
+ # Now let's make sure the long description does not exist
+ self.assertNotContains(response, "Federal: an agency of the U.S. government")
+
+ @override_settings(IS_PRODUCTION=True)
+ @less_console_noise_decorator
+ def test_prod_only_shows_export(self):
+ """Test that production environment only displays export"""
+ response = self.client.get("/admin/registrar/domain/")
+ self.assertContains(response, ">Export<")
+ self.assertNotContains(response, ">Import<")
+
+
+class TestDomainAdminWebTest(MockEppLib, WebTest):
+ # csrf checks do not work with WebTest.
+ # We disable them here. TODO for another ticket.
+ csrf_checks = False
+
+ def setUp(self):
+ self.site = AdminSite()
+ self.admin = DomainAdmin(model=Domain, admin_site=self.site)
+ self.superuser = create_superuser()
+ self.factory = RequestFactory()
+ self.app.set_user(self.superuser.username)
+ super().setUp()
+
+ def tearDown(self):
+ super().tearDown()
+ Host.objects.all().delete()
+ Domain.objects.all().delete()
+ DomainInformation.objects.all().delete()
+ DomainRequest.objects.all().delete()
+ User.objects.all().delete()
+
+ @less_console_noise_decorator
+ @patch("registrar.admin.DomainAdmin._get_current_date", return_value=date(2024, 1, 1))
+ def test_extend_expiration_date_button(self, mock_date_today):
+ """
+ Tests if extend_expiration_date modal gives an accurate date
+ """
+
+ # Create a ready domain with a preset expiration date
+ domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
+ response = self.app.get(reverse("admin:registrar_domain_change", args=[domain.pk]))
+ # load expiration date into cache and registrar with below command
+ domain.registry_expiration_date
+ # Make sure the ex date is what we expect it to be
+ domain_ex_date = Domain.objects.get(id=domain.id).expiration_date
+ self.assertEqual(domain_ex_date, date(2023, 5, 25))
+
+ # Make sure that the page is loading as expected
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain.name)
+ self.assertContains(response, "Extend expiration date")
+
+ # Grab the form to submit
+ form = response.forms["domain_form"]
+
+ with patch("django.contrib.messages.add_message") as mock_add_message:
+ # Submit the form
+ response = form.submit("_extend_expiration_date")
+
+ # Follow the response
+ response = response.follow()
+
+ # Assert that everything on the page looks correct
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain.name)
+ self.assertContains(response, "Extend expiration date")
+
+ # Ensure the message we recieve is in line with what we expect
+ expected_message = "Successfully extended the expiration date."
+ expected_call = call(
+ # The WGSI request doesn't need to be tested
+ ANY,
+ messages.INFO,
+ expected_message,
+ extra_tags="",
+ fail_silently=False,
+ )
+
+ mock_add_message.assert_has_calls([expected_call], 1)
+
+ @less_console_noise_decorator
+ @patch("registrar.admin.DomainAdmin._get_current_date", return_value=date(2024, 1, 1))
+ def test_extend_expiration_date_button_epp(self, mock_date_today):
+ """
+ Tests if extend_expiration_date button sends the right epp command
+ """
+
+ # Create a ready domain with a preset expiration date
+ domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
+
+ response = self.app.get(reverse("admin:registrar_domain_change", args=[domain.pk]))
+
+ # Make sure that the page is loading as expected
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain.name)
+ self.assertContains(response, "Extend expiration date")
+
+ # Grab the form to submit
+ form = response.forms["domain_form"]
+
+ with patch("django.contrib.messages.add_message") as mock_add_message:
+ with patch("registrar.models.Domain.renew_domain") as renew_mock:
+ # Submit the form
+ response = form.submit("_extend_expiration_date")
+
+ # Follow the response
+ response = response.follow()
+
+ # Assert that it is calling the function with the default extension length.
+ # We only need to test the value that EPP sends, as we can assume the other
+ # test cases cover the "renew" function.
+ renew_mock.assert_has_calls([call()], any_order=False)
+
+ # We should not make duplicate calls
+ self.assertEqual(renew_mock.call_count, 1)
+
+ # Assert that everything on the page looks correct
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain.name)
+ self.assertContains(response, "Extend expiration date")
+
+ # Ensure the message we recieve is in line with what we expect
+ expected_message = "Successfully extended the expiration date."
+ expected_call = call(
+ # The WGSI request doesn't need to be tested
+ ANY,
+ messages.INFO,
+ expected_message,
+ extra_tags="",
+ fail_silently=False,
+ )
+ mock_add_message.assert_has_calls([expected_call], 1)
+
+ @less_console_noise_decorator
+ def test_custom_delete_confirmation_page(self):
+ """Tests if we override the delete confirmation page for custom content"""
+ # Create a ready domain with a preset expiration date
+ domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
+
+ domain_change_page = self.app.get(reverse("admin:registrar_domain_change", args=[domain.pk]))
+
+ self.assertContains(domain_change_page, "fake.gov")
+ # click the "Manage" link
+ confirmation_page = domain_change_page.click("Delete", index=0)
+
+ content_slice = "When a domain is deleted:"
+ self.assertContains(confirmation_page, content_slice)
+
+ @less_console_noise_decorator
+ def test_on_hold_is_successful_web_test(self):
+ """
+ Scenario: Domain on_hold is successful through webtest
+ """
+ with less_console_noise():
+ domain = create_ready_domain()
+
+ response = self.app.get(reverse("admin:registrar_domain_change", args=[domain.pk]))
+
+ # Check the contents of the modal
+ # Check for the header
+ self.assertContains(response, "Are you sure you want to place this domain on hold?")
+
+ # Check for some of its body
+ self.assertContains(response, "When a domain is on hold:")
+
+ # Check for some of the button content
+ self.assertContains(response, "Yes, place hold")
+
+ # Grab the form to submit
+ form = response.forms["domain_form"]
+
+ # Submit the form
+ response = form.submit("_place_client_hold")
+
+ # Follow the response
+ response = response.follow()
+
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain.name)
+ self.assertContains(response, "Remove hold")
+
+ # The modal should still exist
+ # Check for the header
+ self.assertContains(response, "Are you sure you want to place this domain on hold?")
+
+ # Check for some of its body
+ self.assertContains(response, "When a domain is on hold:")
+
+ # Check for some of the button content
+ self.assertContains(response, "Yes, place hold")
+
+ # Web test has issues grabbing up to date data from the db, so we can test
+ # the returned view instead
+ self.assertContains(response, '
On hold
')
+
+ @skip("Waiting on epp lib to implement")
+ def test_place_and_remove_hold_epp(self):
+ raise
+
diff --git a/src/registrar/tests/test_admin_request.py b/src/registrar/tests/test_admin_request.py
new file mode 100644
index 000000000..5d42b95a6
--- /dev/null
+++ b/src/registrar/tests/test_admin_request.py
@@ -0,0 +1,1911 @@
+from datetime import datetime
+from django.utils import timezone
+import re
+from django.test import RequestFactory, Client, override_settings
+from django.contrib.admin.sites import AdminSite
+from contextlib import ExitStack
+from api.tests.common import less_console_noise_decorator
+from django.contrib import messages
+from django.urls import reverse
+from registrar.admin import (
+ DomainRequestAdmin,
+ MyUserAdmin,
+)
+from registrar.models import (
+ Domain,
+ DomainRequest,
+ DomainInformation,
+ DraftDomain,
+ User,
+ Contact,
+ Website,
+)
+from .common import (
+ MockSESClient,
+ completed_domain_request,
+ generic_domain_object,
+ less_console_noise,
+ create_superuser,
+ create_user,
+ multiple_unalphabetical_domain_objects,
+ MockEppLib,
+ GenericTestHelper,
+)
+from unittest.mock import ANY, patch
+
+from django.conf import settings
+import boto3_mocking # type: ignore
+import logging
+
+logger = logging.getLogger(__name__)
+
+@boto3_mocking.patching
+class TestDomainRequestAdmin(MockEppLib):
+ def setUp(self):
+ super().setUp()
+ self.site = AdminSite()
+ self.factory = RequestFactory()
+ self.admin = DomainRequestAdmin(model=DomainRequest, admin_site=self.site)
+ self.superuser = create_superuser()
+ self.staffuser = create_user()
+ self.client = Client(HTTP_HOST="localhost:8080")
+ self.test_helper = GenericTestHelper(
+ factory=self.factory,
+ user=self.superuser,
+ admin=self.admin,
+ url="/admin/registrar/domainrequest/",
+ model=DomainRequest,
+ )
+ self.mock_client = MockSESClient()
+# @boto3_mocking.patching
+# class TestDomainRequestAdminAsStaff(MockEppLib):
+# def setUp(self):
+# super().setUp()
+# self.staffuser = create_user()
+# self.client = Client(HTTP_HOST="localhost:8080")
+# self.mock_client = MockSESClient()
+
+# def tearDown(self):
+# super().tearDown()
+# Domain.objects.all().delete()
+# DomainInformation.objects.all().delete()
+# DomainRequest.objects.all().delete()
+# User.objects.all().delete()
+# Contact.objects.all().delete()
+# Website.objects.all().delete()
+# self.mock_client.EMAILS_SENT.clear()
+
+ @less_console_noise_decorator
+ def test_analyst_can_see_and_edit_alternative_domain(self):
+ """Tests if an analyst can still see and edit the alternative domain field"""
+
+ # Create fake creator
+ _creator = User.objects.create(
+ username="MrMeoward",
+ first_name="Meoward",
+ last_name="Jones",
+ )
+
+ # Create a fake domain request
+ _domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator)
+
+ fake_website = Website.objects.create(website="thisisatest.gov")
+ _domain_request.alternative_domains.add(fake_website)
+ _domain_request.save()
+
+ p = "userpass"
+ self.client.login(username="staffuser", password=p)
+ response = self.client.get(
+ "/admin/registrar/domainrequest/{}/change/".format(_domain_request.pk),
+ follow=True,
+ )
+
+ # Make sure the page loaded, and that we're on the right page
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, _domain_request.requested_domain.name)
+
+ # Test if the page has the alternative domain
+ self.assertContains(response, "thisisatest.gov")
+
+ # Check that the page contains the url we expect
+ expected_href = reverse("admin:registrar_website_change", args=[fake_website.id])
+ self.assertContains(response, expected_href)
+
+ # Navigate to the website to ensure that we can still edit it
+ response = self.client.get(
+ "/admin/registrar/website/{}/change/".format(fake_website.pk),
+ follow=True,
+ )
+
+ # Make sure the page loaded, and that we're on the right page
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, "thisisatest.gov")
+
+ @less_console_noise_decorator
+ def test_analyst_can_see_and_edit_requested_domain(self):
+ """Tests if an analyst can still see and edit the requested domain field"""
+
+ # Create fake creator
+ _creator = User.objects.create(
+ username="MrMeoward",
+ first_name="Meoward",
+ last_name="Jones",
+ )
+
+ # Create a fake domain request
+ _domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator)
+
+ p = "userpass"
+ self.client.login(username="staffuser", password=p)
+ response = self.client.get(
+ "/admin/registrar/domainrequest/{}/change/".format(_domain_request.pk),
+ follow=True,
+ )
+
+ # Filter to get the latest from the DB (rather than direct assignment)
+ requested_domain = DraftDomain.objects.filter(name=_domain_request.requested_domain.name).get()
+
+ # Make sure the page loaded, and that we're on the right page
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, requested_domain.name)
+
+ # Check that the page contains the url we expect
+ expected_href = reverse("admin:registrar_draftdomain_change", args=[requested_domain.id])
+ self.assertContains(response, expected_href)
+
+ # Navigate to the website to ensure that we can still edit it
+ response = self.client.get(
+ "/admin/registrar/draftdomain/{}/change/".format(requested_domain.pk),
+ follow=True,
+ )
+
+ # Make sure the page loaded, and that we're on the right page
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, "city.gov")
+
+ @less_console_noise_decorator
+ def test_analyst_can_see_current_websites(self):
+ """Tests if an analyst can still see current website field"""
+
+ # Create fake creator
+ _creator = User.objects.create(
+ username="MrMeoward",
+ first_name="Meoward",
+ last_name="Jones",
+ )
+
+ # Create a fake domain request
+ _domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator)
+
+ fake_website = Website.objects.create(website="thisisatest.gov")
+ _domain_request.current_websites.add(fake_website)
+ _domain_request.save()
+
+ p = "userpass"
+ self.client.login(username="staffuser", password=p)
+ response = self.client.get(
+ "/admin/registrar/domainrequest/{}/change/".format(_domain_request.pk),
+ follow=True,
+ )
+
+ # Make sure the page loaded, and that we're on the right page
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, _domain_request.requested_domain.name)
+
+ # Test if the page has the current website
+ self.assertContains(response, "thisisatest.gov")
+
+ @less_console_noise_decorator
+ def test_model_displays_action_needed_email(self):
+ """Tests if the action needed email is visible for Domain Requests"""
+
+ _domain_request = completed_domain_request(
+ status=DomainRequest.DomainRequestStatus.ACTION_NEEDED,
+ action_needed_reason=DomainRequest.ActionNeededReasons.BAD_NAME,
+ )
+
+ p = "userpass"
+ self.client.login(username="staffuser", password=p)
+ response = self.client.get(
+ "/admin/registrar/domainrequest/{}/change/".format(_domain_request.pk),
+ follow=True,
+ )
+
+ self.assertContains(response, "DOMAIN NAME DOES NOT MEET .GOV REQUIREMENTS")
+
+ @less_console_noise_decorator
+ def test_sticky_submit_row_has_extra_class_for_analysts(self):
+ """Test that the change_form template contains strings indicative of the customization
+ of the sticky submit bar.
+
+ Also test that it DOES contain a CSS class meant for analysts only when logged in as analyst."""
+
+ # make sure there is no user with this email
+ EMAIL = "mayor@igorville.gov"
+ User.objects.filter(email=EMAIL).delete()
+ self.client.force_login(self.staffuser)
+
+ # Create a sample domain request
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
+
+ # Create a mock request
+ request = self.client.post("/admin/registrar/domainrequest/{}/change/".format(domain_request.pk))
+
+ # Since we're using client to mock the request, we can only test against
+ # non-interpolated values
+ expected_content = "Requested domain:"
+ expected_content2 = '
'
+ expected_content3 = '
'
+ self.assertContains(request, expected_content)
+ self.assertContains(request, expected_content2)
+ self.assertContains(request, expected_content3)
+
+ @less_console_noise_decorator
+ def test_other_contacts_has_readonly_link(self):
+ """Tests if the readonly other_contacts field has links"""
+
+ # Create a fake domain request
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
+
+ # Get the other contact
+ other_contact = domain_request.other_contacts.all().first()
+
+ p = "userpass"
+ self.client.login(username="staffuser", password=p)
+ response = self.client.get(
+ "/admin/registrar/domainrequest/{}/change/".format(domain_request.pk),
+ follow=True,
+ )
+
+ # Make sure the page loaded, and that we're on the right page
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain_request.requested_domain.name)
+
+ # Check that the page contains the url we expect
+ expected_href = reverse("admin:registrar_contact_change", args=[other_contact.id])
+ self.assertContains(response, expected_href)
+
+ # Check that the page contains the link we expect.
+ # Since the url is dynamic (populated by JS), we can test for its existence
+ # by checking for the end tag.
+ expected_url = "Testy Tester"
+ self.assertContains(response, expected_url)
+
+ @less_console_noise_decorator
+ def test_other_websites_has_readonly_link(self):
+ """Tests if the readonly other_websites field has links"""
+
+ # Create a fake domain request
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
+
+ p = "userpass"
+ self.client.login(username="staffuser", password=p)
+ response = self.client.get(
+ "/admin/registrar/domainrequest/{}/change/".format(domain_request.pk),
+ follow=True,
+ )
+
+ # Make sure the page loaded, and that we're on the right page
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain_request.requested_domain.name)
+
+ # Check that the page contains the link we expect.
+ expected_url = '
city.com'
+ self.assertContains(response, expected_url)
+
+ @less_console_noise_decorator
+ def test_contact_fields_have_detail_table(self):
+ """Tests if the contact fields have the detail table which displays title, email, and phone"""
+
+ # Create fake creator
+ _creator = User.objects.create(
+ username="MrMeoward",
+ first_name="Meoward",
+ last_name="Jones",
+ )
+
+ # Due to the relation between User <==> Contact,
+ # the underlying contact has to be modified this way.
+ _creator.contact.email = "meoward.jones@igorville.gov"
+ _creator.contact.phone = "(555) 123 12345"
+ _creator.contact.title = "Treat inspector"
+ _creator.contact.save()
+
+ # Create a fake domain request
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator)
+
+ p = "userpass"
+ self.client.login(username="staffuser", password=p)
+ response = self.client.get(
+ "/admin/registrar/domainrequest/{}/change/".format(domain_request.pk),
+ follow=True,
+ )
+
+ # Make sure the page loaded, and that we're on the right page
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain_request.requested_domain.name)
+
+ # == Check for the creator == #
+
+ self.site = AdminSite()
+ self.factory = RequestFactory()
+ self.admin = DomainRequestAdmin(model=DomainRequest, admin_site=self.site)
+ self.superuser = create_superuser()
+ self.test_helper = GenericTestHelper(
+ factory=self.factory,
+ user=self.superuser,
+ admin=self.admin,
+ url="/admin/registrar/domainrequest/",
+ model=DomainRequest,
+ )
+
+ # Check for the right title, email, and phone number in the response.
+ expected_creator_fields = [
+ # Field, expected value
+ ("title", "Treat inspector"),
+ ("email", "meoward.jones@igorville.gov"),
+ ("phone", "(555) 123 12345"),
+ ]
+ self.test_helper.assert_response_contains_distinct_values(response, expected_creator_fields)
+
+ # Check for the field itself
+ self.assertContains(response, "Meoward Jones")
+
+ # == Check for the submitter == #
+ self.assertContains(response, "mayor@igorville.gov", count=2)
+ expected_submitter_fields = [
+ # Field, expected value
+ ("title", "Admin Tester"),
+ ("phone", "(555) 555 5556"),
+ ]
+ self.test_helper.assert_response_contains_distinct_values(response, expected_submitter_fields)
+ self.assertContains(response, "Testy2 Tester2")
+
+ # == Check for the senior_official == #
+ self.assertContains(response, "testy@town.com", count=2)
+ expected_so_fields = [
+ # Field, expected value
+ ("phone", "(555) 555 5555"),
+ ]
+
+ self.test_helper.assert_response_contains_distinct_values(response, expected_so_fields)
+ self.assertContains(response, "Chief Tester")
+
+ # == Test the other_employees field == #
+ self.assertContains(response, "testy2@town.com")
+ expected_other_employees_fields = [
+ # Field, expected value
+ ("title", "Another Tester"),
+ ("phone", "(555) 555 5557"),
+ ]
+ self.test_helper.assert_response_contains_distinct_values(response, expected_other_employees_fields)
+
+ # Test for the copy link
+ self.assertContains(response, "usa-button__clipboard", count=4)
+
+ # Test that Creator counts display properly
+ self.assertNotContains(response, "Approved domains")
+ self.assertContains(response, "Active requests")
+
+ @less_console_noise_decorator
+ def test_user_sets_restricted_status_modal(self):
+ """Tests the modal for when a user sets the status to restricted"""
+ # make sure there is no user with this email
+ EMAIL = "mayor@igorville.gov"
+ User.objects.filter(email=EMAIL).delete()
+
+ # Create a sample domain request
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
+
+ p = "userpass"
+ self.client.login(username="staffuser", password=p)
+ response = self.client.get(
+ "/admin/registrar/domainrequest/{}/change/".format(domain_request.pk),
+ follow=True,
+ )
+
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain_request.requested_domain.name)
+
+ # Check that the modal has the right content
+ # Check for the header
+ self.assertContains(response, "Are you sure you want to select ineligible status?")
+
+ # Check for some of its body
+ self.assertContains(response, "When a domain request is in ineligible status")
+
+ # Check for some of the button content
+ self.assertContains(response, "Yes, select ineligible status")
+
+ # Create a mock request
+ self.site = AdminSite()
+ self.factory = RequestFactory()
+ self.admin = DomainRequestAdmin(model=DomainRequest, admin_site=self.site)
+ request = self.factory.post(
+ "/admin/registrar/domainrequest{}/change/".format(domain_request.pk), follow=True
+ )
+ with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
+ # Modify the domain request's property
+ domain_request.status = DomainRequest.DomainRequestStatus.INELIGIBLE
+
+ # Use the model admin's save_model method
+ self.admin.save_model(request, domain_request, form=None, change=True)
+
+ # Test that approved domain exists and equals requested domain
+ self.assertEqual(domain_request.creator.status, "restricted")
+
+ # 'Get' to the domain request again
+ response = self.client.get(
+ "/admin/registrar/domainrequest/{}/change/".format(domain_request.pk),
+ follow=True,
+ )
+
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain_request.requested_domain.name)
+
+ # The modal should be unchanged
+ self.assertContains(response, "Are you sure you want to select ineligible status?")
+ self.assertContains(response, "When a domain request is in ineligible status")
+ self.assertContains(response, "Yes, select ineligible status")
+
+ @less_console_noise_decorator
+ def test_readonly_fields_for_analyst(self):
+ self.site = AdminSite()
+ self.factory = RequestFactory()
+ self.admin = DomainRequestAdmin(model=DomainRequest, admin_site=self.site)
+ request = self.factory.get("/") # Use the correct method and path
+ request.user = self.staffuser
+
+ readonly_fields = self.admin.get_readonly_fields(request)
+
+ expected_fields = [
+ "other_contacts",
+ "current_websites",
+ "alternative_domains",
+ "is_election_board",
+ "federal_agency",
+ "creator",
+ "about_your_organization",
+ "requested_domain",
+ "approved_domain",
+ "alternative_domains",
+ "purpose",
+ "submitter",
+ "no_other_contacts_rationale",
+ "anything_else",
+ "is_policy_acknowledged",
+ "cisa_representative_first_name",
+ "cisa_representative_last_name",
+ "cisa_representative_email",
+ ]
+
+ self.assertEqual(readonly_fields, expected_fields)
+
+ @less_console_noise_decorator
+ def test_displays_investigator_filter(self):
+ """
+ This test verifies that the investigator filter in the admin interface for
+ the DomainRequest model displays correctly.
+
+ It creates two DomainRequest instances, each with a different investigator.
+ It then simulates a staff user logging in and applying the investigator filter
+ on the DomainRequest admin page.
+
+ We then test if the page displays the filter we expect, but we do not test
+ if we get back the correct response in the table. This is to isolate if
+ the filter displays correctly, when the filter isn't filtering correctly.
+ """
+ # Create a mock DomainRequest object, with a fake investigator
+ domain_request: DomainRequest = generic_domain_object("domain_request", "SomeGuy")
+ investigator_user = User.objects.filter(username=domain_request.investigator.username).get()
+ investigator_user.is_staff = True
+ investigator_user.save()
+
+ p = "userpass"
+ self.client.login(username="staffuser", password=p)
+ response = self.client.get(
+ "/admin/registrar/domainrequest/",
+ {
+ "investigator__id__exact": investigator_user.id,
+ },
+ follow=True,
+ )
+
+ # Then, test if the filter actually exists
+ self.assertIn("filters", response.context)
+
+ # Assert the content of filters and search_query
+ filters = response.context["filters"]
+
+ self.assertEqual(
+ filters,
+ [
+ {
+ "parameter_name": "investigator",
+ "parameter_value": "SomeGuy first_name:investigator SomeGuy last_name:investigator",
+ },
+ ],
+ )
+
+ @less_console_noise_decorator
+ def test_investigator_dropdown_displays_only_staff(self):
+ """
+ This test verifies that the dropdown for the 'investigator' field in the DomainRequestAdmin
+ interface only displays users who are marked as staff.
+
+ It creates two DomainRequest instances, one with an investigator
+ who is a staff user and another with an investigator who is not a staff user.
+
+ It then retrieves the queryset for the 'investigator' dropdown from DomainRequestAdmin
+ and checks that it matches the expected queryset, which only includes staff users.
+ """
+ # Create a mock DomainRequest object, with a fake investigator
+ domain_request: DomainRequest = generic_domain_object("domain_request", "SomeGuy")
+ investigator_user = User.objects.filter(username=domain_request.investigator.username).get()
+ investigator_user.is_staff = True
+ investigator_user.save()
+
+ # Create a mock DomainRequest object, with a user that is not staff
+ domain_request_2: DomainRequest = generic_domain_object("domain_request", "SomeOtherGuy")
+ investigator_user_2 = User.objects.filter(username=domain_request_2.investigator.username).get()
+ investigator_user_2.is_staff = False
+ investigator_user_2.save()
+
+ p = "userpass"
+ self.client.login(username="staffuser", password=p)
+
+ self.site = AdminSite()
+ self.factory = RequestFactory()
+ self.admin = DomainRequestAdmin(model=DomainRequest, admin_site=self.site)
+ request = self.factory.post("/admin/registrar/domainrequest/{}/change/".format(domain_request.pk))
+
+ # Get the actual field from the model's meta information
+ investigator_field = DomainRequest._meta.get_field("investigator")
+
+ # We should only be displaying staff users, in alphabetical order
+ sorted_fields = ["first_name", "last_name", "email"]
+ expected_dropdown = list(User.objects.filter(is_staff=True).order_by(*sorted_fields))
+
+ # Grab the current dropdown. We do an API call to autocomplete to get this info.
+ domain_request_queryset = self.admin.formfield_for_foreignkey(investigator_field, request).queryset
+ user_request = self.factory.post(
+ "/admin/autocomplete/?app_label=registrar&model_name=domainrequest&field_name=investigator"
+ )
+ user_admin = MyUserAdmin(User, self.site)
+ user_queryset = user_admin.get_search_results(user_request, domain_request_queryset, None)[0]
+ current_dropdown = list(user_queryset)
+
+ self.assertEqual(expected_dropdown, current_dropdown)
+
+ # Non staff users should not be in the list
+ self.assertNotIn(domain_request_2, current_dropdown)
+
+ @less_console_noise_decorator
+ def test_investigator_list_is_alphabetically_sorted(self):
+ """
+ This test verifies that filter list for the 'investigator'
+ is displayed alphabetically
+ """
+ # Create a mock DomainRequest object, with a fake investigator
+ domain_request: DomainRequest = generic_domain_object("domain_request", "SomeGuy")
+ investigator_user = User.objects.filter(username=domain_request.investigator.username).get()
+ investigator_user.is_staff = True
+ investigator_user.save()
+
+ domain_request_2: DomainRequest = generic_domain_object("domain_request", "AGuy")
+ investigator_user_2 = User.objects.filter(username=domain_request_2.investigator.username).get()
+ investigator_user_2.first_name = "AGuy"
+ investigator_user_2.is_staff = True
+ investigator_user_2.save()
+
+ domain_request_3: DomainRequest = generic_domain_object("domain_request", "FinalGuy")
+ investigator_user_3 = User.objects.filter(username=domain_request_3.investigator.username).get()
+ investigator_user_3.first_name = "FinalGuy"
+ investigator_user_3.is_staff = True
+ investigator_user_3.save()
+
+ p = "userpass"
+ self.client.login(username="staffuser", password=p)
+ self.site = AdminSite()
+ self.factory = RequestFactory()
+ self.admin = DomainRequestAdmin(model=DomainRequest, admin_site=self.site)
+ request = self.factory.get("/")
+
+ # These names have metadata embedded in them. :investigator implicitly tests if
+ # these are actually from the attribute "investigator".
+ expected_list = [
+ "AGuy AGuy last_name:investigator",
+ "FinalGuy FinalGuy last_name:investigator",
+ "SomeGuy first_name:investigator SomeGuy last_name:investigator",
+ ]
+
+ # Get the actual sorted list of investigators from the lookups method
+ actual_list = [item for _, item in self.admin.InvestigatorFilter.lookups(self, request, self.admin)]
+
+ self.assertEqual(expected_list, actual_list)
+
+ @less_console_noise_decorator
+ def test_staff_can_see_cisa_region_federal(self):
+ """Tests if staff can see CISA Region: N/A"""
+
+ # Create a fake domain request
+ _domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
+
+ p = "userpass"
+ self.client.login(username="staffuser", password=p)
+ response = self.client.get(
+ "/admin/registrar/domainrequest/{}/change/".format(_domain_request.pk),
+ follow=True,
+ )
+
+ # Make sure the page loaded, and that we're on the right page
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, _domain_request.requested_domain.name)
+
+ # Test if the page has the right CISA region
+ expected_html = '
CISA region: N/A
'
+ # Remove whitespace from expected_html
+ expected_html = "".join(expected_html.split())
+
+ # Remove whitespace from response content
+ response_content = "".join(response.content.decode().split())
+
+ # Check if response contains expected_html
+ self.assertIn(expected_html, response_content)
+
+ @less_console_noise_decorator
+ def test_staff_can_see_cisa_region_non_federal(self):
+ """Tests if staff can see the correct CISA region"""
+
+ # Create a fake domain request. State will be NY (2).
+ _domain_request = completed_domain_request(
+ status=DomainRequest.DomainRequestStatus.IN_REVIEW, generic_org_type="interstate"
+ )
+
+ p = "userpass"
+ self.client.login(username="staffuser", password=p)
+ response = self.client.get(
+ "/admin/registrar/domainrequest/{}/change/".format(_domain_request.pk),
+ follow=True,
+ )
+
+ # Make sure the page loaded, and that we're on the right page
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, _domain_request.requested_domain.name)
+
+ # Test if the page has the right CISA region
+ expected_html = '
CISA region: 2
'
+ # Remove whitespace from expected_html
+ expected_html = "".join(expected_html.split())
+
+ # Remove whitespace from response content
+ response_content = "".join(response.content.decode().split())
+
+ # Check if response contains expected_html
+ self.assertIn(expected_html, response_content)
+
+
+# @boto3_mocking.patching
+# class TestDomainRequestAdmin(MockEppLib):
+# def setUp(self):
+# super().setUp()
+# self.site = AdminSite()
+# self.factory = RequestFactory()
+# self.admin = DomainRequestAdmin(model=DomainRequest, admin_site=self.site)
+# self.superuser = create_superuser()
+# self.staffuser = create_user()
+# self.client = Client(HTTP_HOST="localhost:8080")
+# self.test_helper = GenericTestHelper(
+# factory=self.factory,
+# user=self.superuser,
+# admin=self.admin,
+# url="/admin/registrar/domainrequest/",
+# model=DomainRequest,
+# )
+# self.mock_client = MockSESClient()
+
+ @less_console_noise_decorator
+ def test_has_model_description(self):
+ """Tests if this model has a model description on the table view"""
+ p = "adminpass"
+ self.client.login(username="superuser", password=p)
+ response = self.client.get(
+ "/admin/registrar/domainrequest/",
+ follow=True,
+ )
+
+ # Make sure that the page is loaded correctly
+ self.assertEqual(response.status_code, 200)
+
+ # Test for a description snippet
+ self.assertContains(response, "This table contains all domain requests")
+ self.assertContains(response, "Show more")
+
+ @less_console_noise_decorator
+ def test_helper_text(self):
+ """
+ Tests for the correct helper text on this page
+ """
+
+ # Create a fake domain request and domain
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
+
+ p = "adminpass"
+ self.client.login(username="superuser", password=p)
+ response = self.client.get(
+ "/admin/registrar/domainrequest/{}/change/".format(domain_request.pk),
+ follow=True,
+ )
+
+ # Make sure the page loaded, and that we're on the right page
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain_request.requested_domain.name)
+
+ # These should exist in the response
+ expected_values = [
+ ("creator", "Person who submitted the domain request; will not receive email updates"),
+ (
+ "submitter",
+ 'Person listed under "your contact information" in the request form; will receive email updates',
+ ),
+ ("approved_domain", "Domain associated with this request; will be blank until request is approved"),
+ ("no_other_contacts_rationale", "Required if creator does not list other employees"),
+ ("alternative_domains", "Other domain names the creator provided for consideration"),
+ ("no_other_contacts_rationale", "Required if creator does not list other employees"),
+ ("Urbanization", "Required for Puerto Rico only"),
+ ]
+ self.test_helper.assert_response_contains_distinct_values(response, expected_values)
+
+ @less_console_noise_decorator
+ def test_status_logs(self):
+ """
+ Tests that the status changes are shown in a table on the domain request change form,
+ accurately and in chronological order.
+ """
+
+ def assert_status_count(normalized_content, status, count):
+ """Helper function to assert the count of a status in the HTML content."""
+ self.assertEqual(normalized_content.count(f"
{status} | "), count)
+
+ def assert_status_order(normalized_content, statuses):
+ """Helper function to assert the order of statuses in the HTML content."""
+ start_index = 0
+ for status in statuses:
+ index = normalized_content.find(f"
{status} | ", start_index)
+ self.assertNotEqual(index, -1, f"Status '{status}' not found in the expected order.")
+ start_index = index + len(status)
+
+ # Create a fake domain request and domain
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.STARTED)
+
+ p = "adminpass"
+ self.client.login(username="superuser", password=p)
+ response = self.client.get(
+ "/admin/registrar/domainrequest/{}/change/".format(domain_request.pk),
+ follow=True,
+ )
+
+ # Make sure the page loaded, and that we're on the right page
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain_request.requested_domain.name)
+
+ domain_request.submit()
+ domain_request.save()
+
+ domain_request.in_review()
+ domain_request.save()
+
+ domain_request.action_needed()
+ domain_request.action_needed_reason = DomainRequest.ActionNeededReasons.ALREADY_HAS_DOMAINS
+ domain_request.save()
+
+ # Let's just change the action needed reason
+ domain_request.action_needed_reason = DomainRequest.ActionNeededReasons.ELIGIBILITY_UNCLEAR
+ domain_request.save()
+
+ domain_request.reject()
+ domain_request.rejection_reason = DomainRequest.RejectionReasons.DOMAIN_PURPOSE
+ domain_request.save()
+
+ domain_request.in_review()
+ domain_request.save()
+
+ response = self.client.get(
+ "/admin/registrar/domainrequest/{}/change/".format(domain_request.pk),
+ follow=True,
+ )
+
+ # Normalize the HTML response content
+ normalized_content = " ".join(response.content.decode("utf-8").split())
+
+ # Define the expected sequence of status changes
+ expected_status_changes = [
+ "In review",
+ "Rejected - Purpose requirements not met",
+ "Action needed - Unclear organization eligibility",
+ "Action needed - Already has domains",
+ "In review",
+ "Submitted",
+ "Started",
+ ]
+
+ assert_status_order(normalized_content, expected_status_changes)
+
+ assert_status_count(normalized_content, "Started", 1)
+ assert_status_count(normalized_content, "Submitted", 1)
+ assert_status_count(normalized_content, "In review", 2)
+ assert_status_count(normalized_content, "Action needed - Already has domains", 1)
+ assert_status_count(normalized_content, "Action needed - Unclear organization eligibility", 1)
+ assert_status_count(normalized_content, "Rejected - Purpose requirements not met", 1)
+
+ @less_console_noise_decorator
+ def test_collaspe_toggle_button_markup(self):
+ """
+ Tests for the correct collapse toggle button markup
+ """
+
+ # Create a fake domain request and domain
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
+
+ p = "adminpass"
+ self.client.login(username="superuser", password=p)
+ response = self.client.get(
+ "/admin/registrar/domainrequest/{}/change/".format(domain_request.pk),
+ follow=True,
+ )
+
+ # Make sure the page loaded, and that we're on the right page
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, domain_request.requested_domain.name)
+ self.test_helper.assertContains(response, "
Show details")
+
+ @less_console_noise_decorator
+ def test_domain_sortable(self):
+ """Tests if the DomainRequest sorts by domain correctly"""
+ p = "adminpass"
+ self.client.login(username="superuser", password=p)
+
+ multiple_unalphabetical_domain_objects("domain_request")
+
+ # Assert that our sort works correctly
+ self.test_helper.assert_table_sorted("1", ("requested_domain__name",))
+
+ # Assert that sorting in reverse works correctly
+ self.test_helper.assert_table_sorted("-1", ("-requested_domain__name",))
+
+ @less_console_noise_decorator
+ def test_submitter_sortable(self):
+ """Tests if the DomainRequest sorts by submitter correctly"""
+ p = "adminpass"
+ self.client.login(username="superuser", password=p)
+
+ multiple_unalphabetical_domain_objects("domain_request")
+
+ additional_domain_request = generic_domain_object("domain_request", "Xylophone")
+ new_user = User.objects.filter(username=additional_domain_request.investigator.username).get()
+ new_user.first_name = "Xylophonic"
+ new_user.save()
+
+ # Assert that our sort works correctly
+ self.test_helper.assert_table_sorted(
+ "11",
+ (
+ "submitter__first_name",
+ "submitter__last_name",
+ ),
+ )
+
+ # Assert that sorting in reverse works correctly
+ self.test_helper.assert_table_sorted(
+ "-11",
+ (
+ "-submitter__first_name",
+ "-submitter__last_name",
+ ),
+ )
+
+ @less_console_noise_decorator
+ def test_investigator_sortable(self):
+ """Tests if the DomainRequest sorts by investigator correctly"""
+ p = "adminpass"
+ self.client.login(username="superuser", password=p)
+
+ multiple_unalphabetical_domain_objects("domain_request")
+ additional_domain_request = generic_domain_object("domain_request", "Xylophone")
+ new_user = User.objects.filter(username=additional_domain_request.investigator.username).get()
+ new_user.first_name = "Xylophonic"
+ new_user.save()
+
+ # Assert that our sort works correctly
+ self.test_helper.assert_table_sorted(
+ "12",
+ (
+ "investigator__first_name",
+ "investigator__last_name",
+ ),
+ )
+
+ # Assert that sorting in reverse works correctly
+ self.test_helper.assert_table_sorted(
+ "-12",
+ (
+ "-investigator__first_name",
+ "-investigator__last_name",
+ ),
+ )
+
+ @less_console_noise_decorator
+ def test_default_sorting_in_domain_requests_list(self):
+ """
+ Make sure the default sortin in on the domain requests list page is reverse submission_date
+ then alphabetical requested_domain
+ """
+
+ # Create domain requests with different names
+ domain_requests = [
+ completed_domain_request(status=DomainRequest.DomainRequestStatus.SUBMITTED, name=name)
+ for name in ["ccc.gov", "bbb.gov", "eee.gov", "aaa.gov", "zzz.gov", "ddd.gov"]
+ ]
+
+ domain_requests[0].submission_date = timezone.make_aware(datetime(2024, 10, 16))
+ domain_requests[1].submission_date = timezone.make_aware(datetime(2001, 10, 16))
+ domain_requests[2].submission_date = timezone.make_aware(datetime(1980, 10, 16))
+ domain_requests[3].submission_date = timezone.make_aware(datetime(1998, 10, 16))
+ domain_requests[4].submission_date = timezone.make_aware(datetime(2013, 10, 16))
+ domain_requests[5].submission_date = timezone.make_aware(datetime(1980, 10, 16))
+
+ # Save the modified domain requests to update their attributes in the database
+ for domain_request in domain_requests:
+ domain_request.save()
+
+ # Refresh domain request objects from the database to reflect the changes
+ domain_requests = [DomainRequest.objects.get(pk=domain_request.pk) for domain_request in domain_requests]
+
+ # Login as superuser and retrieve the domain request list page
+ self.client.force_login(self.superuser)
+ response = self.client.get("/admin/registrar/domainrequest/")
+
+ # Check that the response is successful
+ self.assertEqual(response.status_code, 200)
+
+ # Extract the domain names from the response content using regex
+ domain_names_match = re.findall(r"(\w+\.gov)", response.content.decode("utf-8"))
+
+ logger.info(f"domain_names_match {domain_names_match}")
+
+ # Verify that domain names are found
+ self.assertTrue(domain_names_match)
+
+ # Extract the domain names
+ domain_names = [match for match in domain_names_match]
+
+ # Verify that the domain names are displayed in the expected order
+ expected_order = [
+ "ccc.gov",
+ "zzz.gov",
+ "bbb.gov",
+ "aaa.gov",
+ "ddd.gov",
+ "eee.gov",
+ ]
+
+ # Remove duplicates
+ # Remove duplicates from domain_names list while preserving order
+ unique_domain_names = []
+ for domain_name in domain_names:
+ if domain_name not in unique_domain_names:
+ unique_domain_names.append(domain_name)
+
+ self.assertEqual(unique_domain_names, expected_order)
+
+ @less_console_noise_decorator
+ def test_short_org_name_in_domain_requests_list(self):
+ """
+ Make sure the short name is displaying in admin on the list page
+ """
+ self.client.force_login(self.superuser)
+ completed_domain_request()
+ response = self.client.get("/admin/registrar/domainrequest/?generic_org_type__exact=federal")
+ # There are 2 template references to Federal (4) and two in the results data
+ # of the request
+ self.assertContains(response, "Federal", count=52)
+ # This may be a bit more robust
+ self.assertContains(response, '
Federal | ', count=1)
+ # Now let's make sure the long description does not exist
+ self.assertNotContains(response, "Federal: an agency of the U.S. government")
+
+ @less_console_noise_decorator
+ def test_default_status_in_domain_requests_list(self):
+ """
+ Make sure the default status in admin is selected on the domain requests list page
+ """
+ self.client.force_login(self.superuser)
+ completed_domain_request()
+ response = self.client.get("/admin/registrar/domainrequest/")
+ # The results are filtered by "status in [submitted,in review,action needed]"
+ self.assertContains(response, "status in [submitted,in review,action needed]", count=1)
+
+ @less_console_noise_decorator
+ def transition_state_and_send_email(self, domain_request, status, rejection_reason=None, action_needed_reason=None):
+ """Helper method for the email test cases."""
+
+ with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
+ # Create a mock request
+ request = self.factory.post("/admin/registrar/domainrequest/{}/change/".format(domain_request.pk))
+
+ # Modify the domain request's properties
+ domain_request.status = status
+
+ if rejection_reason:
+ domain_request.rejection_reason = rejection_reason
+
+ if action_needed_reason:
+ domain_request.action_needed_reason = action_needed_reason
+
+ # Use the model admin's save_model method
+ self.admin.save_model(request, domain_request, form=None, change=True)
+
+ @less_console_noise_decorator
+ 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.
+ email_index is the index of the email in mock_client."""
+
+ # Access the arguments passed to send_email
+ call_args = self.mock_client.EMAILS_SENT
+ kwargs = call_args[email_index]["kwargs"]
+
+ # Retrieve the email details from the arguments
+ from_email = kwargs.get("FromEmailAddress")
+ to_email = kwargs["Destination"]["ToAddresses"][0]
+ email_content = kwargs["Content"]
+ email_body = email_content["Simple"]["Body"]["Text"]["Data"]
+
+ # Assert or perform other checks on the email details
+ self.assertEqual(from_email, settings.DEFAULT_FROM_EMAIL)
+ self.assertEqual(to_email, email_address)
+ 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)
+
+ @less_console_noise_decorator
+ @override_settings(IS_PRODUCTION=True)
+ def test_action_needed_sends_reason_email_prod_bcc(self):
+ """When an action needed reason is set, an email is sent out and help@get.gov
+ is BCC'd in production"""
+ # Ensure there is no user with this email
+ EMAIL = "mayor@igorville.gov"
+ BCC_EMAIL = settings.DEFAULT_FROM_EMAIL
+ User.objects.filter(email=EMAIL).delete()
+ in_review = DomainRequest.DomainRequestStatus.IN_REVIEW
+ action_needed = DomainRequest.DomainRequestStatus.ACTION_NEEDED
+
+ # Create a sample domain request
+ domain_request = completed_domain_request(status=in_review)
+
+ # Test the email sent out for already_has_domains
+ already_has_domains = DomainRequest.ActionNeededReasons.ALREADY_HAS_DOMAINS
+ self.transition_state_and_send_email(domain_request, action_needed, action_needed_reason=already_has_domains)
+ self.assert_email_is_accurate("ORGANIZATION ALREADY HAS A .GOV DOMAIN", 0, EMAIL, bcc_email_address=BCC_EMAIL)
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
+
+ # Test the email sent out for bad_name
+ bad_name = DomainRequest.ActionNeededReasons.BAD_NAME
+ self.transition_state_and_send_email(domain_request, action_needed, action_needed_reason=bad_name)
+ self.assert_email_is_accurate(
+ "DOMAIN NAME DOES NOT MEET .GOV REQUIREMENTS", 1, EMAIL, bcc_email_address=BCC_EMAIL
+ )
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 2)
+
+ # Test the email sent out for eligibility_unclear
+ eligibility_unclear = DomainRequest.ActionNeededReasons.ELIGIBILITY_UNCLEAR
+ self.transition_state_and_send_email(domain_request, action_needed, action_needed_reason=eligibility_unclear)
+ self.assert_email_is_accurate(
+ "ORGANIZATION MAY NOT MEET ELIGIBILITY REQUIREMENTS", 2, EMAIL, bcc_email_address=BCC_EMAIL
+ )
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
+
+ # Test the email sent out for questionable_so
+ questionable_so = DomainRequest.ActionNeededReasons.QUESTIONABLE_SENIOR_OFFICIAL
+ self.transition_state_and_send_email(domain_request, action_needed, action_needed_reason=questionable_so)
+ self.assert_email_is_accurate(
+ "SENIOR OFFICIAL DOES NOT MEET ELIGIBILITY REQUIREMENTS", 3, EMAIL, bcc_email_address=BCC_EMAIL
+ )
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 4)
+
+ # Assert that no other emails are sent on OTHER
+ other = DomainRequest.ActionNeededReasons.OTHER
+ self.transition_state_and_send_email(domain_request, action_needed, action_needed_reason=other)
+
+ # Should be unchanged from before
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 4)
+
+ @less_console_noise_decorator
+ def test_save_model_sends_submitted_email(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 NOT BCCd on non-prod whenever
+ an email does go out."""
+
+ # Ensure there is no user with this email
+ EMAIL = "mayor@igorville.gov"
+ User.objects.filter(email=EMAIL).delete()
+
+ # Create a sample domain request
+ domain_request = completed_domain_request()
+
+ # Test Submitted Status from started
+ self.transition_state_and_send_email(domain_request, DomainRequest.DomainRequestStatus.SUBMITTED)
+ 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(domain_request, DomainRequest.DomainRequestStatus.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(domain_request, DomainRequest.DomainRequestStatus.SUBMITTED)
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
+
+ # Move it to IN_REVIEW
+ other = DomainRequest.ActionNeededReasons.OTHER
+ in_review = DomainRequest.DomainRequestStatus.IN_REVIEW
+ self.transition_state_and_send_email(domain_request, in_review, action_needed_reason=other)
+ 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(domain_request, DomainRequest.DomainRequestStatus.SUBMITTED)
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
+
+ # Move it to IN_REVIEW
+ self.transition_state_and_send_email(domain_request, in_review, action_needed_reason=other)
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
+
+ # Move it to ACTION_NEEDED
+ self.transition_state_and_send_email(domain_request, DomainRequest.DomainRequestStatus.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(domain_request, DomainRequest.DomainRequestStatus.SUBMITTED)
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
+
+ @less_console_noise_decorator
+ @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."""
+
+ # 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 domain request
+ domain_request = completed_domain_request()
+
+ # Test Submitted Status from started
+ self.transition_state_and_send_email(domain_request, DomainRequest.DomainRequestStatus.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)
+
+ # Test Withdrawn Status
+ self.transition_state_and_send_email(domain_request, DomainRequest.DomainRequestStatus.WITHDRAWN)
+ self.assert_email_is_accurate(
+ "Your .gov domain request has been withdrawn and will not be reviewed by our team.", 1, EMAIL
+ )
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 2)
+
+ # Test Submitted Status Again (from withdrawn)
+ self.transition_state_and_send_email(domain_request, DomainRequest.DomainRequestStatus.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)
+
+ # Move it to IN_REVIEW
+ other = domain_request.ActionNeededReasons.OTHER
+ in_review = DomainRequest.DomainRequestStatus.IN_REVIEW
+ self.transition_state_and_send_email(domain_request, in_review, action_needed_reason=other)
+ 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(domain_request, DomainRequest.DomainRequestStatus.SUBMITTED)
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
+
+ # Move it to IN_REVIEW
+ self.transition_state_and_send_email(domain_request, in_review, action_needed_reason=other)
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
+
+ # Move it to ACTION_NEEDED
+ self.transition_state_and_send_email(domain_request, DomainRequest.DomainRequestStatus.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(domain_request, DomainRequest.DomainRequestStatus.SUBMITTED)
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
+
+ @less_console_noise_decorator
+ def test_save_model_sends_approved_email(self):
+ """When transitioning to approved on a domain request,
+ an email is sent out every time."""
+
+ # Ensure there is no user with this email
+ EMAIL = "mayor@igorville.gov"
+ User.objects.filter(email=EMAIL).delete()
+
+ # Create a sample domain request
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
+
+ # Test Submitted Status
+ self.transition_state_and_send_email(domain_request, DomainRequest.DomainRequestStatus.APPROVED)
+ self.assert_email_is_accurate("Congratulations! Your .gov domain request has been approved.", 0, EMAIL)
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
+
+ # Test Withdrawn Status
+ self.transition_state_and_send_email(
+ domain_request,
+ DomainRequest.DomainRequestStatus.REJECTED,
+ DomainRequest.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)
+
+ # Test Submitted Status Again (No new email should be sent)
+ self.transition_state_and_send_email(domain_request, DomainRequest.DomainRequestStatus.APPROVED)
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
+
+ @less_console_noise_decorator
+ def test_save_model_sends_rejected_email_purpose_not_met(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"
+ User.objects.filter(email=EMAIL).delete()
+
+ # Create a sample domain request
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
+
+ # Reject for reason DOMAIN_PURPOSE and test email
+ self.transition_state_and_send_email(
+ domain_request,
+ DomainRequest.DomainRequestStatus.REJECTED,
+ DomainRequest.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)
+
+ # Approve
+ self.transition_state_and_send_email(domain_request, DomainRequest.DomainRequestStatus.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)
+
+ @less_console_noise_decorator
+ 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 domain request
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
+
+ # Reject for reason REQUESTOR and test email including dynamic organization name
+ self.transition_state_and_send_email(
+ domain_request, DomainRequest.DomainRequestStatus.REJECTED, DomainRequest.RejectionReasons.REQUESTOR
+ )
+ self.assert_email_is_accurate(
+ "Your domain request was rejected because we don’t believe you’re eligible to request a \n.gov "
+ "domain on behalf of Testorg",
+ 0,
+ EMAIL,
+ )
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
+
+ # Approve
+ self.transition_state_and_send_email(domain_request, DomainRequest.DomainRequestStatus.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)
+
+ @less_console_noise_decorator
+ def test_save_model_sends_rejected_email_org_has_domain(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 domain request
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
+
+ # Reject for reason SECOND_DOMAIN_REASONING and test email including dynamic organization name
+ self.transition_state_and_send_email(
+ domain_request,
+ DomainRequest.DomainRequestStatus.REJECTED,
+ DomainRequest.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(domain_request, DomainRequest.DomainRequestStatus.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)
+
+ @less_console_noise_decorator
+ 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 domain request
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
+
+ # Reject for reason CONTACTS_OR_ORGANIZATION_LEGITIMACY and test email including dynamic organization name
+ self.transition_state_and_send_email(
+ domain_request,
+ DomainRequest.DomainRequestStatus.REJECTED,
+ DomainRequest.RejectionReasons.CONTACTS_OR_ORGANIZATION_LEGITIMACY,
+ )
+ self.assert_email_is_accurate(
+ "Your domain request was rejected because we could not verify the organizational \n"
+ "contacts 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(domain_request, DomainRequest.DomainRequestStatus.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)
+
+ @less_console_noise_decorator
+ 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 domain request
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
+
+ # Reject for reason ORGANIZATION_ELIGIBILITY and test email including dynamic organization name
+ self.transition_state_and_send_email(
+ domain_request,
+ DomainRequest.DomainRequestStatus.REJECTED,
+ DomainRequest.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(domain_request, DomainRequest.DomainRequestStatus.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)
+
+ @less_console_noise_decorator
+ 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 domain request
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
+
+ # Reject for reason NAMING_REQUIREMENTS and test email including dynamic organization name
+ self.transition_state_and_send_email(
+ domain_request,
+ DomainRequest.DomainRequestStatus.REJECTED,
+ DomainRequest.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(domain_request, DomainRequest.DomainRequestStatus.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_other(self):
+ """When transitioning to rejected on a domain request, an email is sent
+ explaining why when the reason is other."""
+
+ # Ensure there is no user with this email
+ EMAIL = "mayor@igorville.gov"
+ User.objects.filter(email=EMAIL).delete()
+
+ # Create a sample domain request
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
+
+ # Reject for reason NAMING_REQUIREMENTS and test email including dynamic organization name
+ self.transition_state_and_send_email(
+ domain_request,
+ DomainRequest.DomainRequestStatus.REJECTED,
+ DomainRequest.RejectionReasons.OTHER,
+ )
+ self.assert_email_is_accurate("Choosing a .gov domain name", 0, EMAIL)
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
+
+ # Approve
+ self.transition_state_and_send_email(domain_request, DomainRequest.DomainRequestStatus.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)
+
+ @less_console_noise_decorator
+ def test_transition_to_rejected_without_rejection_reason_does_trigger_error(self):
+ """
+ When transitioning to rejected without a rejection reason, admin throws a user friendly message.
+
+ The transition fails.
+ """
+
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.APPROVED)
+
+ # Create a request object with a superuser
+ request = self.factory.post("/admin/registrar/domainrequest/{}/change/".format(domain_request.pk))
+ request.user = self.superuser
+
+ with ExitStack() as stack:
+ stack.enter_context(patch.object(messages, "error"))
+ domain_request.status = DomainRequest.DomainRequestStatus.REJECTED
+
+ self.admin.save_model(request, domain_request, None, True)
+
+ messages.error.assert_called_once_with(
+ request,
+ "A reason is required for this status.",
+ )
+
+ domain_request.refresh_from_db()
+ self.assertEqual(domain_request.status, DomainRequest.DomainRequestStatus.APPROVED)
+
+ @less_console_noise_decorator
+ def test_transition_to_rejected_with_rejection_reason_does_not_trigger_error(self):
+ """
+ When transitioning to rejected with a rejection reason, admin does not throw an error alert.
+
+ The transition is successful.
+ """
+
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.APPROVED)
+
+ # Create a request object with a superuser
+ request = self.factory.post("/admin/registrar/domainrequest/{}/change/".format(domain_request.pk))
+ request.user = self.superuser
+
+ with ExitStack() as stack:
+ stack.enter_context(patch.object(messages, "error"))
+ domain_request.status = DomainRequest.DomainRequestStatus.REJECTED
+ domain_request.rejection_reason = DomainRequest.RejectionReasons.CONTACTS_OR_ORGANIZATION_LEGITIMACY
+
+ self.admin.save_model(request, domain_request, None, True)
+
+ messages.error.assert_not_called()
+
+ domain_request.refresh_from_db()
+ self.assertEqual(domain_request.status, DomainRequest.DomainRequestStatus.REJECTED)
+
+ @less_console_noise_decorator
+ def test_save_model_sends_withdrawn_email(self):
+ """When transitioning to withdrawn on a domain request,
+ an email is sent out every time."""
+
+ # Ensure there is no user with this email
+ EMAIL = "mayor@igorville.gov"
+ User.objects.filter(email=EMAIL).delete()
+
+ # Create a sample domain request
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
+
+ # Test Submitted Status
+ self.transition_state_and_send_email(domain_request, DomainRequest.DomainRequestStatus.WITHDRAWN)
+ self.assert_email_is_accurate(
+ "Your .gov domain request has been withdrawn and will not be reviewed by our team.", 0, EMAIL
+ )
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
+
+ # Test Withdrawn Status
+ self.transition_state_and_send_email(domain_request, DomainRequest.DomainRequestStatus.SUBMITTED)
+ self.assert_email_is_accurate("We received your .gov domain request.", 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(domain_request, DomainRequest.DomainRequestStatus.WITHDRAWN)
+ self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
+
+ @less_console_noise_decorator
+ def test_save_model_sets_approved_domain(self):
+ # make sure there is no user with this email
+ EMAIL = "mayor@igorville.gov"
+ User.objects.filter(email=EMAIL).delete()
+
+ # Create a sample domain request
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
+
+ # Create a mock request
+ request = self.factory.post("/admin/registrar/domainrequest/{}/change/".format(domain_request.pk))
+
+ with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
+ # Modify the domain request's property
+ domain_request.status = DomainRequest.DomainRequestStatus.APPROVED
+
+ # Use the model admin's save_model method
+ self.admin.save_model(request, domain_request, form=None, change=True)
+
+ # Test that approved domain exists and equals requested domain
+ self.assertEqual(domain_request.requested_domain.name, domain_request.approved_domain.name)
+
+ @less_console_noise_decorator
+ def test_sticky_submit_row(self):
+ """Test that the change_form template contains strings indicative of the customization
+ of the sticky submit bar.
+
+ Also test that it does NOT contain a CSS class meant for analysts only when logged in as superuser."""
+
+ # make sure there is no user with this email
+ EMAIL = "mayor@igorville.gov"
+ User.objects.filter(email=EMAIL).delete()
+ self.client.force_login(self.superuser)
+
+ # Create a sample domain request
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
+
+ # Create a mock request
+ request = self.client.post("/admin/registrar/domainrequest/{}/change/".format(domain_request.pk))
+
+ # Since we're using client to mock the request, we can only test against
+ # non-interpolated values
+ expected_content = "Requested domain:"
+ expected_content2 = '
'
+ expected_content3 = '
'
+ not_expected_content = "submit-row-wrapper--analyst-view>"
+ self.assertContains(request, expected_content)
+ self.assertContains(request, expected_content2)
+ self.assertContains(request, expected_content3)
+ self.assertNotContains(request, not_expected_content)
+
+ @less_console_noise_decorator
+ def test_save_model_sets_restricted_status_on_user(self):
+ # make sure there is no user with this email
+ EMAIL = "mayor@igorville.gov"
+ User.objects.filter(email=EMAIL).delete()
+
+ # Create a sample domain request
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
+
+ # Create a mock request
+ request = self.factory.post(
+ "/admin/registrar/domainrequest/{}/change/".format(domain_request.pk), follow=True
+ )
+
+ with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
+ # Modify the domain request's property
+ domain_request.status = DomainRequest.DomainRequestStatus.INELIGIBLE
+
+ # Use the model admin's save_model method
+ self.admin.save_model(request, domain_request, form=None, change=True)
+
+ # Test that approved domain exists and equals requested domain
+ self.assertEqual(domain_request.creator.status, "restricted")
+
+ @less_console_noise_decorator
+ def test_readonly_when_restricted_creator(self):
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
+ with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
+ domain_request.creator.status = User.RESTRICTED
+ domain_request.creator.save()
+
+ request = self.factory.get("/")
+ request.user = self.superuser
+
+ readonly_fields = self.admin.get_readonly_fields(request, domain_request)
+
+ expected_fields = [
+ "other_contacts",
+ "current_websites",
+ "alternative_domains",
+ "is_election_board",
+ "federal_agency",
+ "id",
+ "created_at",
+ "updated_at",
+ "status",
+ "rejection_reason",
+ "action_needed_reason",
+ "action_needed_reason_email",
+ "federal_agency",
+ "portfolio",
+ "sub_organization",
+ "creator",
+ "investigator",
+ "generic_org_type",
+ "is_election_board",
+ "organization_type",
+ "federally_recognized_tribe",
+ "state_recognized_tribe",
+ "tribe_name",
+ "federal_type",
+ "organization_name",
+ "address_line1",
+ "address_line2",
+ "city",
+ "state_territory",
+ "zipcode",
+ "urbanization",
+ "about_your_organization",
+ "senior_official",
+ "approved_domain",
+ "requested_domain",
+ "submitter",
+ "purpose",
+ "no_other_contacts_rationale",
+ "anything_else",
+ "has_anything_else_text",
+ "cisa_representative_email",
+ "cisa_representative_first_name",
+ "cisa_representative_last_name",
+ "has_cisa_representative",
+ "is_policy_acknowledged",
+ "submission_date",
+ "notes",
+ "alternative_domains",
+ ]
+
+ self.assertEqual(readonly_fields, expected_fields)
+
+ @less_console_noise_decorator
+ def test_readonly_fields_for_superuser(self):
+ request = self.factory.get("/") # Use the correct method and path
+ request.user = self.superuser
+
+ readonly_fields = self.admin.get_readonly_fields(request)
+
+ expected_fields = [
+ "other_contacts",
+ "current_websites",
+ "alternative_domains",
+ "is_election_board",
+ "federal_agency",
+ ]
+
+ self.assertEqual(readonly_fields, expected_fields)
+
+ @less_console_noise_decorator
+ def test_saving_when_restricted_creator(self):
+ # Create an instance of the model
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
+ with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
+ domain_request.creator.status = User.RESTRICTED
+ domain_request.creator.save()
+
+ # Create a request object with a superuser
+ request = self.factory.get("/")
+ request.user = self.superuser
+
+ with patch("django.contrib.messages.error") as mock_error:
+ # Simulate saving the model
+ self.admin.save_model(request, domain_request, None, False)
+
+ # Assert that the error message was called with the correct argument
+ mock_error.assert_called_once_with(
+ request,
+ "This action is not permitted for domain requests with a restricted creator.",
+ )
+
+ # Assert that the status has not changed
+ self.assertEqual(domain_request.status, DomainRequest.DomainRequestStatus.IN_REVIEW)
+
+ @less_console_noise_decorator
+ def test_change_view_with_restricted_creator(self):
+ # Create an instance of the model
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
+ with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
+ domain_request.creator.status = User.RESTRICTED
+ domain_request.creator.save()
+
+ with patch("django.contrib.messages.warning") as mock_warning:
+ # Create a request object with a superuser
+ request = self.factory.get("/admin/your_app/domainrequest/{}/change/".format(domain_request.pk))
+ request.user = self.superuser
+
+ self.admin.display_restricted_warning(request, domain_request)
+
+ # Assert that the error message was called with the correct argument
+ mock_warning.assert_called_once_with(
+ request,
+ "Cannot edit a domain request with a restricted creator.",
+ )
+
+ @less_console_noise_decorator
+ 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.
+
+ Used to test errors when saving a change with an active domain, also used to test side effects
+ when saving a change goes through."""
+ # Create an instance of the model
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.APPROVED)
+ domain = Domain.objects.create(name=domain_request.requested_domain.name)
+ domain_information = DomainInformation.objects.create(creator=self.superuser, domain=domain)
+ domain_request.approved_domain = domain
+ domain_request.save()
+
+ # Create a request object with a superuser
+ request = self.factory.post("/admin/registrar/domainrequest/{}/change/".format(domain_request.pk))
+ request.user = self.superuser
+
+ # Define a custom implementation for is_active
+ def custom_is_active(self):
+ return domain_is_active # Override to return True
+
+ # 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(Domain, "is_active", custom_is_active))
+ stack.enter_context(patch.object(messages, "error"))
+
+ domain_request.status = another_state
+
+ if another_state == DomainRequest.DomainRequestStatus.ACTION_NEEDED:
+ domain_request.action_needed_reason = domain_request.ActionNeededReasons.OTHER
+
+ domain_request.rejection_reason = rejection_reason
+
+ self.admin.save_model(request, domain_request, None, True)
+
+ # Assert that the error message was called with the correct argument
+ if domain_is_active:
+ messages.error.assert_called_once_with(
+ request,
+ "This action is not permitted. The domain " + "is already active.",
+ )
+ else:
+ # Assert that the error message was never called
+ messages.error.assert_not_called()
+
+ self.assertEqual(domain_request.approved_domain, None)
+
+ # Assert that Domain got Deleted
+ with self.assertRaises(Domain.DoesNotExist):
+ domain.refresh_from_db()
+
+ # Assert that DomainInformation got Deleted
+ with self.assertRaises(DomainInformation.DoesNotExist):
+ domain_information.refresh_from_db()
+
+ @less_console_noise_decorator
+ def test_error_when_saving_approved_to_in_review_and_domain_is_active(self):
+ self.trigger_saving_approved_to_another_state(True, DomainRequest.DomainRequestStatus.IN_REVIEW)
+
+ @less_console_noise_decorator
+ def test_error_when_saving_approved_to_action_needed_and_domain_is_active(self):
+ self.trigger_saving_approved_to_another_state(True, DomainRequest.DomainRequestStatus.ACTION_NEEDED)
+
+ @less_console_noise_decorator
+ def test_error_when_saving_approved_to_rejected_and_domain_is_active(self):
+ self.trigger_saving_approved_to_another_state(True, DomainRequest.DomainRequestStatus.REJECTED)
+
+ @less_console_noise_decorator
+ def test_error_when_saving_approved_to_ineligible_and_domain_is_active(self):
+ self.trigger_saving_approved_to_another_state(True, DomainRequest.DomainRequestStatus.INELIGIBLE)
+
+ @less_console_noise_decorator
+ def test_side_effects_when_saving_approved_to_in_review(self):
+ self.trigger_saving_approved_to_another_state(False, DomainRequest.DomainRequestStatus.IN_REVIEW)
+
+ @less_console_noise_decorator
+ def test_side_effects_when_saving_approved_to_action_needed(self):
+ self.trigger_saving_approved_to_another_state(False, DomainRequest.DomainRequestStatus.ACTION_NEEDED)
+
+ @less_console_noise_decorator
+ def test_side_effects_when_saving_approved_to_rejected(self):
+ self.trigger_saving_approved_to_another_state(
+ False,
+ DomainRequest.DomainRequestStatus.REJECTED,
+ DomainRequest.RejectionReasons.CONTACTS_OR_ORGANIZATION_LEGITIMACY,
+ )
+
+ @less_console_noise_decorator
+ def test_side_effects_when_saving_approved_to_ineligible(self):
+ self.trigger_saving_approved_to_another_state(False, DomainRequest.DomainRequestStatus.INELIGIBLE)
+
+ @less_console_noise_decorator
+ def test_has_correct_filters(self):
+ """
+ This test verifies that DomainRequestAdmin has the correct filters set up.
+
+ It retrieves the current list of filters from DomainRequestAdmin
+ and checks that it matches the expected list of filters.
+ """
+ request = self.factory.get("/")
+ request.user = self.superuser
+
+ # Grab the current list of table filters
+ readonly_fields = self.admin.get_list_filter(request)
+ expected_fields = (
+ DomainRequestAdmin.StatusListFilter,
+ "generic_org_type",
+ "federal_type",
+ DomainRequestAdmin.ElectionOfficeFilter,
+ "rejection_reason",
+ DomainRequestAdmin.InvestigatorFilter,
+ )
+
+ self.assertEqual(readonly_fields, expected_fields)
+
+ @less_console_noise_decorator
+ def test_table_sorted_alphabetically(self):
+ """
+ This test verifies that the DomainRequestAdmin table is sorted alphabetically
+ by the 'requested_domain__name' field.
+
+ It creates a list of DomainRequest instances in a non-alphabetical order,
+ then retrieves the queryset from the DomainRequestAdmin and checks
+ that it matches the expected queryset,
+ which is sorted alphabetically by the 'requested_domain__name' field.
+ """
+ # Creates a list of DomainRequests in scrambled order
+ multiple_unalphabetical_domain_objects("domain_request")
+
+ request = self.factory.get("/")
+ request.user = self.superuser
+
+ # Get the expected list of alphabetically sorted DomainRequests
+ expected_order = DomainRequest.objects.order_by("requested_domain__name")
+
+ # Get the returned queryset
+ queryset = self.admin.get_queryset(request)
+
+ # Check the order
+ self.assertEqual(
+ list(queryset),
+ list(expected_order),
+ )
+