From 8b93216732946e1f327819d0aa1a9b1ee51a116e Mon Sep 17 00:00:00 2001 From: katypies Date: Tue, 18 Jun 2024 14:34:52 -0600 Subject: [PATCH 001/184] Add Github action to notify users by labels added to issues, and add Katherine for design-review label --- .github/workflows/issue-label-notifier.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/workflows/issue-label-notifier.yaml diff --git a/.github/workflows/issue-label-notifier.yaml b/.github/workflows/issue-label-notifier.yaml new file mode 100644 index 000000000..8e4a35d39 --- /dev/null +++ b/.github/workflows/issue-label-notifier.yaml @@ -0,0 +1,15 @@ +name: Notify users based on issue labels + +on: + issues: + types: [labeled] + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - uses: jenschelkopf/issue-label-notification-action@1.3 + with: + recipients: | + design-review=@Katherine-Osos + message: 'cc/ {recipients} — adding you to this **{label}** issue!' \ No newline at end of file From f76e3619de92bf8ae946ed67b301ae024759b272 Mon Sep 17 00:00:00 2001 From: katypies Date: Thu, 20 Jun 2024 15:53:57 -0600 Subject: [PATCH 002/184] Add notification for when PRs are labeled, too --- .github/workflows/issue-label-notifier.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/issue-label-notifier.yaml b/.github/workflows/issue-label-notifier.yaml index 8e4a35d39..b5c9a93e6 100644 --- a/.github/workflows/issue-label-notifier.yaml +++ b/.github/workflows/issue-label-notifier.yaml @@ -3,6 +3,8 @@ name: Notify users based on issue labels on: issues: types: [labeled] + pull_request: + types: [labeled] jobs: notify: From f84989589762c4ff268742f78065b28406c64b8c Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:09:45 -0600 Subject: [PATCH 003/184] Logic to update label --- src/registrar/assets/js/get-gov.js | 26 +++++++++++++++++++++ src/registrar/assets/sass/_theme/_base.scss | 2 +- src/registrar/forms/user_profile.py | 5 +++- src/registrar/models/contact.py | 7 ++++-- 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js index 7052d786f..6373f176f 100644 --- a/src/registrar/assets/js/get-gov.js +++ b/src/registrar/assets/js/get-gov.js @@ -1834,6 +1834,32 @@ document.addEventListener('DOMContentLoaded', function() { // When the edit button is clicked, show the input field under it handleEditButtonClick(fieldName, button); + + let editableFormGroup = button.parentElement.parentElement.parentElement; + if (editableFormGroup){ + let readonlyField = editableFormGroup.querySelector(".input-with-edit-button__readonly-field") + let inputField = document.getElementById(`id_${fieldName}`).value; + if (!inputField) { + return; + } + + let inputFieldValue = inputField.value + if (readonlyField && inputFieldValue){ + if (fieldName == "full_name"){ + let firstName = document.querySelector(`#id_first_name`).value; + let middleName = document.querySelector(`#id_middle_name`).value; + let lastName = document.querySelector(`#id_last_name`).value; + if (firstName && middleName && lastName) { + let values = [firstName.value, middleName.value, lastName.value] + readonlyField.innerHTML = values.join(" "); + }else { + readonlyField.innerHTML = "Unknown"; + } + }else { + readonlyField.innerHTML = inputValue; + } + } + } } }); } diff --git a/src/registrar/assets/sass/_theme/_base.scss b/src/registrar/assets/sass/_theme/_base.scss index b2bad1edb..b7cedfe85 100644 --- a/src/registrar/assets/sass/_theme/_base.scss +++ b/src/registrar/assets/sass/_theme/_base.scss @@ -190,7 +190,7 @@ abbr[title] { svg.usa-icon { color: #{$dhs-red}; } - div.readonly-field { + div.input-with-edit-button__readonly-field-field { color: #{$dhs-red}; } } diff --git a/src/registrar/forms/user_profile.py b/src/registrar/forms/user_profile.py index 682e1a5df..60e5032c8 100644 --- a/src/registrar/forms/user_profile.py +++ b/src/registrar/forms/user_profile.py @@ -93,4 +93,7 @@ class FinishSetupProfileForm(UserProfileForm): self.fields["title"].label = "Title or role in your organization" # Define the "full_name" value - self.fields["full_name"].initial = self.instance.get_formatted_name() + full_name = None + if self.instance.first_name and self.instance.last_name: + full_name = self.instance.get_formatted_name(return_unknown_when_none=False) + self.fields["full_name"].initial = full_name diff --git a/src/registrar/models/contact.py b/src/registrar/models/contact.py index f94938dd1..b0d6f3ac3 100644 --- a/src/registrar/models/contact.py +++ b/src/registrar/models/contact.py @@ -102,10 +102,13 @@ class Contact(TimeStampedModel): return getattr(self, relation).count() > threshold return False - def get_formatted_name(self): + def get_formatted_name(self, return_unknown_when_none=True): """Returns the contact's name in Western order.""" names = [n for n in [self.first_name, self.middle_name, self.last_name] if n] - return " ".join(names) if names else "Unknown" + if names: + return " ".join(names) + else: + return "Unknown" if return_unknown_when_none else None def has_contact_info(self): return bool(self.title or self.email or self.phone) From 08a3ba53780f2f5dde7f62d050bfc242b3ff8262 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:11:48 -0600 Subject: [PATCH 004/184] css changes --- src/registrar/assets/sass/_theme/_base.scss | 2 +- src/registrar/templates/includes/readonly_input.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_base.scss b/src/registrar/assets/sass/_theme/_base.scss index b7cedfe85..5fb8ce86b 100644 --- a/src/registrar/assets/sass/_theme/_base.scss +++ b/src/registrar/assets/sass/_theme/_base.scss @@ -190,7 +190,7 @@ abbr[title] { svg.usa-icon { color: #{$dhs-red}; } - div.input-with-edit-button__readonly-field-field { + div.input-with-edit-button__readonly-field { color: #{$dhs-red}; } } diff --git a/src/registrar/templates/includes/readonly_input.html b/src/registrar/templates/includes/readonly_input.html index ebd5d788e..47db97f00 100644 --- a/src/registrar/templates/includes/readonly_input.html +++ b/src/registrar/templates/includes/readonly_input.html @@ -8,7 +8,7 @@ {%endif %} -
+
{% if field.name != "phone" %} {{ field.value }} {% else %} From a9c91bc94a79703b8eff9e4a976f027311278106 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:13:38 -0600 Subject: [PATCH 005/184] js bug --- src/registrar/assets/js/get-gov.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js index 6373f176f..73e5d1ee7 100644 --- a/src/registrar/assets/js/get-gov.js +++ b/src/registrar/assets/js/get-gov.js @@ -1838,7 +1838,7 @@ document.addEventListener('DOMContentLoaded', function() { let editableFormGroup = button.parentElement.parentElement.parentElement; if (editableFormGroup){ let readonlyField = editableFormGroup.querySelector(".input-with-edit-button__readonly-field") - let inputField = document.getElementById(`id_${fieldName}`).value; + let inputField = document.getElementById(`id_${fieldName}`); if (!inputField) { return; } From 7a3f00971378824b140e32b610e26dbff2e6ae2a Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:54:10 -0600 Subject: [PATCH 006/184] Edge cases --- src/registrar/assets/js/get-gov.js | 4 +--- src/registrar/forms/user_profile.py | 7 +++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js index 73e5d1ee7..34e22e49d 100644 --- a/src/registrar/assets/js/get-gov.js +++ b/src/registrar/assets/js/get-gov.js @@ -1844,7 +1844,7 @@ document.addEventListener('DOMContentLoaded', function() { } let inputFieldValue = inputField.value - if (readonlyField && inputFieldValue){ + if (readonlyField && inputFieldValue || fieldName == "full_name"){ if (fieldName == "full_name"){ let firstName = document.querySelector(`#id_first_name`).value; let middleName = document.querySelector(`#id_middle_name`).value; @@ -1855,8 +1855,6 @@ document.addEventListener('DOMContentLoaded', function() { }else { readonlyField.innerHTML = "Unknown"; } - }else { - readonlyField.innerHTML = inputValue; } } } diff --git a/src/registrar/forms/user_profile.py b/src/registrar/forms/user_profile.py index 60e5032c8..02bc4e58f 100644 --- a/src/registrar/forms/user_profile.py +++ b/src/registrar/forms/user_profile.py @@ -71,7 +71,7 @@ class UserProfileForm(forms.ModelForm): class FinishSetupProfileForm(UserProfileForm): """Form for updating user profile.""" - full_name = forms.CharField(required=True, label="Full name") + full_name = forms.CharField(required=False, label="Full name") def clean(self): cleaned_data = super().clean() @@ -93,7 +93,10 @@ class FinishSetupProfileForm(UserProfileForm): self.fields["title"].label = "Title or role in your organization" # Define the "full_name" value - full_name = None + full_name = "" if self.instance.first_name and self.instance.last_name: full_name = self.instance.get_formatted_name(return_unknown_when_none=False) self.fields["full_name"].initial = full_name + + # Set full_name as required for styling purposes + self.fields["full_name"].widget.attrs['required'] = 'required' From 2c6aa1bab1143c3965edd0aafd3040778334ae65 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:56:15 -0600 Subject: [PATCH 007/184] Update get-gov.js --- src/registrar/assets/js/get-gov.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js index 34e22e49d..30b80d356 100644 --- a/src/registrar/assets/js/get-gov.js +++ b/src/registrar/assets/js/get-gov.js @@ -1844,7 +1844,7 @@ document.addEventListener('DOMContentLoaded', function() { } let inputFieldValue = inputField.value - if (readonlyField && inputFieldValue || fieldName == "full_name"){ + if (readonlyField && (inputFieldValue || fieldName == "full_name")){ if (fieldName == "full_name"){ let firstName = document.querySelector(`#id_first_name`).value; let middleName = document.querySelector(`#id_middle_name`).value; @@ -1855,6 +1855,8 @@ document.addEventListener('DOMContentLoaded', function() { }else { readonlyField.innerHTML = "Unknown"; } + + inputField.classList.add("text-base") } } } From 61023453d03ad3f3f65c194aa54cd313f9aa03fb Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Wed, 3 Jul 2024 15:39:53 -0400 Subject: [PATCH 008/184] restructured some tests --- src/registrar/tests/test_admin.py | 2567 +-------------------- src/registrar/tests/test_admin_domain.py | 815 +++++++ src/registrar/tests/test_admin_request.py | 1911 +++++++++++++++ 3 files changed, 2729 insertions(+), 2564 deletions(-) create mode 100644 src/registrar/tests/test_admin_domain.py create mode 100644 src/registrar/tests/test_admin_request.py diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 51832e4ed..9c59786d8 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -1,16 +1,11 @@ -from datetime import date, datetime +from datetime import datetime from django.utils import timezone -import re -from django.test import TestCase, RequestFactory, Client, override_settings +from django.test import TestCase, RequestFactory, Client from django.contrib.admin.sites import AdminSite -from contextlib import ExitStack 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, - DomainRequestAdmin, DomainRequestAdminForm, DomainInvitationAdmin, ListHeaderAdmin, @@ -48,7 +43,6 @@ from registrar.models.user_domain_role import UserDomainRole from registrar.models.verified_by_staff import VerifiedByStaff from .common import ( MockDb, - MockSESClient, AuditedAdminMockData, completed_domain_request, generic_domain_object, @@ -56,18 +50,13 @@ from .common import ( mock_user, create_superuser, create_user, - create_ready_domain, multiple_unalphabetical_domain_objects, - MockEppLib, GenericTestHelper, ) from django.contrib.sessions.backends.db import SessionStore from django.contrib.auth import get_user_model -from unittest.mock import ANY, call, patch, Mock -from unittest import skip +from unittest.mock import ANY, patch, Mock -from django.conf import settings -import boto3_mocking # type: ignore import logging logger = logging.getLogger(__name__) @@ -116,743 +105,6 @@ class TestFsmModelResource(TestCase): fsm_field_mock.save.assert_not_called() -class TestDomainAdmin(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.client = Client(HTTP_HOST="localhost:8080") - self.superuser = create_superuser() - self.staffuser = create_user() - self.factory = RequestFactory() - self.app.set_user(self.superuser.username) - self.client.force_login(self.superuser) - - # 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) - - # 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, - ) - super().setUp() - - @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 - p = "userpass" - self.client.login(username="staffuser", 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) - - # 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 - - p = "userpass" - self.client.login(username="staffuser", 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) - - # 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_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/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() - - 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) - - # 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 - """ - - # 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) - - # 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 - """ - - # 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), - ] - - p = "adminpass" - self.client.login(username="superuser", password=p) - 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) - - @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 - 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() - - p = "userpass" - self.client.login(username="staffuser", 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) - - # 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_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() - - 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) - - # Test for data. We only need to test one since its all interconnected. - expected_organization_name = "MonkeySeeMonkeyDo" - self.assertContains(response, expected_organization_name) - - @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) - - 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) - - 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}" - - # 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) - - def test_short_org_name_in_domains_list(self): - """ - Make sure the short name is displaying in admin on the list page - """ - with less_console_noise(): - self.client.force_login(self.superuser) - 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") - - @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 - p = "userpass" - self.client.login(username="staffuser", password=p) - 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") - - 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` - """ - with less_console_noise(): - 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") - - # 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) - - 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
') - - 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` - """ - with less_console_noise(): - 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) - - 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 - """ - with less_console_noise(): - 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) - - @skip("Waiting on epp lib to implement") - def test_place_and_remove_hold_epp(self): - raise - - @override_settings(IS_PRODUCTION=True) - def test_prod_only_shows_export(self): - """Test that production environment only displays export""" - with less_console_noise(): - response = self.client.get("/admin/registrar/domain/") - self.assertContains(response, ">Export<") - self.assertNotContains(response, ">Import<") - - def tearDown(self): - super().tearDown() - PublicContact.objects.all().delete() - Host.objects.all().delete() - Domain.objects.all().delete() - DomainInformation.objects.all().delete() - DomainRequest.objects.all().delete() - User.objects.all().delete() - - class TestDomainRequestAdminForm(TestCase): def setUp(self): # Create a test domain request with an initial state of started @@ -916,1819 +168,6 @@ class TestDomainRequestAdminForm(TestCase): ) -@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) - - 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_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") - - def test_domain_sortable(self): - """Tests if the DomainRequest sorts by domain correctly""" - with less_console_noise(): - 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",)) - - def test_submitter_sortable(self): - """Tests if the DomainRequest sorts by submitter correctly""" - with less_console_noise(): - 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", - ), - ) - - def test_investigator_sortable(self): - """Tests if the DomainRequest sorts by investigator correctly""" - with less_console_noise(): - 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) - - def test_short_org_name_in_domain_requests_list(self): - """ - Make sure the short name is displaying in admin on the list page - """ - with less_console_noise(): - 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") - - def test_default_status_in_domain_requests_list(self): - """ - Make sure the default status in admin is selected on the domain requests list page - """ - with less_console_noise(): - 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) - - 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.""" - - with less_console_noise(): - # 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) - - @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) - - 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.""" - - with less_console_noise(): - # 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 - 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") - - @override_settings(IS_PRODUCTION=True) - def test_save_model_sends_submitted_email_with_bcc_on_prod(self): - """When transitioning to submitted from started or withdrawn on a domain request, - an email is sent out. - - When transitioning to submitted from dns needed or in review on a domain request, - no email is sent out. - - Also test that the default email set in settings IS BCCd on prod whenever - an email does go out.""" - - with less_console_noise(): - # Ensure there is no user with this email - EMAIL = "mayor@igorville.gov" - User.objects.filter(email=EMAIL).delete() - - BCC_EMAIL = settings.DEFAULT_FROM_EMAIL - - # Create a sample 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) - - def test_save_model_sends_approved_email(self): - """When transitioning to approved on a domain request, - an email is sent out every time.""" - - with less_console_noise(): - # 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) - - 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.""" - - with less_console_noise(): - # 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) - - 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.""" - - with less_console_noise(): - # 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) - - 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.""" - - with less_console_noise(): - # 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) - - 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.""" - - with less_console_noise(): - # 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) - - 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.""" - - with less_console_noise(): - # 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) - - 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.""" - - with less_console_noise(): - # 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.""" - - with less_console_noise(): - # 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) - - 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. - """ - - with less_console_noise(): - 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) - - 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. - """ - - with less_console_noise(): - 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) - - def test_save_model_sends_withdrawn_email(self): - """When transitioning to withdrawn on a domain request, - an email is sent out every time.""" - - with less_console_noise(): - # 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) - - def test_save_model_sets_approved_domain(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)) - - 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_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) - - 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), + ) + From 876041fe40a5bce462e287ce0af1f75d3c90436b Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 3 Jul 2024 14:18:20 -0600 Subject: [PATCH 009/184] Update get-gov.js --- src/registrar/assets/js/get-gov.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js index 30b80d356..90fb5711e 100644 --- a/src/registrar/assets/js/get-gov.js +++ b/src/registrar/assets/js/get-gov.js @@ -1839,12 +1839,12 @@ document.addEventListener('DOMContentLoaded', function() { if (editableFormGroup){ let readonlyField = editableFormGroup.querySelector(".input-with-edit-button__readonly-field") let inputField = document.getElementById(`id_${fieldName}`); - if (!inputField) { + if (!inputField || !readonlyField) { return; } let inputFieldValue = inputField.value - if (readonlyField && (inputFieldValue || fieldName == "full_name")){ + if (inputFieldValue || fieldName == "full_name"){ if (fieldName == "full_name"){ let firstName = document.querySelector(`#id_first_name`).value; let middleName = document.querySelector(`#id_middle_name`).value; @@ -1857,6 +1857,8 @@ document.addEventListener('DOMContentLoaded', function() { } inputField.classList.add("text-base") + }else { + readonlyField.innerHTML = inputFieldValue } } } From 0edc8484735564e456f3008fb1e171e3ef36712e Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 3 Jul 2024 14:25:05 -0600 Subject: [PATCH 010/184] Cleanup --- src/registrar/assets/js/get-gov.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js index 90fb5711e..74f2715b2 100644 --- a/src/registrar/assets/js/get-gov.js +++ b/src/registrar/assets/js/get-gov.js @@ -1846,17 +1846,23 @@ document.addEventListener('DOMContentLoaded', function() { let inputFieldValue = inputField.value if (inputFieldValue || fieldName == "full_name"){ if (fieldName == "full_name"){ - let firstName = document.querySelector(`#id_first_name`).value; - let middleName = document.querySelector(`#id_middle_name`).value; - let lastName = document.querySelector(`#id_last_name`).value; - if (firstName && middleName && lastName) { + let firstName = document.querySelector(`#id_first_name`); + let middleName = document.querySelector(`#id_middle_name`); + let lastName = document.querySelector(`#id_last_name`); + if (firstName && lastName) { let values = [firstName.value, middleName.value, lastName.value] + console.log(values) readonlyField.innerHTML = values.join(" "); }else { readonlyField.innerHTML = "Unknown"; } - - inputField.classList.add("text-base") + + // Technically, the full_name field is optional, but we want to display it as required. + // This style is applied to readonly fields (gray text). This just removes it, as + // this is difficult to achieve otherwise by modifying the .readonly property. + if (readonlyField.classList.contains("text-base")) { + readonlyField.classList.remove("text-base") + } }else { readonlyField.innerHTML = inputFieldValue } From ac4b657dbbb9da30a21d5ef7fa47b1df88a7ada0 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 3 Jul 2024 14:53:01 -0600 Subject: [PATCH 011/184] Add unit test and lint --- src/registrar/forms/user_profile.py | 2 +- src/registrar/tests/test_views.py | 84 +++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/src/registrar/forms/user_profile.py b/src/registrar/forms/user_profile.py index 02bc4e58f..60e67886c 100644 --- a/src/registrar/forms/user_profile.py +++ b/src/registrar/forms/user_profile.py @@ -99,4 +99,4 @@ class FinishSetupProfileForm(UserProfileForm): self.fields["full_name"].initial = full_name # Set full_name as required for styling purposes - self.fields["full_name"].widget.attrs['required'] = 'required' + self.fields["full_name"].widget.attrs["required"] = "required" diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 61bc94a32..f8f4e1775 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -539,6 +539,49 @@ class FinishUserProfileTests(TestWithUser, WebTest): self._set_session_cookie() return page.follow() if follow else page + @less_console_noise_decorator + @override_flag("profile_feature", active=True) + def test_full_name_initial_value(self): + """Test that full_name initial value is empty when first_name or last_name is empty. + This will later be displayed as "unknown" using javascript.""" + self.app.set_user(self.incomplete_regular_user.username) + + # Test when first_name is empty + self.incomplete_regular_user.contact.first_name = "" + self.incomplete_regular_user.contact.last_name = "Doe" + self.incomplete_regular_user.contact.save() + + finish_setup_page = self.app.get(reverse("home")).follow() + form = finish_setup_page.form + self.assertEqual(form["full_name"].value, "") + + # Test when last_name is empty + self.incomplete_regular_user.contact.first_name = "John" + self.incomplete_regular_user.contact.last_name = "" + self.incomplete_regular_user.contact.save() + + finish_setup_page = self.app.get(reverse("home")).follow() + form = finish_setup_page.form + self.assertEqual(form["full_name"].value, "") + + # Test when both first_name and last_name are empty + self.incomplete_regular_user.contact.first_name = "" + self.incomplete_regular_user.contact.last_name = "" + self.incomplete_regular_user.contact.save() + + finish_setup_page = self.app.get(reverse("home")).follow() + form = finish_setup_page.form + self.assertEqual(form["full_name"].value, "") + + # Test when both first_name and last_name are present + self.incomplete_regular_user.contact.first_name = "John" + self.incomplete_regular_user.contact.last_name = "Doe" + self.incomplete_regular_user.contact.save() + + finish_setup_page = self.app.get(reverse("home")).follow() + form = finish_setup_page.form + self.assertEqual(form["full_name"].value, "John Doe") + @less_console_noise_decorator def test_new_user_with_profile_feature_on(self): """Tests that a new user is redirected to the profile setup page when profile_feature is on""" @@ -577,6 +620,47 @@ class FinishUserProfileTests(TestWithUser, WebTest): completed_setup_page = self.app.get(reverse("home")) self.assertContains(completed_setup_page, "Manage your domain") + @less_console_noise_decorator + def test_new_user_with_empty_name_profile_feature_on(self): + """Tests that a new user without a name can still enter this information accordingly""" + self.incomplete_regular_user.contact.first_name = None + self.incomplete_regular_user.contact.last_name = None + self.incomplete_regular_user.save() + self.app.set_user(self.incomplete_regular_user.username) + with override_flag("profile_feature", active=True): + # This will redirect the user to the setup page. + # Follow implicity checks if our redirect is working. + finish_setup_page = self.app.get(reverse("home")).follow() + self._set_session_cookie() + + # Assert that we're on the right page + self.assertContains(finish_setup_page, "Finish setting up your profile") + + finish_setup_page = self._submit_form_webtest(finish_setup_page.form) + + self.assertEqual(finish_setup_page.status_code, 200) + + # We're missing a phone number, so the page should tell us that + self.assertContains(finish_setup_page, "Enter your phone number.") + + # Check for the name of the save button + self.assertContains(finish_setup_page, "contact_setup_save_button") + + # Add a phone number + finish_setup_form = finish_setup_page.form + finish_setup_form["phone"] = "(201) 555-0123" + finish_setup_form["title"] = "CEO" + finish_setup_form["last_name"] = "example" + save_page = self._submit_form_webtest(finish_setup_form, follow=True) + + self.assertEqual(save_page.status_code, 200) + self.assertContains(save_page, "Your profile has been updated.") + + # Try to navigate back to the home page. + # This is the same as clicking the back button. + completed_setup_page = self.app.get(reverse("home")) + self.assertContains(completed_setup_page, "Manage your domain") + @less_console_noise_decorator def test_new_user_goes_to_domain_request_with_profile_feature_on(self): """Tests that a new user is redirected to the domain request page when profile_feature is on""" From a6f7cc6f020b4d4784a387a208e7ad925632efdc Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Thu, 4 Jul 2024 07:57:56 -0400 Subject: [PATCH 012/184] wip --- src/registrar/tests/test_admin_request.py | 76 +++++++++-------------- 1 file changed, 29 insertions(+), 47 deletions(-) diff --git a/src/registrar/tests/test_admin_request.py b/src/registrar/tests/test_admin_request.py index 5d42b95a6..969d98a65 100644 --- a/src/registrar/tests/test_admin_request.py +++ b/src/registrar/tests/test_admin_request.py @@ -39,41 +39,24 @@ import logging logger = logging.getLogger(__name__) + @boto3_mocking.patching -class TestDomainRequestAdmin(MockEppLib): +class TestDomainRequestAdminAsStaff(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() + 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): @@ -685,24 +668,24 @@ class TestDomainRequestAdmin(MockEppLib): 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() +@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): @@ -1532,7 +1515,6 @@ class TestDomainRequestAdmin(MockEppLib): 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.""" From 36985353c2769c755ea44d06ba30374e431a47e5 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Thu, 4 Jul 2024 11:00:03 -0400 Subject: [PATCH 013/184] mockdb tests now more efficient --- src/registrar/tests/common.py | 17 ++++++++++++----- src/registrar/tests/test_admin.py | 19 +++++++++++++++---- src/registrar/tests/test_reports.py | 20 +++++++++++++------- 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index 4d13f0bb9..85e34403a 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -525,10 +525,16 @@ class AuditedAdminMockData: class MockDb(TestCase): - """Hardcoded mocks make test case assertions straightforward.""" + """Hardcoded mocks make test case assertions straightforward. + setUpClass and tearDownClass are used so that multiple tests + can be executed using the same mock db data without having to + setUp and tearDown the data in between. + This strategy requires that any changes to data within a test + must be cleaned up within the test rather than relying on tearDown.""" - def setUp(self): - super().setUp() + @classmethod + def setUpClass(self): + super().setUpClass() username = "test_user" first_name = "First" last_name = "Last" @@ -782,8 +788,9 @@ class MockDb(TestCase): self.domain_request_6.submission_date = get_time_aware_date(datetime(2024, 4, 2)) self.domain_request_6.save() - def tearDown(self): - super().tearDown() + @classmethod + def tearDownClass(self): + super().tearDownClass() PublicContact.objects.all().delete() Domain.objects.all().delete() DomainInformation.objects.all().delete() diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 9c59786d8..13445fb15 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -898,8 +898,9 @@ class TestListHeaderAdmin(TestCase): class TestMyUserAdmin(MockDb): - def setUp(self): - super().setUp() + @classmethod + def setUpClass(self): + super().setUpClass() admin_site = AdminSite() self.admin = MyUserAdmin(model=get_user_model(), admin_site=admin_site) self.client = Client(HTTP_HOST="localhost:8080") @@ -907,8 +908,9 @@ class TestMyUserAdmin(MockDb): self.staffuser = create_user() self.test_helper = GenericTestHelper(admin=self.admin) - def tearDown(self): - super().tearDown() + @classmethod + def tearDownClass(self): + super().tearDownClass() DomainRequest.objects.all().delete() User.objects.all().delete() @@ -1094,6 +1096,15 @@ class TestMyUserAdmin(MockDb): expected_href = reverse("admin:registrar_domain_change", args=[domain_deleted.pk]) self.assertNotContains(response, expected_href) + # Must clean up within test since MockDB is shared across tests for performance reasons + domain_request_started.delete() + domain_request_submitted.delete() + domain_request_in_review.delete() + domain_request_withdrawn.delete() + domain_request_approved.delete() + domain_request_rejected.delete() + domain_request_ineligible.delete() + domain_deleted.delete() class AuditedAdminTest(TestCase): def setUp(self): diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py index 45600cb01..f2974a52e 100644 --- a/src/registrar/tests/test_reports.py +++ b/src/registrar/tests/test_reports.py @@ -29,10 +29,15 @@ from .common import MockDb, MockEppLib, less_console_noise, get_time_aware_date class CsvReportsTest(MockDb): - """Tests to determine if we are uploading our reports correctly""" + """Tests to determine if we are uploading our reports correctly. + These tests use MockDb, which runs setUpClass and tearDownClass to handle + creation of fake domain data. + setUp and tearDown in this class set up client and factory. + This is for efficiency purposes when running tests, but + also means that care must be taken to clean up within each test. + """ def setUp(self): - """Create fake domain data""" super().setUp() self.client = Client(HTTP_HOST="localhost:8080") self.factory = RequestFactory() @@ -196,11 +201,12 @@ class CsvReportsTest(MockDb): class ExportDataTest(MockDb, MockEppLib): - def setUp(self): - super().setUp() - - def tearDown(self): - super().tearDown() + """Test the ExportData class from csv_export. + These tests use MockDb, which runs setUpClass and tearDownClass. + setUp and tearDown in this test run from MockEppLib to set up EPP mocks. + This is for efficiency purposes when running tests, but + also means that care must be taken to clean up within each test. + """ def test_export_domains_to_writer_security_emails_and_first_ready(self): """Test that export_domains_to_writer returns the From 99f19a70cbfca81422b33df926f158b6d04d3096 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 5 Jul 2024 08:24:23 -0400 Subject: [PATCH 014/184] improvements to test_admin_domain --- src/registrar/tests/test_admin.py | 7 ++ src/registrar/tests/test_admin_domain.py | 91 +++++++++++++++++------- 2 files changed, 71 insertions(+), 27 deletions(-) diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 13445fb15..3fdf32954 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -898,6 +898,13 @@ class TestListHeaderAdmin(TestCase): class TestMyUserAdmin(MockDb): + """Test the MyUserAdmin class in Django Admin. + These tests use MockDb, which runs setUpClass and tearDownClass, rather than + setUp and tearDown. This is for efficiency purposes when running tests, but + also means that care must be taken to clean up within each test, because + setUp and tearDown are not used. + """ + @classmethod def setUpClass(self): super().setUpClass() diff --git a/src/registrar/tests/test_admin_domain.py b/src/registrar/tests/test_admin_domain.py index b5e3af01d..a25c92142 100644 --- a/src/registrar/tests/test_admin_domain.py +++ b/src/registrar/tests/test_admin_domain.py @@ -36,10 +36,17 @@ logger = logging.getLogger(__name__) class TestDomainAdminAsStaff(MockEppLib): - def setUp(self): + @classmethod + def setUpClass(self): + super().setUpClass() self.client = Client(HTTP_HOST="localhost:8080") self.staffuser = create_user() + + def setUp(self): self.client.force_login(self.staffuser) + self.site = AdminSite() + self.admin = DomainAdmin(model=Domain, admin_site=self.site) + self.factory = RequestFactory() super().setUp() def tearDown(self): @@ -48,7 +55,11 @@ class TestDomainAdminAsStaff(MockEppLib): Domain.objects.all().delete() DomainInformation.objects.all().delete() DomainRequest.objects.all().delete() + + @classmethod + def tearDownClass(self): User.objects.all().delete() + super().tearDownClass() @less_console_noise_decorator def test_staff_can_see_cisa_region_federal(self): @@ -149,6 +160,12 @@ class TestDomainAdminAsStaff(MockEppLib): expected_organization_name = "MonkeySeeMonkeyDo" self.assertContains(response, expected_organization_name) + # clean up this test's data + domain.delete() + domain_information.delete() + _domain_request.delete() + _creator.delete() + @skip("Why did this test stop working, and is is a good test") def test_place_and_remove_hold(self): domain = create_ready_domain() @@ -184,6 +201,9 @@ class TestDomainAdminAsStaff(MockEppLib): self.assertContains(response, "Place hold") self.assertNotContains(response, "Remove hold") + # clean up this test's data + domain.delete() + @less_console_noise_decorator def test_deletion_is_successful(self): """ @@ -192,9 +212,6 @@ class TestDomainAdminAsStaff(MockEppLib): 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() @@ -241,6 +258,8 @@ class TestDomainAdminAsStaff(MockEppLib): self.assertEqual(domain.state, Domain.State.DELETED) + # clean up data within this test + domain.delete() @less_console_noise_decorator def test_deletion_ready_fsm_failure(self): @@ -250,12 +269,8 @@ class TestDomainAdminAsStaff(MockEppLib): 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), @@ -285,6 +300,9 @@ class TestDomainAdminAsStaff(MockEppLib): self.assertEqual(domain.state, Domain.State.READY) + # delete data created in this test + domain.delete() + @less_console_noise_decorator def test_analyst_deletes_domain_idempotent(self): """ @@ -294,14 +312,9 @@ class TestDomainAdminAsStaff(MockEppLib): 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), @@ -348,13 +361,23 @@ class TestDomainAdminAsStaff(MockEppLib): ) self.assertEqual(domain.state, Domain.State.DELETED) + # delete data created in this test + domain.delete() + class TestDomainAdminWClient(TestCase): - def setUp(self): + @classmethod + def setUpClass(self): + super().setUpClass() self.client = Client(HTTP_HOST="localhost:8080") self.superuser = create_superuser() + + def setUp(self): self.client.force_login(self.superuser) + self.site = AdminSite() + self.admin = DomainAdmin(model=Domain, admin_site=self.site) + self.factory = RequestFactory() super().setUp() def tearDown(self): @@ -363,7 +386,11 @@ class TestDomainAdminWClient(TestCase): Domain.objects.all().delete() DomainInformation.objects.all().delete() DomainRequest.objects.all().delete() + + @classmethod + def tearDownClass(self): User.objects.all().delete() + super().tearDownClass() @less_console_noise_decorator def test_has_model_description(self): @@ -450,21 +477,21 @@ class TestDomainAdminWClient(TestCase): # Test for the copy link self.assertContains(response, "usa-button__clipboard") + # cleanup from this test + domain.delete() + _domain_info.delete() + domain_request.delete() + _creator.delete() + @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, @@ -566,6 +593,12 @@ class TestDomainAdminWClient(TestCase): expected_organization_name = "MonkeySeeMonkeyDo" self.assertContains(response, expected_organization_name) + # cleanup from this test + domain.delete() + domain_information.delete() + _domain_request.delete() + _creator.delete() + @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""" @@ -575,10 +608,6 @@ class TestDomainAdminWClient(TestCase): # 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, @@ -629,13 +658,17 @@ class TestDomainAdminWebTest(MockEppLib, WebTest): # We disable them here. TODO for another ticket. csrf_checks = False - def setUp(self): + @classmethod + def setUpClass(self): + super().setUpClass() 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) + + def setUp(self): super().setUp() + self.app.set_user(self.superuser.username) def tearDown(self): super().tearDown() @@ -643,7 +676,11 @@ class TestDomainAdminWebTest(MockEppLib, WebTest): Domain.objects.all().delete() DomainInformation.objects.all().delete() DomainRequest.objects.all().delete() + + @classmethod + def tearDownClass(self): User.objects.all().delete() + super().tearDownClass() @less_console_noise_decorator @patch("registrar.admin.DomainAdmin._get_current_date", return_value=date(2024, 1, 1)) From 8cea9a778604c01e61a272fe763313185dbed9ba Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 5 Jul 2024 08:53:38 -0400 Subject: [PATCH 015/184] improved performance of domain_admin_request --- src/registrar/tests/test_admin_request.py | 2412 ++++++++++----------- 1 file changed, 1204 insertions(+), 1208 deletions(-) diff --git a/src/registrar/tests/test_admin_request.py b/src/registrar/tests/test_admin_request.py index 969d98a65..e1aeda562 100644 --- a/src/registrar/tests/test_admin_request.py +++ b/src/registrar/tests/test_admin_request.py @@ -40,638 +40,12 @@ import logging logger = logging.getLogger(__name__) -@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() + + @classmethod + def setUpClass(self): + super().setUpClass() self.site = AdminSite() self.factory = RequestFactory() self.admin = DomainRequestAdmin(model=DomainRequest, admin_site=self.site) @@ -686,6 +60,9 @@ class TestDomainRequestAdmin(MockEppLib): model=DomainRequest, ) self.mock_client = MockSESClient() + # def setUp(self): + # super().setUp() + @less_console_noise_decorator def test_has_model_description(self): @@ -821,7 +198,6 @@ class TestDomainRequestAdmin(MockEppLib): 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 @@ -843,79 +219,220 @@ class TestDomainRequestAdmin(MockEppLib): self.test_helper.assertContains(response, "Show details") @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") + + # clean up objects in this test + fake_website.delete() + _domain_request.delete() + _creator.delete() + + @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") + + # clean up objects in this test + _domain_request.delete() + requested_domain.delete() + _creator.delete() + + @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") + + # clean up objects in this test + fake_website.delete() + _domain_request.delete() + _creator.delete() + def test_domain_sortable(self): """Tests if the DomainRequest sorts by domain correctly""" - p = "adminpass" - self.client.login(username="superuser", password=p) + with less_console_noise(): + p = "adminpass" + self.client.login(username="superuser", password=p) - multiple_unalphabetical_domain_objects("domain_request") + multiple_unalphabetical_domain_objects("domain_request") - # Assert that our sort works correctly - self.test_helper.assert_table_sorted("1", ("requested_domain__name",)) + # 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",)) + # 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) + with less_console_noise(): + p = "adminpass" + self.client.login(username="superuser", password=p) - multiple_unalphabetical_domain_objects("domain_request") + 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() + 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 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", - ), - ) + # Assert that sorting in reverse works correctly + self.test_helper.assert_table_sorted( + "-11", + ( + "-submitter__first_name", + "-submitter__last_name", + ), + ) + + # clean up objects in this test + new_user.delete() - @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) + with less_console_noise(): + 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() + 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 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", - ), - ) + # Assert that sorting in reverse works correctly + self.test_helper.assert_table_sorted( + "-12", + ( + "-investigator__first_name", + "-investigator__last_name", + ), + ) + + # clean up objects in this test + new_user.delete() @less_console_noise_decorator def test_default_sorting_in_domain_requests_list(self): @@ -981,32 +498,32 @@ class TestDomainRequestAdmin(MockEppLib): 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") + with less_console_noise(): + 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) + with less_console_noise(): + 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): @@ -1028,27 +545,27 @@ class TestDomainRequestAdmin(MockEppLib): # 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"] + with less_console_noise(): + # 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"] + # 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) + # 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: _ = "" @@ -1061,7 +578,6 @@ class TestDomainRequestAdmin(MockEppLib): 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 @@ -1113,7 +629,6 @@ class TestDomainRequestAdmin(MockEppLib): # 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. @@ -1124,52 +639,70 @@ class TestDomainRequestAdmin(MockEppLib): 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() + with less_console_noise(): + # 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() + # 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 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 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) + # 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) + # 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) + # 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 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) + # 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) + # 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_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") + @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, @@ -1181,287 +714,288 @@ class TestDomainRequestAdmin(MockEppLib): 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() + with less_console_noise(): + # Ensure there is no user with this email + EMAIL = "mayor@igorville.gov" + User.objects.filter(email=EMAIL).delete() - BCC_EMAIL = settings.DEFAULT_FROM_EMAIL + BCC_EMAIL = settings.DEFAULT_FROM_EMAIL - # Create a sample domain request - domain_request = completed_domain_request() + # 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 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 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) + # 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) + # 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) + # 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 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) + # 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) + # 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() + with less_console_noise(): + # 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) + # 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 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 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) + # 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() + with less_console_noise(): + # 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) + # 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) + # 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) + # 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() + with less_console_noise(): + # 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) + # 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) + # 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) + # 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() + with less_console_noise(): + # 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) + # 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) + # 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) + # 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() + with less_console_noise(): + # 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) + # 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) + # 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) + # 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() + with less_console_noise(): + # 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) + # 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) + # 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) + # 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() + with less_console_noise(): + # 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) + # 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) + # 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) + # 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() + with less_console_noise(): + # 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) + # 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) + # 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) + # 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. @@ -1469,27 +1003,27 @@ class TestDomainRequestAdmin(MockEppLib): The transition fails. """ - domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.APPROVED) + with less_console_noise(): + 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 + # 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 + 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) + self.admin.save_model(request, domain_request, None, True) - messages.error.assert_called_once_with( - request, - "A reason is required for this status.", - ) + 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) + 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. @@ -1497,72 +1031,74 @@ class TestDomainRequestAdmin(MockEppLib): The transition is successful. """ - domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.APPROVED) + with less_console_noise(): + 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 + # 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 + 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) + self.admin.save_model(request, domain_request, None, True) - messages.error.assert_not_called() + messages.error.assert_not_called() - domain_request.refresh_from_db() - self.assertEqual(domain_request.status, DomainRequest.DomainRequestStatus.REJECTED) + domain_request.refresh_from_db() + self.assertEqual(domain_request.status, DomainRequest.DomainRequestStatus.REJECTED) 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() + with less_console_noise(): + # 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) + # 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 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 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) + # 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() + 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 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)) + # 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 + 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) + # 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) + # 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): @@ -1594,239 +1130,486 @@ class TestDomainRequestAdmin(MockEppLib): self.assertNotContains(request, not_expected_content) @less_console_noise_decorator - def test_save_model_sets_restricted_status_on_user(self): + 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.factory.post( - "/admin/registrar/domainrequest/{}/change/".format(domain_request.pk), follow=True + 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) + + 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, ) - with boto3_mocking.clients.handler_for("sesv2", self.mock_client): - # Modify the domain request's property - domain_request.status = DomainRequest.DomainRequestStatus.INELIGIBLE + # 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) - # Use the model admin's save_model method - self.admin.save_model(request, domain_request, form=None, change=True) + # 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) - # Test that approved domain exists and equals requested domain - self.assertEqual(domain_request.creator.status, "restricted") + # 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_readonly_when_restricted_creator(self): + 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) - 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 + p = "userpass" + self.client.login(username="staffuser", password=p) + response = self.client.get( + "/admin/registrar/domainrequest/{}/change/".format(domain_request.pk), + follow=True, + ) - readonly_fields = self.admin.get_readonly_fields(request, domain_request) + # 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) - 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", + # 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.assertEqual(readonly_fields, expected_fields) + self.test_helper.assert_response_contains_distinct_values(response, expected_so_fields) + self.assertContains(response, "Chief Tester") - @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", + # == 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) - self.assertEqual(readonly_fields, expected_fields) + # Test for the copy link + self.assertContains(response, "usa-button__clipboard", count=4) - @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() + # Test that Creator counts display properly + self.assertNotContains(response, "Approved domains") + self.assertContains(response, "Active requests") - # Create a request object with a superuser - request = self.factory.get("/") - request.user = self.superuser + # cleanup objects from this test + domain_request.delete() + _creator.delete() - with patch("django.contrib.messages.error") as mock_error: - # Simulate saving the model - self.admin.save_model(request, domain_request, None, False) + 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() - # 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.", + # 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 ) - # Assert that the status has not changed - self.assertEqual(domain_request.status, DomainRequest.DomainRequestStatus.IN_REVIEW) + with boto3_mocking.clients.handler_for("sesv2", self.mock_client): + # Modify the domain request's property + domain_request.status = DomainRequest.DomainRequestStatus.INELIGIBLE - @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() + # Use the model admin's save_model method + self.admin.save_model(request, domain_request, form=None, change=True) - 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)) + # 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 - self.admin.display_restricted_warning(request, domain_request) + readonly_fields = self.admin.get_readonly_fields(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.", - ) + 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.", + ) - @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 + 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() - # Define a custom implementation for is_active - def custom_is_active(self): - return domain_is_active # Override to return True + # Create a request object with a superuser + request = self.factory.post("/admin/registrar/domainrequest/{}/change/".format(domain_request.pk)) + request.user = self.superuser - # 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")) + # Define a custom implementation for is_active + def custom_is_active(self): + return domain_is_active # Override to return True - domain_request.status = another_state + # 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")) - if another_state == DomainRequest.DomainRequestStatus.ACTION_NEEDED: - domain_request.action_needed_reason = domain_request.ActionNeededReasons.OTHER + domain_request.status = another_state - domain_request.rejection_reason = rejection_reason + if another_state == DomainRequest.DomainRequestStatus.ACTION_NEEDED: + domain_request.action_needed_reason = domain_request.ActionNeededReasons.OTHER - self.admin.save_model(request, domain_request, None, True) + domain_request.rejection_reason = rejection_reason - # 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.admin.save_model(request, domain_request, None, True) - self.assertEqual(domain_request.approved_domain, None) + # 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() - # Assert that Domain got Deleted - with self.assertRaises(Domain.DoesNotExist): - domain.refresh_from_db() + self.assertEqual(domain_request.approved_domain, None) - # Assert that DomainInformation got Deleted - with self.assertRaises(DomainInformation.DoesNotExist): - domain_information.refresh_from_db() + # 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, @@ -1834,11 +1617,9 @@ class TestDomainRequestAdmin(MockEppLib): 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. @@ -1846,23 +1627,23 @@ class TestDomainRequestAdmin(MockEppLib): 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 + 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, - ) + # 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) + 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 @@ -1873,21 +1654,236 @@ class TestDomainRequestAdmin(MockEppLib): 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") + 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 + 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 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) + # Get the returned queryset + queryset = self.admin.get_queryset(request) - # Check the order - self.assertEqual( - list(queryset), - list(expected_order), + # 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() + Contact.objects.all().delete() + Website.objects.all().delete() + self.mock_client.EMAILS_SENT.clear() + + @classmethod + def tearDownClass(self): + super().tearDownClass() + User.objects.all().delete() + + From 5b4711e6f37db7eef4bb06241b1c0044d431ba2f Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 5 Jul 2024 09:54:23 -0400 Subject: [PATCH 016/184] wip --- src/registrar/tests/test_admin_request.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/registrar/tests/test_admin_request.py b/src/registrar/tests/test_admin_request.py index e1aeda562..85fcfd60d 100644 --- a/src/registrar/tests/test_admin_request.py +++ b/src/registrar/tests/test_admin_request.py @@ -60,9 +60,6 @@ class TestDomainRequestAdmin(MockEppLib): model=DomainRequest, ) self.mock_client = MockSESClient() - # def setUp(self): - # super().setUp() - @less_console_noise_decorator def test_has_model_description(self): @@ -198,6 +195,7 @@ class TestDomainRequestAdmin(MockEppLib): 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 From e338a732492a61bf722bb76f2c76c551b546c5da Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 5 Jul 2024 12:13:42 -0400 Subject: [PATCH 017/184] wip --- src/registrar/tests/common.py | 17 +- src/registrar/tests/test_admin.py | 16 +- src/registrar/tests/test_admin_request.py | 1002 +++++++++++---------- src/registrar/tests/test_reports.py | 10 +- 4 files changed, 511 insertions(+), 534 deletions(-) diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index 85e34403a..e09b395a9 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -525,16 +525,8 @@ class AuditedAdminMockData: class MockDb(TestCase): - """Hardcoded mocks make test case assertions straightforward. - setUpClass and tearDownClass are used so that multiple tests - can be executed using the same mock db data without having to - setUp and tearDown the data in between. - This strategy requires that any changes to data within a test - must be cleaned up within the test rather than relying on tearDown.""" - - @classmethod - def setUpClass(self): - super().setUpClass() + def setUp(self): + super().setUp() username = "test_user" first_name = "First" last_name = "Last" @@ -788,9 +780,8 @@ class MockDb(TestCase): self.domain_request_6.submission_date = get_time_aware_date(datetime(2024, 4, 2)) self.domain_request_6.save() - @classmethod - def tearDownClass(self): - super().tearDownClass() + def tearDown(self): + super().tearDown() PublicContact.objects.all().delete() Domain.objects.all().delete() DomainInformation.objects.all().delete() diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 3fdf32954..eb49cf46a 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -898,16 +898,9 @@ class TestListHeaderAdmin(TestCase): class TestMyUserAdmin(MockDb): - """Test the MyUserAdmin class in Django Admin. - These tests use MockDb, which runs setUpClass and tearDownClass, rather than - setUp and tearDown. This is for efficiency purposes when running tests, but - also means that care must be taken to clean up within each test, because - setUp and tearDown are not used. - """ - @classmethod - def setUpClass(self): - super().setUpClass() + def setUp(self): + super().setUp() admin_site = AdminSite() self.admin = MyUserAdmin(model=get_user_model(), admin_site=admin_site) self.client = Client(HTTP_HOST="localhost:8080") @@ -915,9 +908,8 @@ class TestMyUserAdmin(MockDb): self.staffuser = create_user() self.test_helper = GenericTestHelper(admin=self.admin) - @classmethod - def tearDownClass(self): - super().tearDownClass() + def tearDown(self): + super().tearDown() DomainRequest.objects.all().delete() User.objects.all().delete() diff --git a/src/registrar/tests/test_admin_request.py b/src/registrar/tests/test_admin_request.py index 85fcfd60d..d20ea41f1 100644 --- a/src/registrar/tests/test_admin_request.py +++ b/src/registrar/tests/test_admin_request.py @@ -351,86 +351,86 @@ class TestDomainRequestAdmin(MockEppLib): _domain_request.delete() _creator.delete() + @less_console_noise_decorator def test_domain_sortable(self): """Tests if the DomainRequest sorts by domain correctly""" - with less_console_noise(): - p = "adminpass" - self.client.login(username="superuser", password=p) + p = "adminpass" + self.client.login(username="superuser", password=p) - multiple_unalphabetical_domain_objects("domain_request") + multiple_unalphabetical_domain_objects("domain_request") - # Assert that our sort works correctly - self.test_helper.assert_table_sorted("1", ("requested_domain__name",)) + # 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",)) + # 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""" - with less_console_noise(): - p = "adminpass" - self.client.login(username="superuser", password=p) + p = "adminpass" + self.client.login(username="superuser", password=p) - multiple_unalphabetical_domain_objects("domain_request") + 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() + 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 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", - ), - ) + # Assert that sorting in reverse works correctly + self.test_helper.assert_table_sorted( + "-11", + ( + "-submitter__first_name", + "-submitter__last_name", + ), + ) - # clean up objects in this test - new_user.delete() + # clean up objects in this test + new_user.delete() + @less_console_noise_decorator def test_investigator_sortable(self): """Tests if the DomainRequest sorts by investigator correctly""" - with less_console_noise(): - p = "adminpass" - self.client.login(username="superuser", password=p) + 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() + 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 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", - ), - ) + # Assert that sorting in reverse works correctly + self.test_helper.assert_table_sorted( + "-12", + ( + "-investigator__first_name", + "-investigator__last_name", + ), + ) - # clean up objects in this test - new_user.delete() + # clean up objects in this test + new_user.delete() @less_console_noise_decorator def test_default_sorting_in_domain_requests_list(self): @@ -496,32 +496,32 @@ class TestDomainRequestAdmin(MockEppLib): 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 """ - with less_console_noise(): - 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") + 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 """ - with less_console_noise(): - 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) + 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): @@ -577,6 +577,7 @@ class TestDomainRequestAdmin(MockEppLib): self.assertEqual(bcc_email, bcc_email_address) @override_settings(IS_PRODUCTION=True) + @less_console_noise_decorator 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""" @@ -627,6 +628,7 @@ class TestDomainRequestAdmin(MockEppLib): # 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. @@ -637,51 +639,50 @@ class TestDomainRequestAdmin(MockEppLib): Also test that the default email set in settings is NOT BCCd on non-prod whenever an email does go out.""" - with less_console_noise(): - # Ensure there is no user with this email - EMAIL = "mayor@igorville.gov" - User.objects.filter(email=EMAIL).delete() + # 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() + # 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 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 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) + # 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) + # 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) + # 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 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) + # 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) + # 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_model_displays_action_needed_email(self): @@ -702,6 +703,7 @@ class TestDomainRequestAdmin(MockEppLib): self.assertContains(response, "DOMAIN NAME DOES NOT MEET .GOV REQUIREMENTS") @override_settings(IS_PRODUCTION=True) + @less_console_noise_decorator 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. @@ -712,288 +714,288 @@ class TestDomainRequestAdmin(MockEppLib): Also test that the default email set in settings IS BCCd on prod whenever an email does go out.""" - with less_console_noise(): - # Ensure there is no user with this email - EMAIL = "mayor@igorville.gov" - User.objects.filter(email=EMAIL).delete() + # Ensure there is no user with this email + EMAIL = "mayor@igorville.gov" + User.objects.filter(email=EMAIL).delete() - BCC_EMAIL = settings.DEFAULT_FROM_EMAIL + BCC_EMAIL = settings.DEFAULT_FROM_EMAIL - # Create a sample domain request - domain_request = completed_domain_request() + # 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 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 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) + # 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) + # 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) + # 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 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) + # 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) + # 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.""" - with less_console_noise(): - # Ensure there is no user with this email - EMAIL = "mayor@igorville.gov" - User.objects.filter(email=EMAIL).delete() + # 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) + # 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 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 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) + # 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.""" - with less_console_noise(): - # Ensure there is no user with this email - EMAIL = "mayor@igorville.gov" - User.objects.filter(email=EMAIL).delete() + # 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) + # 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) + # 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) + # 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.""" - with less_console_noise(): - # Ensure there is no user with this email - EMAIL = "mayor@igorville.gov" - User.objects.filter(email=EMAIL).delete() + # 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) + # 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) + # 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) + # 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.""" - with less_console_noise(): - # Ensure there is no user with this email - EMAIL = "mayor@igorville.gov" - User.objects.filter(email=EMAIL).delete() + # 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) + # 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) + # 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) + # 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.""" - with less_console_noise(): - # Ensure there is no user with this email - EMAIL = "mayor@igorville.gov" - User.objects.filter(email=EMAIL).delete() + # 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) + # 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) + # 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) + # 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.""" - with less_console_noise(): - # Ensure there is no user with this email - EMAIL = "mayor@igorville.gov" - User.objects.filter(email=EMAIL).delete() + # 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) + # 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) + # 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) + # 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.""" - with less_console_noise(): - # Ensure there is no user with this email - EMAIL = "mayor@igorville.gov" - User.objects.filter(email=EMAIL).delete() + # 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) + # 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) + # 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) + # 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_other(self): """When transitioning to rejected on a domain request, an email is sent explaining why when the reason is other.""" - with less_console_noise(): - # Ensure there is no user with this email - EMAIL = "mayor@igorville.gov" - User.objects.filter(email=EMAIL).delete() + # 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) + # 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) + # 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) + # 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. @@ -1001,27 +1003,27 @@ class TestDomainRequestAdmin(MockEppLib): The transition fails. """ - with less_console_noise(): - domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.APPROVED) + 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 + # 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 + 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) + self.admin.save_model(request, domain_request, None, True) - messages.error.assert_called_once_with( - request, - "A reason is required for this status.", - ) + 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) + 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. @@ -1029,74 +1031,73 @@ class TestDomainRequestAdmin(MockEppLib): The transition is successful. """ - with less_console_noise(): - domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.APPROVED) + 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 + # 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 + 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) + self.admin.save_model(request, domain_request, None, True) - messages.error.assert_not_called() + messages.error.assert_not_called() - domain_request.refresh_from_db() - self.assertEqual(domain_request.status, DomainRequest.DomainRequestStatus.REJECTED) + 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.""" - with less_console_noise(): - # Ensure there is no user with this email - EMAIL = "mayor@igorville.gov" - User.objects.filter(email=EMAIL).delete() + # 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) + # 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 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 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) + # 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): - with less_console_noise(): - # make sure there is no user with this email - EMAIL = "mayor@igorville.gov" - User.objects.filter(email=EMAIL).delete() + # 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 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)) + # 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 + 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) + # 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) + # 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): @@ -1154,6 +1155,7 @@ class TestDomainRequestAdmin(MockEppLib): 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""" @@ -1292,152 +1294,152 @@ class TestDomainRequestAdmin(MockEppLib): domain_request.delete() _creator.delete() + @less_console_noise_decorator 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() + # 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 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 - ) + # 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 + 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) + # 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") + # Test that approved domain exists and equals requested domain + self.assertEqual(domain_request.creator.status, "restricted") + @less_console_noise_decorator 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() + # 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 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, - ) + 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) + 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 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 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") + # 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 + # 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) + # 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") + # 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, - ) + # '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) + 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") + # 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_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() + 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 + request = self.factory.get("/") + request.user = self.superuser - readonly_fields = self.admin.get_readonly_fields(request, domain_request) + 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", - ] + 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) + self.assertEqual(readonly_fields, expected_fields) def test_readonly_fields_for_analyst(self): with less_console_noise(): diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py index f2974a52e..dbf109957 100644 --- a/src/registrar/tests/test_reports.py +++ b/src/registrar/tests/test_reports.py @@ -30,14 +30,10 @@ from .common import MockDb, MockEppLib, less_console_noise, get_time_aware_date class CsvReportsTest(MockDb): """Tests to determine if we are uploading our reports correctly. - These tests use MockDb, which runs setUpClass and tearDownClass to handle - creation of fake domain data. - setUp and tearDown in this class set up client and factory. - This is for efficiency purposes when running tests, but - also means that care must be taken to clean up within each test. """ def setUp(self): + """setup fake comain data""" super().setUp() self.client = Client(HTTP_HOST="localhost:8080") self.factory = RequestFactory() @@ -202,10 +198,6 @@ class CsvReportsTest(MockDb): class ExportDataTest(MockDb, MockEppLib): """Test the ExportData class from csv_export. - These tests use MockDb, which runs setUpClass and tearDownClass. - setUp and tearDown in this test run from MockEppLib to set up EPP mocks. - This is for efficiency purposes when running tests, but - also means that care must be taken to clean up within each test. """ def test_export_domains_to_writer_security_emails_and_first_ready(self): From 0cfa59fa6b271a4f2ce1b83d6bb5aac3517292ab Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 5 Jul 2024 12:46:10 -0400 Subject: [PATCH 018/184] updated test_admin --- src/registrar/tests/common.py | 2 ++ src/registrar/tests/test_admin.py | 32 +++++++++++++++---------- src/registrar/tests/test_admin_views.py | 2 ++ 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index e09b395a9..09247eb0b 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -41,6 +41,7 @@ from registrar.models.user_domain_role import UserDomainRole from registrar.models.utility.contact_error import ContactError, ContactErrorCodes +from api.tests.common import less_console_noise_decorator logger = logging.getLogger(__name__) @@ -525,6 +526,7 @@ class AuditedAdminMockData: class MockDb(TestCase): + @less_console_noise_decorator def setUp(self): super().setUp() username = "test_user" diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index eb49cf46a..ef270e887 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -66,6 +66,7 @@ class TestFsmModelResource(TestCase): def setUp(self): self.resource = FsmModelResource() + @less_console_noise_decorator def test_init_instance(self): """Test initializing an instance of a class with a FSM field""" @@ -80,6 +81,7 @@ class TestFsmModelResource(TestCase): self.assertIsInstance(instance, Domain) self.assertEqual(instance.state, "ready") + @less_console_noise_decorator def test_import_field(self): """Test that importing a field does not import FSM field""" @@ -106,6 +108,7 @@ class TestFsmModelResource(TestCase): class TestDomainRequestAdminForm(TestCase): + @less_console_noise_decorator def setUp(self): # Create a test domain request with an initial state of started self.domain_request = completed_domain_request() @@ -171,6 +174,7 @@ class TestDomainRequestAdminForm(TestCase): class TestDomainInvitationAdmin(TestCase): """Tests for the DomainInvitation page""" + @less_console_noise_decorator def setUp(self): """Create a client object""" self.client = Client(HTTP_HOST="localhost:8080") @@ -296,6 +300,7 @@ class TestHostAdmin(TestCase): class TestDomainInformationAdmin(TestCase): + @less_console_noise_decorator def setUp(self): """Setup environment for a mock admin user""" self.site = AdminSite() @@ -958,21 +963,20 @@ class TestMyUserAdmin(MockDb): @less_console_noise_decorator def test_list_display_without_username(self): - with less_console_noise(): - request = self.client.request().wsgi_request - request.user = self.staffuser + request = self.client.request().wsgi_request + request.user = self.staffuser - list_display = self.admin.get_list_display(request) - expected_list_display = [ - "email", - "first_name", - "last_name", - "group", - "status", - ] + list_display = self.admin.get_list_display(request) + expected_list_display = [ + "email", + "first_name", + "last_name", + "group", + "status", + ] - self.assertEqual(list_display, expected_list_display) - self.assertNotIn("username", list_display) + self.assertEqual(list_display, expected_list_display) + self.assertNotIn("username", list_display) def test_get_fieldsets_superuser(self): with less_console_noise(): @@ -1004,6 +1008,7 @@ class TestMyUserAdmin(MockDb): ) self.assertEqual(fieldsets, expected_fieldsets) + @less_console_noise_decorator def test_analyst_can_see_related_domains_and_requests_in_user_form(self): """Tests if an analyst can see the related domains and domain requests for a user in that user's form""" @@ -1123,6 +1128,7 @@ class AuditedAdminTest(TestCase): return ordered_list + @less_console_noise_decorator def test_alphabetically_sorted_domain_request_investigator(self): """Tests if the investigator field is alphabetically sorted by mimicking the call event flow""" diff --git a/src/registrar/tests/test_admin_views.py b/src/registrar/tests/test_admin_views.py index cc4b3f1c7..e88c070df 100644 --- a/src/registrar/tests/test_admin_views.py +++ b/src/registrar/tests/test_admin_views.py @@ -1,6 +1,7 @@ from django.test import TestCase, Client from django.urls import reverse from registrar.tests.common import create_superuser +from api.tests.common import less_console_noise_decorator class TestAdminViews(TestCase): @@ -8,6 +9,7 @@ class TestAdminViews(TestCase): self.client = Client(HTTP_HOST="localhost:8080") self.superuser = create_superuser() + @less_console_noise_decorator def test_export_data_view(self): self.client.force_login(self.superuser) From 0d3def12c911f7fec7ba24edb12e70da38504120 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 5 Jul 2024 13:29:01 -0400 Subject: [PATCH 019/184] cleanup emails mgmt scripts and models --- src/registrar/tests/test_emails.py | 47 +++-- .../tests/test_management_scripts.py | 12 ++ src/registrar/tests/test_models.py | 199 +++++++++++++----- 3 files changed, 184 insertions(+), 74 deletions(-) diff --git a/src/registrar/tests/test_emails.py b/src/registrar/tests/test_emails.py index b01272e64..1a1556720 100644 --- a/src/registrar/tests/test_emails.py +++ b/src/registrar/tests/test_emails.py @@ -8,6 +8,7 @@ from registrar.utility import email from registrar.utility.email import send_templated_email from .common import completed_domain_request, less_console_noise +from api.tests.common import less_console_noise_decorator from datetime import datetime import boto3_mocking # type: ignore @@ -19,6 +20,7 @@ class TestEmails(TestCase): @boto3_mocking.patching @override_flag("disable_email_sending", active=True) + @less_console_noise_decorator def test_disable_email_flag(self): """Test if the 'disable_email_sending' stops emails from being sent""" with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): @@ -36,13 +38,13 @@ class TestEmails(TestCase): self.assertFalse(self.mock_client.send_email.called) @boto3_mocking.patching + @less_console_noise_decorator def test_submission_confirmation(self): """Submission confirmation email works.""" domain_request = completed_domain_request() with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): - with less_console_noise(): - domain_request.submit() + domain_request.submit() # check that an email was sent self.assertTrue(self.mock_client.send_email.called) @@ -74,12 +76,12 @@ class TestEmails(TestCase): self.assertIn("Anything else", body) @boto3_mocking.patching + @less_console_noise_decorator def test_submission_confirmation_no_current_website_spacing(self): """Test line spacing without current_website.""" domain_request = completed_domain_request(has_current_website=False) with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): - with less_console_noise(): - domain_request.submit() + domain_request.submit() _, kwargs = self.mock_client.send_email.call_args body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] self.assertNotIn("Current websites:", body) @@ -87,12 +89,12 @@ class TestEmails(TestCase): self.assertRegex(body, r"5555\n\n.gov domain:") @boto3_mocking.patching + @less_console_noise_decorator def test_submission_confirmation_current_website_spacing(self): """Test line spacing with current_website.""" domain_request = completed_domain_request(has_current_website=True) with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): - with less_console_noise(): - domain_request.submit() + domain_request.submit() _, kwargs = self.mock_client.send_email.call_args body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] self.assertIn("Current websites:", body) @@ -101,12 +103,12 @@ class TestEmails(TestCase): self.assertRegex(body, r"city.com\n\n.gov domain:") @boto3_mocking.patching + @less_console_noise_decorator def test_submission_confirmation_other_contacts_spacing(self): """Test line spacing with other contacts.""" domain_request = completed_domain_request(has_other_contacts=True) with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): - with less_console_noise(): - domain_request.submit() + domain_request.submit() _, kwargs = self.mock_client.send_email.call_args body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] self.assertIn("Other employees from your organization:", body) @@ -115,12 +117,12 @@ class TestEmails(TestCase): self.assertRegex(body, r"5557\n\nAnything else") @boto3_mocking.patching + @less_console_noise_decorator def test_submission_confirmation_no_other_contacts_spacing(self): """Test line spacing without other contacts.""" domain_request = completed_domain_request(has_other_contacts=False) with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): - with less_console_noise(): - domain_request.submit() + domain_request.submit() _, kwargs = self.mock_client.send_email.call_args body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] # spacing should be right between adjacent elements @@ -128,12 +130,12 @@ class TestEmails(TestCase): self.assertRegex(body, r"None\n\nAnything else") @boto3_mocking.patching + @less_console_noise_decorator def test_submission_confirmation_alternative_govdomain_spacing(self): """Test line spacing with alternative .gov domain.""" domain_request = completed_domain_request(has_alternative_gov_domain=True) with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): - with less_console_noise(): - domain_request.submit() + domain_request.submit() _, kwargs = self.mock_client.send_email.call_args body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] self.assertIn("city1.gov", body) @@ -141,12 +143,12 @@ class TestEmails(TestCase): self.assertRegex(body, r"city.gov\n\nAlternative domains:\ncity1.gov\n\nPurpose of your domain:") @boto3_mocking.patching + @less_console_noise_decorator def test_submission_confirmation_no_alternative_govdomain_spacing(self): """Test line spacing without alternative .gov domain.""" domain_request = completed_domain_request(has_alternative_gov_domain=False) with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): - with less_console_noise(): - domain_request.submit() + domain_request.submit() _, kwargs = self.mock_client.send_email.call_args body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] self.assertNotIn("city1.gov", body) @@ -154,12 +156,12 @@ class TestEmails(TestCase): self.assertRegex(body, r"city.gov\n\nPurpose of your domain:") @boto3_mocking.patching + @less_console_noise_decorator def test_submission_confirmation_about_your_organization_spacing(self): """Test line spacing with about your organization.""" domain_request = completed_domain_request(has_about_your_organization=True) with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): - with less_console_noise(): - domain_request.submit() + domain_request.submit() _, kwargs = self.mock_client.send_email.call_args body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] self.assertIn("About your organization:", body) @@ -167,12 +169,12 @@ class TestEmails(TestCase): self.assertRegex(body, r"10002\n\nAbout your organization:") @boto3_mocking.patching + @less_console_noise_decorator def test_submission_confirmation_no_about_your_organization_spacing(self): """Test line spacing without about your organization.""" domain_request = completed_domain_request(has_about_your_organization=False) with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): - with less_console_noise(): - domain_request.submit() + domain_request.submit() _, kwargs = self.mock_client.send_email.call_args body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] self.assertNotIn("About your organization:", body) @@ -180,24 +182,24 @@ class TestEmails(TestCase): self.assertRegex(body, r"10002\n\nSenior official:") @boto3_mocking.patching + @less_console_noise_decorator def test_submission_confirmation_anything_else_spacing(self): """Test line spacing with anything else.""" domain_request = completed_domain_request(has_anything_else=True) with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): - with less_console_noise(): - domain_request.submit() + domain_request.submit() _, kwargs = self.mock_client.send_email.call_args body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] # spacing should be right between adjacent elements self.assertRegex(body, r"5557\n\nAnything else?") @boto3_mocking.patching + @less_console_noise_decorator def test_submission_confirmation_no_anything_else_spacing(self): """Test line spacing without anything else.""" domain_request = completed_domain_request(has_anything_else=False) with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): - with less_console_noise(): - domain_request.submit() + domain_request.submit() _, kwargs = self.mock_client.send_email.call_args body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] self.assertNotIn("Anything else", body) @@ -205,6 +207,7 @@ class TestEmails(TestCase): self.assertRegex(body, r"5557\n\n----") @boto3_mocking.patching + @less_console_noise_decorator def test_send_email_with_attachment(self): with boto3_mocking.clients.handler_for("ses", self.mock_client_class): sender_email = "sender@example.com" diff --git a/src/registrar/tests/test_management_scripts.py b/src/registrar/tests/test_management_scripts.py index cebee9994..38dd664be 100644 --- a/src/registrar/tests/test_management_scripts.py +++ b/src/registrar/tests/test_management_scripts.py @@ -36,6 +36,7 @@ logger = logging.getLogger(__name__) class TestPopulateVerificationType(MockEppLib): """Tests for the populate_organization_type script""" + @less_console_noise_decorator def setUp(self): """Creates a fake domain object""" super().setUp() @@ -133,6 +134,7 @@ class TestPopulateVerificationType(MockEppLib): class TestPopulateOrganizationType(MockEppLib): """Tests for the populate_organization_type script""" + @less_console_noise_decorator def setUp(self): """Creates a fake domain object""" super().setUp() @@ -205,6 +207,7 @@ class TestPopulateOrganizationType(MockEppLib): ): call_command("populate_organization_type", "registrar/tests/data/fake_election_domains.csv") + @less_console_noise_decorator def assert_expected_org_values_on_request_and_info( self, domain_request: DomainRequest, @@ -247,6 +250,7 @@ class TestPopulateOrganizationType(MockEppLib): """Does nothing for mocking purposes""" pass + @less_console_noise_decorator def test_request_and_info_city_not_in_csv(self): """ Tests what happens to a city domain that is not defined in the CSV. @@ -282,6 +286,7 @@ class TestPopulateOrganizationType(MockEppLib): # All values should be the same self.assert_expected_org_values_on_request_and_info(city_request, city_info, expected_values) + @less_console_noise_decorator def test_request_and_info_federal(self): """ Tests what happens to a federal domain after the script is run (should be unchanged). @@ -316,6 +321,7 @@ class TestPopulateOrganizationType(MockEppLib): # All values should be the same self.assert_expected_org_values_on_request_and_info(federal_request, federal_info, expected_values) + @less_console_noise_decorator def test_request_and_info_tribal_add_election_office(self): """ Tests if a tribal domain in the election csv changes organization_type to TRIBAL - ELECTION @@ -356,6 +362,7 @@ class TestPopulateOrganizationType(MockEppLib): self.assert_expected_org_values_on_request_and_info(tribal_request, tribal_info, expected_values) + @less_console_noise_decorator def test_request_and_info_tribal_doesnt_remove_election_office(self): """ Tests if a tribal domain in the election csv changes organization_type to TRIBAL_ELECTION @@ -409,6 +416,7 @@ class TestPopulateOrganizationType(MockEppLib): class TestPopulateFirstReady(TestCase): """Tests for the populate_first_ready script""" + @less_console_noise_decorator def setUp(self): """Creates a fake domain object""" super().setUp() @@ -537,6 +545,7 @@ class TestPopulateFirstReady(TestCase): class TestPatchAgencyInfo(TestCase): + @less_console_noise_decorator def setUp(self): self.user, _ = User.objects.get_or_create(username="testuser") self.domain, _ = Domain.objects.get_or_create(name="testdomain.gov") @@ -560,6 +569,7 @@ class TestPatchAgencyInfo(TestCase): class TestExtendExpirationDates(MockEppLib): + @less_console_noise_decorator def setUp(self): """Defines the file name of migration_json and the folder its contained in""" super().setUp() @@ -882,6 +892,7 @@ class TestExportTables(MockEppLib): def tearDown(self): self.logger_patcher.stop() + @less_console_noise_decorator @patch("os.makedirs") @patch("os.path.exists") @patch("os.remove") @@ -1118,6 +1129,7 @@ class TestImportTables(TestCase): class TestTransferFederalAgencyType(TestCase): """Tests for the transfer_federal_agency_type script""" + @less_console_noise_decorator def setUp(self): """Creates a fake domain object""" super().setUp() diff --git a/src/registrar/tests/test_models.py b/src/registrar/tests/test_models.py index d652bef14..6c08694d0 100644 --- a/src/registrar/tests/test_models.py +++ b/src/registrar/tests/test_models.py @@ -27,12 +27,14 @@ from .common import MockSESClient, less_console_noise, completed_domain_request, from django_fsm import TransitionNotAllowed from waffle.testutils import override_flag +from api.tests.common import less_console_noise_decorator # Test comment for push -- will remove # The DomainRequest submit method has a side effect of sending an email # with AWS SES, so mock that out in all of these test cases @boto3_mocking.patching class TestDomainRequest(TestCase): + @less_console_noise_decorator def setUp(self): self.dummy_user, _ = Contact.objects.get_or_create( @@ -97,6 +99,7 @@ class TestDomainRequest(TestCase): with less_console_noise(): return self.assertRaises(Exception, None, exception_type) + @less_console_noise_decorator def test_federal_agency_set_to_non_federal_on_approve(self): """Ensures that when the federal_agency field is 'none' when .approve() is called, the field is set to the 'Non-Federal Agency' record""" @@ -240,12 +243,14 @@ class TestDomainRequest(TestCase): self.assertIn(expected_content, email_content) @override_flag("profile_feature", active=False) + @less_console_noise_decorator def test_submit_from_started_sends_email(self): msg = "Create a domain request and submit it and see if email was sent." domain_request = completed_domain_request(submitter=self.dummy_user, user=self.dummy_user_2) self.check_email_sent(domain_request, msg, "submit", 1, expected_content="Hello") @override_flag("profile_feature", active=True) + @less_console_noise_decorator def test_submit_from_started_sends_email_to_creator(self): """Tests if, when the profile feature flag is on, we send an email to the creator""" msg = "Create a domain request and submit it and see if email was sent when the feature flag is on." @@ -254,6 +259,7 @@ class TestDomainRequest(TestCase): domain_request, msg, "submit", 1, expected_content="Lava", expected_email="intern@igorville.com" ) + @less_console_noise_decorator def test_submit_from_withdrawn_sends_email(self): msg = "Create a withdrawn domain request and submit it and see if email was sent." domain_request = completed_domain_request( @@ -261,16 +267,19 @@ class TestDomainRequest(TestCase): ) self.check_email_sent(domain_request, msg, "submit", 1, expected_content="Hello") + @less_console_noise_decorator def test_submit_from_action_needed_does_not_send_email(self): msg = "Create a domain request with ACTION_NEEDED status and submit it, check if email was not sent." domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.ACTION_NEEDED) self.check_email_sent(domain_request, msg, "submit", 0) + @less_console_noise_decorator def test_submit_from_in_review_does_not_send_email(self): msg = "Create a withdrawn domain request and submit it and see if email was sent." domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW) self.check_email_sent(domain_request, msg, "submit", 0) + @less_console_noise_decorator def test_approve_sends_email(self): msg = "Create a domain request and approve it and see if email was sent." domain_request = completed_domain_request( @@ -278,6 +287,7 @@ class TestDomainRequest(TestCase): ) self.check_email_sent(domain_request, msg, "approve", 1, expected_content="Hello") + @less_console_noise_decorator def test_withdraw_sends_email(self): msg = "Create a domain request and withdraw it and see if email was sent." domain_request = completed_domain_request( @@ -285,6 +295,7 @@ class TestDomainRequest(TestCase): ) self.check_email_sent(domain_request, msg, "withdraw", 1, expected_content="Hello") + @less_console_noise_decorator def test_reject_sends_email(self): msg = "Create a domain request and reject it and see if email was sent." domain_request = completed_domain_request( @@ -292,11 +303,13 @@ class TestDomainRequest(TestCase): ) self.check_email_sent(domain_request, msg, "reject", 1, expected_content="Hello") + @less_console_noise_decorator def test_reject_with_prejudice_does_not_send_email(self): msg = "Create a domain request and reject it with prejudice and see if email was sent." domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.APPROVED) self.check_email_sent(domain_request, msg, "reject_with_prejudice", 0) + @less_console_noise_decorator def assert_fsm_transition_raises_error(self, test_cases, method_to_run): """Given a list of test cases, check if each transition throws the intended error""" with boto3_mocking.clients.handler_for("sesv2", self.mock_client), less_console_noise(): @@ -308,6 +321,7 @@ class TestDomainRequest(TestCase): # Call the method method() + @less_console_noise_decorator def assert_fsm_transition_does_not_raise_error(self, test_cases, method_to_run): """Given a list of test cases, ensure that none of them throw transition errors""" with boto3_mocking.clients.handler_for("sesv2", self.mock_client), less_console_noise(): @@ -321,6 +335,7 @@ class TestDomainRequest(TestCase): except exception_type: self.fail(f"{exception_type} was raised, but it was not expected.") + @less_console_noise_decorator def test_submit_transition_allowed_with_no_investigator(self): """ Tests for attempting to transition without an investigator. @@ -339,6 +354,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_does_not_raise_error(test_cases, "submit") + @less_console_noise_decorator def test_submit_transition_allowed_with_investigator_not_staff(self): """ Tests for attempting to transition with an investigator user that is not staff. @@ -356,6 +372,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_does_not_raise_error(test_cases, "submit") + @less_console_noise_decorator def test_submit_transition_allowed(self): """ Test that calling submit from allowable statuses does raises TransitionNotAllowed. @@ -369,26 +386,27 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_does_not_raise_error(test_cases, "submit") + @less_console_noise_decorator def test_submit_transition_allowed_twice(self): """ Test that rotating between submit and in_review doesn't throw an error """ with boto3_mocking.clients.handler_for("sesv2", self.mock_client): - with less_console_noise(): - try: - # Make a submission - self.in_review_domain_request.submit() + try: + # Make a submission + self.in_review_domain_request.submit() - # Rerun the old method to get back to the original state - self.in_review_domain_request.in_review() + # Rerun the old method to get back to the original state + self.in_review_domain_request.in_review() - # Make another submission - self.in_review_domain_request.submit() - except TransitionNotAllowed: - self.fail("TransitionNotAllowed was raised, but it was not expected.") + # Make another submission + self.in_review_domain_request.submit() + except TransitionNotAllowed: + self.fail("TransitionNotAllowed was raised, but it was not expected.") self.assertEqual(self.in_review_domain_request.status, DomainRequest.DomainRequestStatus.SUBMITTED) + @less_console_noise_decorator def test_submit_transition_not_allowed(self): """ Test that calling submit against transition rules raises TransitionNotAllowed. @@ -402,6 +420,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_raises_error(test_cases, "submit") + @less_console_noise_decorator def test_in_review_transition_allowed(self): """ Test that calling in_review from allowable statuses does raises TransitionNotAllowed. @@ -416,6 +435,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_does_not_raise_error(test_cases, "in_review") + @less_console_noise_decorator def test_in_review_transition_not_allowed_with_no_investigator(self): """ Tests for attempting to transition without an investigator @@ -433,6 +453,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_raises_error(test_cases, "in_review") + @less_console_noise_decorator def test_in_review_transition_not_allowed_with_investigator_not_staff(self): """ Tests for attempting to transition with an investigator that is not staff. @@ -452,6 +473,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_raises_error(test_cases, "in_review") + @less_console_noise_decorator def test_in_review_transition_not_allowed(self): """ Test that calling in_review against transition rules raises TransitionNotAllowed. @@ -464,6 +486,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_raises_error(test_cases, "in_review") + @less_console_noise_decorator def test_action_needed_transition_allowed(self): """ Test that calling action_needed from allowable statuses does raises TransitionNotAllowed. @@ -477,6 +500,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_does_not_raise_error(test_cases, "action_needed") + @less_console_noise_decorator def test_action_needed_transition_not_allowed_with_no_investigator(self): """ Tests for attempting to transition without an investigator @@ -494,6 +518,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_raises_error(test_cases, "action_needed") + @less_console_noise_decorator def test_action_needed_transition_not_allowed_with_investigator_not_staff(self): """ Tests for attempting to transition with an investigator that is not staff @@ -512,6 +537,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_raises_error(test_cases, "action_needed") + @less_console_noise_decorator def test_action_needed_transition_not_allowed(self): """ Test that calling action_needed against transition rules raises TransitionNotAllowed. @@ -525,6 +551,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_raises_error(test_cases, "action_needed") + @less_console_noise_decorator def test_approved_transition_allowed(self): """ Test that calling action_needed from allowable statuses does raises TransitionNotAllowed. @@ -538,6 +565,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_does_not_raise_error(test_cases, "approve") + @less_console_noise_decorator def test_approved_transition_not_allowed_with_no_investigator(self): """ Tests for attempting to transition without an investigator @@ -554,6 +582,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_raises_error(test_cases, "approve") + @less_console_noise_decorator def test_approved_transition_not_allowed_with_investigator_not_staff(self): """ Tests for attempting to transition with an investigator that is not staff @@ -571,6 +600,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_raises_error(test_cases, "approve") + @less_console_noise_decorator def test_approved_skips_sending_email(self): """ Test that calling .approve with send_email=False doesn't actually send @@ -578,12 +608,12 @@ class TestDomainRequest(TestCase): """ with boto3_mocking.clients.handler_for("sesv2", self.mock_client): - with less_console_noise(): - self.submitted_domain_request.approve(send_email=False) + self.submitted_domain_request.approve(send_email=False) # Assert that no emails were sent self.assertEqual(len(self.mock_client.EMAILS_SENT), 0) + @less_console_noise_decorator def test_approved_transition_not_allowed(self): """ Test that calling action_needed against transition rules raises TransitionNotAllowed. @@ -596,6 +626,7 @@ class TestDomainRequest(TestCase): ] self.assert_fsm_transition_raises_error(test_cases, "approve") + @less_console_noise_decorator def test_withdraw_transition_allowed(self): """ Test that calling action_needed from allowable statuses does raises TransitionNotAllowed. @@ -608,6 +639,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_does_not_raise_error(test_cases, "withdraw") + @less_console_noise_decorator def test_withdraw_transition_allowed_with_no_investigator(self): """ Tests for attempting to transition without an investigator. @@ -625,6 +657,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_does_not_raise_error(test_cases, "withdraw") + @less_console_noise_decorator def test_withdraw_transition_allowed_with_investigator_not_staff(self): """ Tests for attempting to transition when investigator is not staff. @@ -643,6 +676,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_does_not_raise_error(test_cases, "withdraw") + @less_console_noise_decorator def test_withdraw_transition_not_allowed(self): """ Test that calling action_needed against transition rules raises TransitionNotAllowed. @@ -657,6 +691,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_raises_error(test_cases, "withdraw") + @less_console_noise_decorator def test_reject_transition_allowed(self): """ Test that calling action_needed from allowable statuses does raises TransitionNotAllowed. @@ -669,6 +704,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_does_not_raise_error(test_cases, "reject") + @less_console_noise_decorator def test_reject_transition_not_allowed_with_no_investigator(self): """ Tests for attempting to transition without an investigator @@ -685,6 +721,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_raises_error(test_cases, "reject") + @less_console_noise_decorator def test_reject_transition_not_allowed_with_investigator_not_staff(self): """ Tests for attempting to transition when investigator is not staff @@ -702,6 +739,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_raises_error(test_cases, "reject") + @less_console_noise_decorator def test_reject_transition_not_allowed(self): """ Test that calling action_needed against transition rules raises TransitionNotAllowed. @@ -716,6 +754,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_raises_error(test_cases, "reject") + @less_console_noise_decorator def test_reject_with_prejudice_transition_allowed(self): """ Test that calling action_needed from allowable statuses does raises TransitionNotAllowed. @@ -729,6 +768,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_does_not_raise_error(test_cases, "reject_with_prejudice") + @less_console_noise_decorator def test_reject_with_prejudice_transition_not_allowed_with_no_investigator(self): """ Tests for attempting to transition without an investigator @@ -746,6 +786,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_raises_error(test_cases, "reject_with_prejudice") + @less_console_noise_decorator def test_reject_with_prejudice_not_allowed_with_investigator_not_staff(self): """ Tests for attempting to transition when investigator is not staff @@ -764,6 +805,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_raises_error(test_cases, "reject_with_prejudice") + @less_console_noise_decorator def test_reject_with_prejudice_transition_not_allowed(self): """ Test that calling action_needed against transition rules raises TransitionNotAllowed. @@ -777,6 +819,7 @@ class TestDomainRequest(TestCase): self.assert_fsm_transition_raises_error(test_cases, "reject_with_prejudice") + @less_console_noise_decorator def test_transition_not_allowed_approved_in_review_when_domain_is_active(self): """Create a domain request with status approved, create a matching domain that is active, and call in_review against transition rules""" @@ -790,13 +833,13 @@ class TestDomainRequest(TestCase): return True # Override to return True with boto3_mocking.clients.handler_for("sesv2", self.mock_client): - with less_console_noise(): - # Use patch to temporarily replace is_active with the custom implementation - with patch.object(Domain, "is_active", custom_is_active): - # Now, when you call is_active on Domain, it will return True - with self.assertRaises(TransitionNotAllowed): - self.approved_domain_request.in_review() + # Use patch to temporarily replace is_active with the custom implementation + with patch.object(Domain, "is_active", custom_is_active): + # Now, when you call is_active on Domain, it will return True + with self.assertRaises(TransitionNotAllowed): + self.approved_domain_request.in_review() + @less_console_noise_decorator def test_transition_not_allowed_approved_action_needed_when_domain_is_active(self): """Create a domain request with status approved, create a matching domain that is active, and call action_needed against transition rules""" @@ -810,13 +853,13 @@ class TestDomainRequest(TestCase): return True # Override to return True with boto3_mocking.clients.handler_for("sesv2", self.mock_client): - with less_console_noise(): - # Use patch to temporarily replace is_active with the custom implementation - with patch.object(Domain, "is_active", custom_is_active): - # Now, when you call is_active on Domain, it will return True - with self.assertRaises(TransitionNotAllowed): - self.approved_domain_request.action_needed() + # Use patch to temporarily replace is_active with the custom implementation + with patch.object(Domain, "is_active", custom_is_active): + # Now, when you call is_active on Domain, it will return True + with self.assertRaises(TransitionNotAllowed): + self.approved_domain_request.action_needed() + @less_console_noise_decorator def test_transition_not_allowed_approved_rejected_when_domain_is_active(self): """Create a domain request with status approved, create a matching domain that is active, and call reject against transition rules""" @@ -830,13 +873,13 @@ class TestDomainRequest(TestCase): return True # Override to return True with boto3_mocking.clients.handler_for("sesv2", self.mock_client): - with less_console_noise(): - # Use patch to temporarily replace is_active with the custom implementation - with patch.object(Domain, "is_active", custom_is_active): - # Now, when you call is_active on Domain, it will return True - with self.assertRaises(TransitionNotAllowed): - self.approved_domain_request.reject() + # Use patch to temporarily replace is_active with the custom implementation + with patch.object(Domain, "is_active", custom_is_active): + # Now, when you call is_active on Domain, it will return True + with self.assertRaises(TransitionNotAllowed): + self.approved_domain_request.reject() + @less_console_noise_decorator def test_transition_not_allowed_approved_ineligible_when_domain_is_active(self): """Create a domain request with status approved, create a matching domain that is active, and call reject_with_prejudice against transition rules""" @@ -850,12 +893,11 @@ class TestDomainRequest(TestCase): return True # Override to return True with boto3_mocking.clients.handler_for("sesv2", self.mock_client): - with less_console_noise(): - # Use patch to temporarily replace is_active with the custom implementation - with patch.object(Domain, "is_active", custom_is_active): - # Now, when you call is_active on Domain, it will return True - with self.assertRaises(TransitionNotAllowed): - self.approved_domain_request.reject_with_prejudice() + # Use patch to temporarily replace is_active with the custom implementation + with patch.object(Domain, "is_active", custom_is_active): + # Now, when you call is_active on Domain, it will return True + with self.assertRaises(TransitionNotAllowed): + self.approved_domain_request.reject_with_prejudice() def test_approve_from_rejected_clears_rejection_reason(self): """When transitioning from rejected to approved on a domain request, @@ -946,6 +988,7 @@ class TestPermissions(TestCase): self.mock_client.EMAILS_SENT.clear() @boto3_mocking.patching + @less_console_noise_decorator def test_approval_creates_role(self): draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov") user, _ = User.objects.get_or_create() @@ -955,10 +998,9 @@ class TestPermissions(TestCase): ) with boto3_mocking.clients.handler_for("sesv2", self.mock_client): - with less_console_noise(): - # skip using the submit method - domain_request.status = DomainRequest.DomainRequestStatus.SUBMITTED - domain_request.approve() + # skip using the submit method + domain_request.status = DomainRequest.DomainRequestStatus.SUBMITTED + domain_request.approve() # should be a role for this user domain = Domain.objects.get(name="igorville.gov") @@ -982,6 +1024,7 @@ class TestDomainInformation(TestCase): DraftDomain.objects.all().delete() @boto3_mocking.patching + @less_console_noise_decorator def test_approval_creates_info(self): draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov") user, _ = User.objects.get_or_create() @@ -991,10 +1034,9 @@ class TestDomainInformation(TestCase): ) with boto3_mocking.clients.handler_for("sesv2", self.mock_client): - with less_console_noise(): - # skip using the submit method - domain_request.status = DomainRequest.DomainRequestStatus.SUBMITTED - domain_request.approve() + # skip using the submit method + domain_request.status = DomainRequest.DomainRequestStatus.SUBMITTED + domain_request.approve() # should be an information present for this domain domain = Domain.objects.get(name="igorville.gov") @@ -1023,6 +1065,7 @@ class TestDomainInformation(TestCase): class TestInvitations(TestCase): """Test the retrieval of invitations.""" + @less_console_noise_decorator def setUp(self): self.domain, _ = Domain.objects.get_or_create(name="igorville.gov") self.email = "mayor@igorville.gov" @@ -1032,24 +1075,27 @@ class TestInvitations(TestCase): # clean out the roles each time UserDomainRole.objects.all().delete() + @less_console_noise_decorator def test_retrieval_creates_role(self): self.invitation.retrieve() self.assertTrue(UserDomainRole.objects.get(user=self.user, domain=self.domain)) + @less_console_noise_decorator def test_retrieve_missing_user_error(self): # get rid of matching users User.objects.filter(email=self.email).delete() with self.assertRaises(RuntimeError): self.invitation.retrieve() + @less_console_noise_decorator def test_retrieve_existing_role_no_error(self): # make the overlapping role UserDomainRole.objects.get_or_create(user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER) # this is not an error but does produce a console warning - with less_console_noise(): - self.invitation.retrieve() + self.invitation.retrieve() self.assertEqual(self.invitation.status, DomainInvitation.DomainInvitationStatus.RETRIEVED) + @less_console_noise_decorator def test_retrieve_on_each_login(self): """A user's authenticate on_each_login callback retrieves their invitations.""" self.user.on_each_login() @@ -1060,6 +1106,7 @@ class TestUser(TestCase): """Test actions that occur on user login, test class method that controls how users get validated.""" + @less_console_noise_decorator def setUp(self): self.email = "mayor@igorville.gov" self.domain_name = "igorvilleInTransition.gov" @@ -1077,6 +1124,7 @@ class TestUser(TestCase): User.objects.all().delete() UserDomainRole.objects.all().delete() + @less_console_noise_decorator def test_check_transition_domains_without_domains_on_login(self): """A user's on_each_login callback does not check transition domains. This test makes sure that in the event a domain does not exist @@ -1085,35 +1133,41 @@ class TestUser(TestCase): self.user.on_each_login() self.assertFalse(Domain.objects.filter(name=self.domain_name).exists()) + @less_console_noise_decorator def test_identity_verification_with_domain_manager(self): """A domain manager should return False when tested with class method needs_identity_verification""" UserDomainRole.objects.get_or_create(user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER) self.assertFalse(User.needs_identity_verification(self.user.email, self.user.username)) + @less_console_noise_decorator def test_identity_verification_with_transition_user(self): """A user from the Verisign transition should return False when tested with class method needs_identity_verification""" TransitionDomain.objects.get_or_create(username=self.user.email, domain_name=self.domain_name) self.assertFalse(User.needs_identity_verification(self.user.email, self.user.username)) + @less_console_noise_decorator def test_identity_verification_with_very_important_person(self): """A Very Important Person should return False when tested with class method needs_identity_verification""" VerifiedByStaff.objects.get_or_create(email=self.user.email) self.assertFalse(User.needs_identity_verification(self.user.email, self.user.username)) + @less_console_noise_decorator def test_identity_verification_with_invited_user(self): """An invited user should return False when tested with class method needs_identity_verification""" DomainInvitation.objects.get_or_create(email=self.user.email, domain=self.domain) self.assertFalse(User.needs_identity_verification(self.user.email, self.user.username)) + @less_console_noise_decorator def test_identity_verification_with_new_user(self): """A new user who's neither transitioned nor invited should return True when tested with class method needs_identity_verification""" self.assertTrue(User.needs_identity_verification(self.user.email, self.user.username)) + @less_console_noise_decorator def test_check_domain_invitations_on_login_caps_email(self): """A DomainInvitation with an email address with capital letters should match a User record whose email address is not in caps""" @@ -1122,13 +1176,13 @@ class TestUser(TestCase): caps_email = "MAYOR@igorville.gov" # mock the domain invitation save routine with patch("registrar.models.DomainInvitation.save") as save_mock: - with less_console_noise(): - DomainInvitation.objects.get_or_create(email=caps_email, domain=self.domain) - self.user.check_domain_invitations_on_login() - # if check_domain_invitations_on_login properly matches exactly one - # Domain Invitation, then save routine should be called exactly once - save_mock.assert_called_once() + DomainInvitation.objects.get_or_create(email=caps_email, domain=self.domain) + self.user.check_domain_invitations_on_login() + # if check_domain_invitations_on_login properly matches exactly one + # Domain Invitation, then save routine should be called exactly once + save_mock.assert_called_once() + @less_console_noise_decorator def test_approved_domains_count(self): """Test that the correct approved domain count is returned for a user""" # with no associated approved domains, expect this to return 0 @@ -1153,6 +1207,7 @@ class TestUser(TestCase): UserDomainRole.objects.get_or_create(user=self.user, domain=domain5, role=UserDomainRole.Roles.MANAGER) self.assertEquals(self.user.get_approved_domains_count(), 4) + @less_console_noise_decorator def test_active_requests_count(self): """Test that the correct active domain requests count is returned for a user""" # with no associated active requests, expect this to return 0 @@ -1182,6 +1237,7 @@ class TestUser(TestCase): ) self.assertEquals(self.user.get_active_requests_count(), 3) + @less_console_noise_decorator def test_rejected_requests_count(self): """Test that the correct rejected domain requests count is returned for a user""" # with no associated rejected requests, expect this to return 0 @@ -1193,6 +1249,7 @@ class TestUser(TestCase): ) self.assertEquals(self.user.get_rejected_requests_count(), 1) + @less_console_noise_decorator def test_ineligible_requests_count(self): """Test that the correct ineligible domain requests count is returned for a user""" # with no associated ineligible requests, expect this to return 0 @@ -1204,6 +1261,7 @@ class TestUser(TestCase): ) self.assertEquals(self.user.get_ineligible_requests_count(), 1) + @less_console_noise_decorator def test_has_contact_info(self): """Test that has_contact_info properly returns""" # test with a user with contact info defined @@ -1216,6 +1274,7 @@ class TestUser(TestCase): class TestContact(TestCase): + @less_console_noise_decorator def setUp(self): self.email_for_invalid = "intern@igorville.gov" self.invalid_user, _ = User.objects.get_or_create( @@ -1242,6 +1301,7 @@ class TestContact(TestCase): Contact.objects.all().delete() User.objects.all().delete() + @less_console_noise_decorator def test_saving_contact_updates_user_first_last_names_and_phone(self): """When a contact is updated, we propagate the changes to the linked user if it exists.""" @@ -1271,6 +1331,7 @@ class TestContact(TestCase): self.assertEqual(self.invalid_user.last_name, "Baloney") self.assertEqual(self.invalid_user.phone, "123456789") + @less_console_noise_decorator def test_saving_contact_does_not_update_user_first_last_names_and_phone(self): """When a contact is updated, we avoid propagating the changes to the linked user if it already has a value""" @@ -1298,6 +1359,7 @@ class TestContact(TestCase): self.assertEqual(self.user.last_name, "Lebowski") self.assertEqual(self.user.phone, "123456789") + @less_console_noise_decorator def test_saving_contact_does_not_update_user_email(self): """When a contact's email is updated, the change is not propagated to the user.""" self.contact.email = "joey.baloney@diaperville.com" @@ -1310,6 +1372,7 @@ class TestContact(TestCase): self.assertEqual(self.contact.email, "joey.baloney@diaperville.com") self.assertEqual(self.user.email, "mayor@igorville.gov") + @less_console_noise_decorator def test_saving_contact_does_not_update_user_email_when_none(self): """When a contact's email is updated, and the first/last name is none, the change is not propagated to the user.""" @@ -1323,6 +1386,7 @@ class TestContact(TestCase): self.assertEqual(self.invalid_contact.email, "joey.baloney@diaperville.com") self.assertEqual(self.invalid_user.email, "intern@igorville.gov") + @less_console_noise_decorator def test_has_more_than_one_join(self): """Test the Contact model method, has_more_than_one_join""" # test for a contact which has one user defined @@ -1332,6 +1396,7 @@ class TestContact(TestCase): self.assertFalse(self.contact_as_so.has_more_than_one_join("senior_official")) self.assertTrue(self.contact_as_so.has_more_than_one_join("submitted_domain_requests")) + @less_console_noise_decorator def test_has_contact_info(self): """Test that has_contact_info properly returns""" # test with a contact with contact info defined @@ -1350,6 +1415,7 @@ class TestDomainRequestCustomSave(TestCase): DomainRequest.objects.all().delete() super().tearDown() + @less_console_noise_decorator def test_create_or_update_organization_type_new_instance(self): """Test create_or_update_organization_type when creating a new instance""" domain_request = completed_domain_request( @@ -1361,6 +1427,7 @@ class TestDomainRequestCustomSave(TestCase): self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION) + @less_console_noise_decorator def test_create_or_update_organization_type_new_instance_federal_does_nothing(self): """Test if create_or_update_organization_type does nothing when creating a new instance for federal""" domain_request = completed_domain_request( @@ -1372,6 +1439,7 @@ class TestDomainRequestCustomSave(TestCase): self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.FEDERAL) self.assertEqual(domain_request.is_election_board, None) + @less_console_noise_decorator def test_create_or_update_organization_type_existing_instance_updates_election_board(self): """Test create_or_update_organization_type for an existing instance.""" domain_request = completed_domain_request( @@ -1400,6 +1468,7 @@ class TestDomainRequestCustomSave(TestCase): self.assertEqual(domain_request.is_election_board, False) self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY) + @less_console_noise_decorator def test_create_or_update_organization_type_existing_instance_updates_generic_org_type(self): """Test create_or_update_organization_type when modifying generic_org_type on an existing instance.""" domain_request = completed_domain_request( @@ -1436,6 +1505,7 @@ class TestDomainRequestCustomSave(TestCase): domain_request_tribal.organization_type, DomainRequest.OrgChoicesElectionOffice.STATE_OR_TERRITORY_ELECTION ) + @less_console_noise_decorator def test_create_or_update_organization_type_no_update(self): """Test create_or_update_organization_type when there are no values to update.""" @@ -1499,6 +1569,7 @@ class TestDomainInformationCustomSave(TestCase): Domain.objects.all().delete() super().tearDown() + @less_console_noise_decorator def test_create_or_update_organization_type_new_instance(self): """Test create_or_update_organization_type when creating a new instance""" domain_request = completed_domain_request( @@ -1511,6 +1582,7 @@ class TestDomainInformationCustomSave(TestCase): domain_information = DomainInformation.create_from_da(domain_request) self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION) + @less_console_noise_decorator def test_create_or_update_organization_type_new_instance_federal_does_nothing(self): """Test if create_or_update_organization_type does nothing when creating a new instance for federal""" domain_request = completed_domain_request( @@ -1524,6 +1596,7 @@ class TestDomainInformationCustomSave(TestCase): self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.FEDERAL) self.assertEqual(domain_information.is_election_board, None) + @less_console_noise_decorator def test_create_or_update_organization_type_existing_instance_updates_election_board(self): """Test create_or_update_organization_type for an existing instance.""" domain_request = completed_domain_request( @@ -1554,6 +1627,7 @@ class TestDomainInformationCustomSave(TestCase): self.assertEqual(domain_information.is_election_board, False) self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY) + @less_console_noise_decorator def test_create_or_update_organization_type_existing_instance_updates_generic_org_type(self): """Test create_or_update_organization_type when modifying generic_org_type on an existing instance.""" domain_request = completed_domain_request( @@ -1593,6 +1667,7 @@ class TestDomainInformationCustomSave(TestCase): DomainRequest.OrgChoicesElectionOffice.STATE_OR_TERRITORY_ELECTION, ) + @less_console_noise_decorator def test_create_or_update_organization_type_no_update(self): """Test create_or_update_organization_type when there are no values to update.""" @@ -1650,6 +1725,7 @@ class TestDomainInformationCustomSave(TestCase): class TestDomainRequestIncomplete(TestCase): + @less_console_noise_decorator def setUp(self): super().setUp() self.factory = RequestFactory() @@ -1717,12 +1793,14 @@ class TestDomainRequestIncomplete(TestCase): DomainRequest.objects.all().delete() Contact.objects.all().delete() + @less_console_noise_decorator def test_is_federal_complete(self): self.assertTrue(self.domain_request._is_federal_complete()) self.domain_request.federal_type = None self.domain_request.save() self.assertFalse(self.domain_request._is_federal_complete()) + @less_console_noise_decorator def test_is_interstate_complete(self): self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.INTERSTATE self.domain_request.about_your_organization = "Something something about your organization" @@ -1732,6 +1810,7 @@ class TestDomainRequestIncomplete(TestCase): self.domain_request.save() self.assertFalse(self.domain_request._is_interstate_complete()) + @less_console_noise_decorator def test_is_state_or_territory_complete(self): self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.STATE_OR_TERRITORY self.domain_request.is_election_board = True @@ -1742,6 +1821,7 @@ class TestDomainRequestIncomplete(TestCase): # is_election_board will overwrite to False bc of _update_org_type_from_generic_org_and_election self.assertTrue(self.domain_request._is_state_or_territory_complete()) + @less_console_noise_decorator def test_is_tribal_complete(self): self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.TRIBAL self.domain_request.tribe_name = "Tribe Name" @@ -1754,6 +1834,7 @@ class TestDomainRequestIncomplete(TestCase): # is_election_board will overwrite to False bc of _update_org_type_from_generic_org_and_election self.assertFalse(self.domain_request._is_tribal_complete()) + @less_console_noise_decorator def test_is_county_complete(self): self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.COUNTY self.domain_request.is_election_board = False @@ -1764,6 +1845,7 @@ class TestDomainRequestIncomplete(TestCase): # is_election_board will overwrite to False bc of _update_org_type_from_generic_org_and_election self.assertTrue(self.domain_request._is_county_complete()) + @less_console_noise_decorator def test_is_city_complete(self): self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.CITY self.domain_request.is_election_board = False @@ -1774,6 +1856,7 @@ class TestDomainRequestIncomplete(TestCase): # is_election_board will overwrite to False bc of _update_org_type_from_generic_org_and_election self.assertTrue(self.domain_request._is_city_complete()) + @less_console_noise_decorator def test_is_special_district_complete(self): self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.SPECIAL_DISTRICT self.domain_request.about_your_organization = "Something something about your organization" @@ -1786,6 +1869,7 @@ class TestDomainRequestIncomplete(TestCase): # is_election_board will overwrite to False bc of _update_org_type_from_generic_org_and_election self.assertFalse(self.domain_request._is_special_district_complete()) + @less_console_noise_decorator def test_is_organization_name_and_address_complete(self): self.assertTrue(self.domain_request._is_organization_name_and_address_complete()) self.domain_request.organization_name = None @@ -1793,30 +1877,35 @@ class TestDomainRequestIncomplete(TestCase): self.domain_request.save() self.assertTrue(self.domain_request._is_organization_name_and_address_complete()) + @less_console_noise_decorator def test_is_senior_official_complete(self): self.assertTrue(self.domain_request._is_senior_official_complete()) self.domain_request.senior_official = None self.domain_request.save() self.assertFalse(self.domain_request._is_senior_official_complete()) + @less_console_noise_decorator def test_is_requested_domain_complete(self): self.assertTrue(self.domain_request._is_requested_domain_complete()) self.domain_request.requested_domain = None self.domain_request.save() self.assertFalse(self.domain_request._is_requested_domain_complete()) + @less_console_noise_decorator def test_is_purpose_complete(self): self.assertTrue(self.domain_request._is_purpose_complete()) self.domain_request.purpose = None self.domain_request.save() self.assertFalse(self.domain_request._is_purpose_complete()) + @less_console_noise_decorator def test_is_submitter_complete(self): self.assertTrue(self.domain_request._is_submitter_complete()) self.domain_request.submitter = None self.domain_request.save() self.assertFalse(self.domain_request._is_submitter_complete()) + @less_console_noise_decorator def test_is_other_contacts_complete_missing_one_field(self): self.assertTrue(self.domain_request._is_other_contacts_complete()) contact = self.domain_request.other_contacts.first() @@ -1824,10 +1913,12 @@ class TestDomainRequestIncomplete(TestCase): contact.save() self.assertFalse(self.domain_request._is_other_contacts_complete()) + @less_console_noise_decorator def test_is_other_contacts_complete_all_none(self): self.domain_request.other_contacts.clear() self.assertFalse(self.domain_request._is_other_contacts_complete()) + @less_console_noise_decorator def test_is_other_contacts_False_and_has_rationale(self): # Click radio button "No" for no other contacts and give rationale self.domain_request.other_contacts.clear() @@ -1835,6 +1926,7 @@ class TestDomainRequestIncomplete(TestCase): self.domain_request.no_other_contacts_rationale = "Some rationale" self.assertTrue(self.domain_request._is_other_contacts_complete()) + @less_console_noise_decorator def test_is_other_contacts_False_and_NO_rationale(self): # Click radio button "No" for no other contacts and DONT give rationale self.domain_request.other_contacts.clear() @@ -1842,6 +1934,7 @@ class TestDomainRequestIncomplete(TestCase): self.domain_request.no_other_contacts_rationale = None self.assertFalse(self.domain_request._is_other_contacts_complete()) + @less_console_noise_decorator def test_is_additional_details_complete(self): test_cases = [ # CISA Rep - Yes @@ -2048,6 +2141,7 @@ class TestDomainRequestIncomplete(TestCase): msg=f"Failed for case: {case}", ) + @less_console_noise_decorator def test_is_policy_acknowledgement_complete(self): self.assertTrue(self.domain_request._is_policy_acknowledgement_complete()) self.domain_request.is_policy_acknowledged = False @@ -2055,6 +2149,7 @@ class TestDomainRequestIncomplete(TestCase): self.domain_request.is_policy_acknowledged = None self.assertFalse(self.domain_request._is_policy_acknowledgement_complete()) + @less_console_noise_decorator def test_form_complete(self): request = self.factory.get("/") request.user = self.user From 4f1a82362fbd6677fdd5d6f52c28963d1f67b6e6 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 5 Jul 2024 13:56:12 -0400 Subject: [PATCH 020/184] test_views_domain --- src/registrar/tests/test_views_domain.py | 220 +++++++++++++---------- 1 file changed, 124 insertions(+), 96 deletions(-) diff --git a/src/registrar/tests/test_views_domain.py b/src/registrar/tests/test_views_domain.py index 890879811..8f9466cc9 100644 --- a/src/registrar/tests/test_views_domain.py +++ b/src/registrar/tests/test_views_domain.py @@ -5,6 +5,7 @@ from django.conf import settings from django.urls import reverse from django.contrib.auth import get_user_model +from api.tests.common import less_console_noise_decorator from .common import MockEppLib, MockSESClient, create_user # type: ignore from django_webtest import WebTest # type: ignore import boto3_mocking # type: ignore @@ -45,6 +46,7 @@ logger = logging.getLogger(__name__) class TestWithDomainPermissions(TestWithUser): + @less_console_noise_decorator def setUp(self): super().setUp() self.domain, _ = Domain.objects.get_or_create(name="igorville.gov") @@ -142,6 +144,7 @@ class TestWithDomainPermissions(TestWithUser): class TestDomainPermissions(TestWithDomainPermissions): + @less_console_noise_decorator def test_not_logged_in(self): """Not logged in gets a redirect to Login.""" for view_name in [ @@ -158,6 +161,7 @@ class TestDomainPermissions(TestWithDomainPermissions): response = self.client.get(reverse(view_name, kwargs={"pk": self.domain.id})) self.assertEqual(response.status_code, 302) + @less_console_noise_decorator def test_no_domain_role(self): """Logged in but no role gets 403 Forbidden.""" self.client.force_login(self.user) @@ -174,10 +178,10 @@ class TestDomainPermissions(TestWithDomainPermissions): "domain-security-email", ]: with self.subTest(view_name=view_name): - with less_console_noise(): - response = self.client.get(reverse(view_name, kwargs={"pk": self.domain.id})) + response = self.client.get(reverse(view_name, kwargs={"pk": self.domain.id})) self.assertEqual(response.status_code, 403) + @less_console_noise_decorator def test_domain_pages_blocked_for_on_hold_and_deleted(self): """Test that the domain pages are blocked for on hold and deleted domains""" @@ -199,9 +203,8 @@ class TestDomainPermissions(TestWithDomainPermissions): self.domain_deleted, ]: with self.subTest(view_name=view_name, domain=domain): - with less_console_noise(): - response = self.client.get(reverse(view_name, kwargs={"pk": domain.id})) - self.assertEqual(response.status_code, 403) + response = self.client.get(reverse(view_name, kwargs={"pk": domain.id})) + self.assertEqual(response.status_code, 403) class TestDomainOverview(TestWithDomainPermissions, WebTest): @@ -312,21 +315,25 @@ class TestDomainManagers(TestDomainOverview): """Ensure that the user has its original permissions""" super().tearDown() + @less_console_noise_decorator def test_domain_managers(self): response = self.client.get(reverse("domain-users", kwargs={"pk": self.domain.id})) self.assertContains(response, "Domain managers") + @less_console_noise_decorator def test_domain_managers_add_link(self): """Button to get to user add page works.""" management_page = self.app.get(reverse("domain-users", kwargs={"pk": self.domain.id})) add_page = management_page.click("Add a domain manager") self.assertContains(add_page, "Add a domain manager") + @less_console_noise_decorator def test_domain_user_add(self): response = self.client.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) self.assertContains(response, "Add a domain manager") @boto3_mocking.patching + @less_console_noise_decorator def test_domain_user_add_form(self): """Adding an existing user works.""" other_user, _ = get_user_model().objects.get_or_create(email="mayor@igorville.gov") @@ -353,6 +360,7 @@ class TestDomainManagers(TestDomainOverview): self.assertContains(success_page, "mayor@igorville.gov") @boto3_mocking.patching + @less_console_noise_decorator def test_domain_invitation_created(self): """Add user on a nonexistent email creates an invitation. @@ -383,6 +391,7 @@ class TestDomainManagers(TestDomainOverview): self.assertTrue(DomainInvitation.objects.filter(email=email_address).exists()) @boto3_mocking.patching + @less_console_noise_decorator def test_domain_invitation_created_for_caps_email(self): """Add user on a nonexistent email with CAPS creates an invitation to lowercase email. @@ -403,8 +412,7 @@ class TestDomainManagers(TestDomainOverview): mock_client = MockSESClient() with boto3_mocking.clients.handler_for("sesv2", mock_client): - with less_console_noise(): - success_result = add_page.form.submit() + success_result = add_page.form.submit() self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) success_page = success_result.follow() @@ -414,6 +422,7 @@ class TestDomainManagers(TestDomainOverview): self.assertTrue(DomainInvitation.objects.filter(email=email_address).exists()) @boto3_mocking.patching + @less_console_noise_decorator def test_domain_invitation_email_sent(self): """Inviting a non-existent user sends them an email.""" # make sure there is no user with this email @@ -425,12 +434,11 @@ class TestDomainManagers(TestDomainOverview): mock_client = MagicMock() mock_client_instance = mock_client.return_value with boto3_mocking.clients.handler_for("sesv2", mock_client): - with less_console_noise(): - add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - add_page.form["email"] = email_address - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - add_page.form.submit() + add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + add_page.form["email"] = email_address + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + add_page.form.submit() # check the mock instance to see if `send_email` was called right mock_client_instance.send_email.assert_called_once_with( @@ -440,6 +448,7 @@ class TestDomainManagers(TestDomainOverview): ) @boto3_mocking.patching + @less_console_noise_decorator def test_domain_invitation_email_has_email_as_requestor_non_existent(self): """Inviting a non existent user sends them an email, with email as the name.""" # make sure there is no user with this email @@ -452,12 +461,11 @@ class TestDomainManagers(TestDomainOverview): mock_client_instance = mock_client.return_value with boto3_mocking.clients.handler_for("sesv2", mock_client): - with less_console_noise(): - add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - add_page.form["email"] = email_address - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - add_page.form.submit() + add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + add_page.form["email"] = email_address + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + add_page.form.submit() # check the mock instance to see if `send_email` was called right mock_client_instance.send_email.assert_called_once_with( @@ -479,6 +487,7 @@ class TestDomainManagers(TestDomainOverview): self.assertNotIn("First Last", email_content) @boto3_mocking.patching + @less_console_noise_decorator def test_domain_invitation_email_has_email_as_requestor(self): """Inviting a user sends them an email, with email as the name.""" # Create a fake user object @@ -491,12 +500,11 @@ class TestDomainManagers(TestDomainOverview): mock_client_instance = mock_client.return_value with boto3_mocking.clients.handler_for("sesv2", mock_client): - with less_console_noise(): - add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - add_page.form["email"] = email_address - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - add_page.form.submit() + add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + add_page.form["email"] = email_address + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + add_page.form.submit() # check the mock instance to see if `send_email` was called right mock_client_instance.send_email.assert_called_once_with( @@ -518,6 +526,7 @@ class TestDomainManagers(TestDomainOverview): self.assertNotIn("First Last", email_content) @boto3_mocking.patching + @less_console_noise_decorator def test_domain_invitation_email_has_email_as_requestor_staff(self): """Inviting a user sends them an email, with email as the name.""" # Create a fake user object @@ -534,12 +543,11 @@ class TestDomainManagers(TestDomainOverview): mock_client_instance = mock_client.return_value with boto3_mocking.clients.handler_for("sesv2", mock_client): - with less_console_noise(): - add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - add_page.form["email"] = email_address - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - add_page.form.submit() + add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + add_page.form["email"] = email_address + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + add_page.form.submit() # check the mock instance to see if `send_email` was called right mock_client_instance.send_email.assert_called_once_with( @@ -561,6 +569,7 @@ class TestDomainManagers(TestDomainOverview): self.assertNotIn("First Last", email_content) @boto3_mocking.patching + @less_console_noise_decorator def test_domain_invitation_email_displays_error_non_existent(self): """Inviting a non existent user sends them an email, with email as the name.""" # make sure there is no user with this email @@ -577,12 +586,11 @@ class TestDomainManagers(TestDomainOverview): mock_error_message = MagicMock() with boto3_mocking.clients.handler_for("sesv2", mock_client): with patch("django.contrib.messages.error") as mock_error_message: - with less_console_noise(): - add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - add_page.form["email"] = email_address - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - add_page.form.submit().follow() + add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + add_page.form["email"] = email_address + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + add_page.form.submit().follow() expected_message_content = "Can't send invitation email. No email is associated with your account." @@ -593,6 +601,7 @@ class TestDomainManagers(TestDomainOverview): self.assertEqual(expected_message_content, returned_error_message) @boto3_mocking.patching + @less_console_noise_decorator def test_domain_invitation_email_displays_error(self): """When the requesting user has no email, an error is displayed""" # make sure there is no user with this email @@ -611,12 +620,11 @@ class TestDomainManagers(TestDomainOverview): mock_error_message = MagicMock() with boto3_mocking.clients.handler_for("sesv2", mock_client): with patch("django.contrib.messages.error") as mock_error_message: - with less_console_noise(): - add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - add_page.form["email"] = email_address - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - add_page.form.submit().follow() + add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + add_page.form["email"] = email_address + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + add_page.form.submit().follow() expected_message_content = "Can't send invitation email. No email is associated with your account." @@ -626,34 +634,35 @@ class TestDomainManagers(TestDomainOverview): # Check that the message content is what we expect self.assertEqual(expected_message_content, returned_error_message) + @less_console_noise_decorator def test_domain_invitation_cancel(self): """Posting to the delete view deletes an invitation.""" email_address = "mayor@igorville.gov" invitation, _ = DomainInvitation.objects.get_or_create(domain=self.domain, email=email_address) mock_client = MockSESClient() with boto3_mocking.clients.handler_for("sesv2", mock_client): - with less_console_noise(): - self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id})) + self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id})) mock_client.EMAILS_SENT.clear() with self.assertRaises(DomainInvitation.DoesNotExist): DomainInvitation.objects.get(id=invitation.id) + @less_console_noise_decorator def test_domain_invitation_cancel_retrieved_invitation(self): """Posting to the delete view when invitation retrieved returns an error message""" email_address = "mayor@igorville.gov" invitation, _ = DomainInvitation.objects.get_or_create( domain=self.domain, email=email_address, status=DomainInvitation.DomainInvitationStatus.RETRIEVED ) - with less_console_noise(): - response = self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id}), follow=True) - # Assert that an error message is displayed to the user - self.assertContains(response, f"Invitation to {email_address} has already been retrieved.") - # Assert that the Cancel link is not displayed - self.assertNotContains(response, "Cancel") + response = self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id}), follow=True) + # Assert that an error message is displayed to the user + self.assertContains(response, f"Invitation to {email_address} has already been retrieved.") + # Assert that the Cancel link is not displayed + self.assertNotContains(response, "Cancel") # Assert that the DomainInvitation is not deleted self.assertTrue(DomainInvitation.objects.filter(id=invitation.id).exists()) DomainInvitation.objects.filter(email=email_address).delete() + @less_console_noise_decorator def test_domain_invitation_cancel_no_permissions(self): """Posting to the delete view as a different user should fail.""" email_address = "mayor@igorville.gov" @@ -664,12 +673,12 @@ class TestDomainManagers(TestDomainOverview): self.client.force_login(other_user) mock_client = MagicMock() with boto3_mocking.clients.handler_for("sesv2", mock_client): - with less_console_noise(): # permission denied makes console errors - result = self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id})) + result = self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id})) self.assertEqual(result.status_code, 403) @boto3_mocking.patching + @less_console_noise_decorator def test_domain_invitation_flow(self): """Send an invitation to a new user, log in and load the dashboard.""" email_address = "mayor@igorville.gov" @@ -685,8 +694,7 @@ class TestDomainManagers(TestDomainOverview): mock_client = MagicMock() with boto3_mocking.clients.handler_for("sesv2", mock_client): - with less_console_noise(): - add_page.form.submit() + add_page.form.submit() # user was invited, create them new_user = User.objects.create(username=email_address, email=email_address) @@ -701,11 +709,13 @@ class TestDomainManagers(TestDomainOverview): class TestDomainNameservers(TestDomainOverview, MockEppLib): + @less_console_noise_decorator def test_domain_nameservers(self): """Can load domain's nameservers page.""" page = self.client.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) self.assertContains(page, "DNS name servers") + @less_console_noise_decorator def test_domain_nameservers_form_submit_one_nameserver(self): """Nameserver form submitted with one nameserver throws error. @@ -717,8 +727,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) # attempt to submit the form with only one nameserver, should error # regarding required fields - with less_console_noise(): # swallow log warning message - result = nameservers_page.form.submit() + result = nameservers_page.form.submit() # form submission was a post with an error, response should be a 200 # error text appears twice, once at the top of the page, once around # the required field. form requires a minimum of 2 name servers @@ -729,6 +738,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): status_code=200, ) + @less_console_noise_decorator def test_domain_nameservers_form_submit_subdomain_missing_ip(self): """Nameserver form catches missing ip error on subdomain. @@ -742,8 +752,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): # only one has ips nameservers_page.form["form-1-server"] = "ns2.igorville.gov" - with less_console_noise(): # swallow log warning message - result = nameservers_page.form.submit() + result = nameservers_page.form.submit() # form submission was a post with an error, response should be a 200 # error text appears twice, once at the top of the page, once around # the required field. subdomain missing an ip @@ -754,6 +763,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): status_code=200, ) + @less_console_noise_decorator def test_domain_nameservers_form_submit_missing_host(self): """Nameserver form catches error when host is missing. @@ -766,8 +776,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): # attempt to submit the form without two hosts, both subdomains, # only one has ips nameservers_page.form["form-1-ip"] = "127.0.0.1" - with less_console_noise(): # swallow log warning message - result = nameservers_page.form.submit() + result = nameservers_page.form.submit() # form submission was a post with an error, response should be a 200 # error text appears twice, once at the top of the page, once around # the required field. nameserver has ip but missing host @@ -778,6 +787,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): status_code=200, ) + @less_console_noise_decorator def test_domain_nameservers_form_submit_duplicate_host(self): """Nameserver form catches error when host is duplicated. @@ -790,8 +800,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): # attempt to submit the form with duplicate host names of fake.host.com nameservers_page.form["form-0-ip"] = "" nameservers_page.form["form-1-server"] = "fake.host.com" - with less_console_noise(): # swallow log warning message - result = nameservers_page.form.submit() + result = nameservers_page.form.submit() # form submission was a post with an error, response should be a 200 # error text appears twice, once at the top of the page, once around # the required field. remove duplicate entry @@ -802,6 +811,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): status_code=200, ) + @less_console_noise_decorator def test_domain_nameservers_form_submit_whitespace(self): """Nameserver form removes whitespace from ip. @@ -820,8 +830,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): nameservers_page.form["form-0-ip"] = valid_ip nameservers_page.form["form-1-ip"] = valid_ip_2 nameservers_page.form["form-1-server"] = nameserver2 - with less_console_noise(): # swallow log warning message - result = nameservers_page.form.submit() + result = nameservers_page.form.submit() # form submission was a post with an ip address which has been stripped of whitespace, # response should be a 302 to success page self.assertEqual(result.status_code, 302) @@ -835,6 +844,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): # with an error message displayed, so need to follow 302 and test for success message self.assertContains(page, "The name servers for this domain have been updated") + @less_console_noise_decorator def test_domain_nameservers_form_submit_glue_record_not_allowed(self): """Nameserver form catches error when IP is present but host not subdomain. @@ -853,8 +863,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): nameservers_page.form["form-0-server"] = nameserver1 nameservers_page.form["form-1-server"] = nameserver2 nameservers_page.form["form-1-ip"] = valid_ip - with less_console_noise(): # swallow log warning message - result = nameservers_page.form.submit() + result = nameservers_page.form.submit() # form submission was a post with an error, response should be a 200 # error text appears twice, once at the top of the page, once around # the required field. nameserver has ip but missing host @@ -865,6 +874,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): status_code=200, ) + @less_console_noise_decorator def test_domain_nameservers_form_submit_invalid_ip(self): """Nameserver form catches invalid IP on submission. @@ -880,8 +890,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): # only one has ips nameservers_page.form["form-1-server"] = nameserver nameservers_page.form["form-1-ip"] = invalid_ip - with less_console_noise(): # swallow log warning message - result = nameservers_page.form.submit() + result = nameservers_page.form.submit() # form submission was a post with an error, response should be a 200 # error text appears twice, once at the top of the page, once around # the required field. nameserver has ip but missing host @@ -892,6 +901,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): status_code=200, ) + @less_console_noise_decorator def test_domain_nameservers_form_submit_invalid_host(self): """Nameserver form catches invalid host on submission. @@ -907,8 +917,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): # only one has ips nameservers_page.form["form-1-server"] = nameserver nameservers_page.form["form-1-ip"] = valid_ip - with less_console_noise(): # swallow log warning message - result = nameservers_page.form.submit() + result = nameservers_page.form.submit() # form submission was a post with an error, response should be a 200 # error text appears twice, once at the top of the page, once around # the required field. nameserver has invalid host @@ -919,6 +928,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): status_code=200, ) + @less_console_noise_decorator def test_domain_nameservers_form_submits_successfully(self): """Nameserver form submits successfully with valid input. @@ -935,8 +945,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): nameservers_page.form["form-0-ip"] = valid_ip nameservers_page.form["form-1-server"] = nameserver2 nameservers_page.form["form-1-ip"] = valid_ip_2 - with less_console_noise(): # swallow log warning message - result = nameservers_page.form.submit() + result = nameservers_page.form.submit() # form submission was a successful post, response should be a 302 self.assertEqual(result.status_code, 302) self.assertEqual( @@ -947,6 +956,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): page = result.follow() self.assertContains(page, "The name servers for this domain have been updated") + @less_console_noise_decorator def test_domain_nameservers_can_blank_out_first_or_second_one_if_enough_entries(self): """Nameserver form submits successfully with 2 valid inputs, even if the first or second entries are blanked out. @@ -969,8 +979,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): nameservers_page.form["form-1-ip"] = valid_ip_2 nameservers_page.form["form-2-server"] = nameserver3 nameservers_page.form["form-2-ip"] = valid_ip_3 - with less_console_noise(): # swallow log warning message - result = nameservers_page.form.submit() + result = nameservers_page.form.submit() # form submission was a successful post, response should be a 302 self.assertEqual(result.status_code, 302) @@ -996,8 +1005,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): nameservers_page.form["form-1-ip"] = valid_ip_2 nameservers_page.form["form-2-server"] = nameserver3 nameservers_page.form["form-2-ip"] = valid_ip_3 - with less_console_noise(): # swallow log warning message - result = nameservers_page.form.submit() + result = nameservers_page.form.submit() # form submission was a successful post, response should be a 302 self.assertEqual(result.status_code, 302) @@ -1009,6 +1017,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): nameservers_page = result.follow() self.assertContains(nameservers_page, "The name servers for this domain have been updated") + @less_console_noise_decorator def test_domain_nameservers_can_blank_out_first_and_second_one_if_enough_entries(self): """Nameserver form submits successfully with 2 valid inputs, even if the first and second entries are blanked out. @@ -1045,8 +1054,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): nameservers_page.form["form-2-ip"] = valid_ip_3 nameservers_page.form["form-3-server"] = nameserver4 nameservers_page.form["form-3-ip"] = valid_ip_4 - with less_console_noise(): # swallow log warning message - result = nameservers_page.form.submit() + result = nameservers_page.form.submit() # form submission was a successful post, response should be a 302 self.assertEqual(result.status_code, 302) @@ -1058,6 +1066,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): nameservers_page = result.follow() self.assertContains(nameservers_page, "The name servers for this domain have been updated") + @less_console_noise_decorator def test_domain_nameservers_form_invalid(self): """Nameserver form does not submit with invalid data. @@ -1069,8 +1078,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): # first two nameservers are required, so if we empty one out we should # get a form error nameservers_page.form["form-0-server"] = "" - with less_console_noise(): # swallow logged warning message - result = nameservers_page.form.submit() + result = nameservers_page.form.submit() # form submission was a post with an error, response should be a 200 # error text appears four times, twice at the top of the page, # once around each required field. @@ -1083,11 +1091,13 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): class TestDomainSeniorOfficial(TestDomainOverview): + @less_console_noise_decorator def test_domain_senior_official(self): """Can load domain's senior official page.""" page = self.client.get(reverse("domain-senior-official", kwargs={"pk": self.domain.id})) self.assertContains(page, "Senior official", count=13) + @less_console_noise_decorator def test_domain_senior_official_content(self): """Senior official information appears on the page.""" self.domain_information.senior_official = Contact(first_name="Testy") @@ -1096,6 +1106,7 @@ class TestDomainSeniorOfficial(TestDomainOverview): page = self.app.get(reverse("domain-senior-official", kwargs={"pk": self.domain.id})) self.assertContains(page, "Testy") + @less_console_noise_decorator def test_domain_edit_senior_official_in_place(self): """When editing a senior official for domain information and SO is not joined to any other objects""" @@ -1120,6 +1131,7 @@ class TestDomainSeniorOfficial(TestDomainOverview): self.assertEqual("Testy2", self.domain_information.senior_official.first_name) self.assertEqual(so_pk, self.domain_information.senior_official.id) + @less_console_noise_decorator def assert_all_form_fields_have_expected_values(self, form, test_cases, test_for_disabled=False): """ Asserts that each specified form field has the expected value and, optionally, checks if the field is disabled. @@ -1146,6 +1158,7 @@ class TestDomainSeniorOfficial(TestDomainOverview): # Test for disabled on each field self.assertTrue("disabled" in form[field_name].attrs) + @less_console_noise_decorator def test_domain_edit_senior_official_federal(self): """Tests that no edit can occur when the underlying domain is federal""" @@ -1202,6 +1215,7 @@ class TestDomainSeniorOfficial(TestDomainOverview): self.assertEqual("CIO", self.domain_information.senior_official.title) self.assertEqual("nobody@igorville.gov", self.domain_information.senior_official.email) + @less_console_noise_decorator def test_domain_edit_senior_official_tribal(self): """Tests that no edit can occur when the underlying domain is tribal""" @@ -1258,6 +1272,7 @@ class TestDomainSeniorOfficial(TestDomainOverview): self.assertEqual("CIO", self.domain_information.senior_official.title) self.assertEqual("nobody@igorville.gov", self.domain_information.senior_official.email) + @less_console_noise_decorator def test_domain_edit_senior_official_creates_new(self): """When editing a senior official for domain information and SO IS joined to another object""" @@ -1295,12 +1310,14 @@ class TestDomainSeniorOfficial(TestDomainOverview): class TestDomainOrganization(TestDomainOverview): + @less_console_noise_decorator def test_domain_org_name_address(self): """Can load domain's org name and mailing address page.""" page = self.client.get(reverse("domain-org-name-address", kwargs={"pk": self.domain.id})) # once on the sidebar, once in the page title, once as H1 self.assertContains(page, "Organization name and mailing address", count=3) + @less_console_noise_decorator def test_domain_org_name_address_content(self): """Org name and address information appears on the page.""" self.domain_information.organization_name = "Town of Igorville" @@ -1308,6 +1325,7 @@ class TestDomainOrganization(TestDomainOverview): page = self.app.get(reverse("domain-org-name-address", kwargs={"pk": self.domain.id})) self.assertContains(page, "Town of Igorville") + @less_console_noise_decorator def test_domain_org_name_address_form(self): """Submitting changes works on the org name address page.""" self.domain_information.organization_name = "Town of Igorville" @@ -1325,6 +1343,7 @@ class TestDomainOrganization(TestDomainOverview): self.assertContains(success_result_page, "Not igorville") self.assertContains(success_result_page, "Faketown") + @less_console_noise_decorator def test_domain_org_name_address_form_tribal(self): """ Submitting a change to organization_name is blocked for tribal domains @@ -1382,6 +1401,7 @@ class TestDomainOrganization(TestDomainOverview): # Check for the value we want to update self.assertContains(success_result_page, "Faketown") + @less_console_noise_decorator def test_domain_org_name_address_form_federal(self): """ Submitting a change to federal_agency is blocked for federal domains @@ -1437,6 +1457,7 @@ class TestDomainOrganization(TestDomainOverview): # Check for the value we want to update self.assertContains(success_result_page, "Faketown") + @less_console_noise_decorator def test_federal_agency_submit_blocked(self): """ Submitting a change to federal_agency is blocked for federal domains @@ -1470,11 +1491,13 @@ class TestDomainOrganization(TestDomainOverview): class TestDomainContactInformation(TestDomainOverview): + @less_console_noise_decorator def test_domain_your_contact_information(self): """Can load domain's your contact information page.""" page = self.client.get(reverse("domain-your-contact-information", kwargs={"pk": self.domain.id})) self.assertContains(page, "Your contact information") + @less_console_noise_decorator def test_domain_your_contact_information_content(self): """Logged-in user's contact information appears on the page.""" self.user.contact.first_name = "Testy" @@ -1602,20 +1625,21 @@ class TestDomainSecurityEmail(TestDomainOverview): self.assertEqual(message.tags, message_tag) self.assertEqual(message.message.strip(), expected_message.strip()) + @less_console_noise_decorator def test_domain_overview_blocked_for_ineligible_user(self): """We could easily duplicate this test for all domain management views, but a single url test should be solid enough since all domain management pages share the same permissions class""" self.user.status = User.RESTRICTED self.user.save() - with less_console_noise(): - response = self.client.get(reverse("domain", kwargs={"pk": self.domain.id})) - self.assertEqual(response.status_code, 403) + response = self.client.get(reverse("domain", kwargs={"pk": self.domain.id})) + self.assertEqual(response.status_code, 403) class TestDomainDNSSEC(TestDomainOverview): """MockEPPLib is already inherited.""" + @less_console_noise_decorator def test_dnssec_page_refreshes_enable_button(self): """DNSSEC overview page loads when domain has no DNSSEC data and shows a 'Enable DNSSEC' button.""" @@ -1623,6 +1647,7 @@ class TestDomainDNSSEC(TestDomainOverview): page = self.client.get(reverse("domain-dns-dnssec", kwargs={"pk": self.domain.id})) self.assertContains(page, "Enable DNSSEC") + @less_console_noise_decorator def test_dnssec_page_loads_with_data_in_domain(self): """DNSSEC overview page loads when domain has DNSSEC data and the template contains a button to disable DNSSEC.""" @@ -1644,6 +1669,7 @@ class TestDomainDNSSEC(TestDomainOverview): self.assertContains(updated_page, "Enable DNSSEC") + @less_console_noise_decorator def test_ds_form_loads_with_no_domain_data(self): """DNSSEC Add DS data page loads when there is no domain DNSSEC data and shows a button to Add new record""" @@ -1652,6 +1678,7 @@ class TestDomainDNSSEC(TestDomainOverview): self.assertContains(page, "You have no DS data added") self.assertContains(page, "Add new record") + @less_console_noise_decorator def test_ds_form_loads_with_ds_data(self): """DNSSEC Add DS data page loads when there is domain DNSSEC DS data and shows the data""" @@ -1659,6 +1686,7 @@ class TestDomainDNSSEC(TestDomainOverview): page = self.client.get(reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id})) self.assertContains(page, "DS data record 1") + @less_console_noise_decorator def test_ds_data_form_modal(self): """When user clicks on save, a modal pops up.""" add_data_page = self.app.get(reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id})) @@ -1677,6 +1705,7 @@ class TestDomainDNSSEC(TestDomainOverview): # Now check to see whether the JS trigger for the modal is present on the page self.assertContains(response, "Trigger Disable DNSSEC Modal") + @less_console_noise_decorator def test_ds_data_form_submits(self): """DS data form submits successfully @@ -1685,8 +1714,7 @@ class TestDomainDNSSEC(TestDomainOverview): add_data_page = self.app.get(reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id})) session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - with less_console_noise(): # swallow log warning message - result = add_data_page.forms[0].submit() + result = add_data_page.forms[0].submit() # form submission was a post, response should be a redirect self.assertEqual(result.status_code, 302) self.assertEqual( @@ -1697,6 +1725,7 @@ class TestDomainDNSSEC(TestDomainOverview): page = result.follow() self.assertContains(page, "The DS data records for this domain have been updated.") + @less_console_noise_decorator def test_ds_data_form_invalid(self): """DS data form errors with invalid data (missing required fields) @@ -1710,8 +1739,7 @@ class TestDomainDNSSEC(TestDomainOverview): add_data_page.forms[0]["form-0-algorithm"] = "" add_data_page.forms[0]["form-0-digest_type"] = "" add_data_page.forms[0]["form-0-digest"] = "" - with less_console_noise(): # swallow logged warning message - result = add_data_page.forms[0].submit() + result = add_data_page.forms[0].submit() # form submission was a post with an error, response should be a 200 # error text appears twice, once at the top of the page, once around # the field. @@ -1720,6 +1748,7 @@ class TestDomainDNSSEC(TestDomainOverview): self.assertContains(result, "Digest type is required", count=2, status_code=200) self.assertContains(result, "Digest is required", count=2, status_code=200) + @less_console_noise_decorator def test_ds_data_form_invalid_keytag(self): """DS data form errors with invalid data (key tag too large) @@ -1734,8 +1763,7 @@ class TestDomainDNSSEC(TestDomainOverview): add_data_page.forms[0]["form-0-algorithm"] = "" add_data_page.forms[0]["form-0-digest_type"] = "" add_data_page.forms[0]["form-0-digest"] = "" - with less_console_noise(): # swallow logged warning message - result = add_data_page.forms[0].submit() + result = add_data_page.forms[0].submit() # form submission was a post with an error, response should be a 200 # error text appears twice, once at the top of the page, once around # the field. @@ -1743,6 +1771,7 @@ class TestDomainDNSSEC(TestDomainOverview): result, str(DsDataError(code=DsDataErrorCodes.INVALID_KEYTAG_SIZE)), count=2, status_code=200 ) + @less_console_noise_decorator def test_ds_data_form_invalid_digest_chars(self): """DS data form errors with invalid data (digest contains non hexadecimal chars) @@ -1757,8 +1786,7 @@ class TestDomainDNSSEC(TestDomainOverview): add_data_page.forms[0]["form-0-algorithm"] = "3" add_data_page.forms[0]["form-0-digest_type"] = "1" add_data_page.forms[0]["form-0-digest"] = "GG1234" - with less_console_noise(): # swallow logged warning message - result = add_data_page.forms[0].submit() + result = add_data_page.forms[0].submit() # form submission was a post with an error, response should be a 200 # error text appears twice, once at the top of the page, once around # the field. @@ -1766,6 +1794,7 @@ class TestDomainDNSSEC(TestDomainOverview): result, str(DsDataError(code=DsDataErrorCodes.INVALID_DIGEST_CHARS)), count=2, status_code=200 ) + @less_console_noise_decorator def test_ds_data_form_invalid_digest_sha1(self): """DS data form errors with invalid data (digest is invalid sha-1) @@ -1780,8 +1809,7 @@ class TestDomainDNSSEC(TestDomainOverview): add_data_page.forms[0]["form-0-algorithm"] = "3" add_data_page.forms[0]["form-0-digest_type"] = "1" # SHA-1 add_data_page.forms[0]["form-0-digest"] = "A123" - with less_console_noise(): # swallow logged warning message - result = add_data_page.forms[0].submit() + result = add_data_page.forms[0].submit() # form submission was a post with an error, response should be a 200 # error text appears twice, once at the top of the page, once around # the field. @@ -1789,6 +1817,7 @@ class TestDomainDNSSEC(TestDomainOverview): result, str(DsDataError(code=DsDataErrorCodes.INVALID_DIGEST_SHA1)), count=2, status_code=200 ) + @less_console_noise_decorator def test_ds_data_form_invalid_digest_sha256(self): """DS data form errors with invalid data (digest is invalid sha-256) @@ -1803,8 +1832,7 @@ class TestDomainDNSSEC(TestDomainOverview): add_data_page.forms[0]["form-0-algorithm"] = "3" add_data_page.forms[0]["form-0-digest_type"] = "2" # SHA-256 add_data_page.forms[0]["form-0-digest"] = "GG1234" - with less_console_noise(): # swallow logged warning message - result = add_data_page.forms[0].submit() + result = add_data_page.forms[0].submit() # form submission was a post with an error, response should be a 200 # error text appears twice, once at the top of the page, once around # the field. From 6c8ec6044b579d04d461989c4ce661c8d3e8b8ea Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 5 Jul 2024 14:08:42 -0400 Subject: [PATCH 021/184] test_views_request --- src/registrar/tests/test_views_request.py | 82 ++++++++++++++++++----- 1 file changed, 67 insertions(+), 15 deletions(-) diff --git a/src/registrar/tests/test_views_request.py b/src/registrar/tests/test_views_request.py index e78dfe860..1b5ba7aeb 100644 --- a/src/registrar/tests/test_views_request.py +++ b/src/registrar/tests/test_views_request.py @@ -3,7 +3,7 @@ from unittest.mock import Mock from django.conf import settings from django.urls import reverse - +from api.tests.common import less_console_noise_decorator from .common import MockSESClient, completed_domain_request # type: ignore from django_webtest import WebTest # type: ignore import boto3_mocking # type: ignore @@ -40,11 +40,13 @@ class DomainRequestTests(TestWithUser, WebTest): self.app.set_user(self.user.username) self.TITLES = DomainRequestWizard.TITLES + @less_console_noise_decorator def test_domain_request_form_intro_acknowledgement(self): """Tests that user is presented with intro acknowledgement page""" intro_page = self.app.get(reverse("domain-request:")) self.assertContains(intro_page, "You’re about to start your .gov domain request") + @less_console_noise_decorator def test_domain_request_form_intro_is_skipped_when_edit_access(self): """Tests that user is NOT presented with intro acknowledgement page when accessed through 'edit'""" domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.STARTED, user=self.user) @@ -55,6 +57,7 @@ class DomainRequestTests(TestWithUser, WebTest): redirect_url = detail_page.url self.assertEqual(redirect_url, "/request/generic_org_type/") + @less_console_noise_decorator def test_domain_request_form_empty_submit(self): """Tests empty submit on the first page after the acknowledgement page""" intro_page = self.app.get(reverse("domain-request:")) @@ -77,31 +80,31 @@ class DomainRequestTests(TestWithUser, WebTest): result = type_page.forms[0].submit() self.assertIn("What kind of U.S.-based government organization do you represent?", result) + @less_console_noise_decorator def test_domain_request_multiple_domain_requests_exist(self): """Test that an info message appears when user has multiple domain requests already""" # create and submit a domain request domain_request = completed_domain_request(user=self.user) mock_client = MockSESClient() with boto3_mocking.clients.handler_for("sesv2", mock_client): - with less_console_noise(): - domain_request.submit() - domain_request.save() + domain_request.submit() + domain_request.save() # now, attempt to create another one - with less_console_noise(): - intro_page = self.app.get(reverse("domain-request:")) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - intro_form = intro_page.forms[0] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - intro_result = intro_form.submit() + intro_page = self.app.get(reverse("domain-request:")) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + intro_form = intro_page.forms[0] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + intro_result = intro_form.submit() - # follow first redirect - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_page = intro_result.follow() - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + # follow first redirect + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_page = intro_result.follow() + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.assertContains(type_page, "You cannot submit this request yet") + self.assertContains(type_page, "You cannot submit this request yet") + @less_console_noise_decorator def test_domain_request_into_acknowledgement_creates_new_request(self): """ We had to solve a bug where the wizard was creating 2 requests on first intro acknowledgement ('continue') @@ -155,6 +158,7 @@ class DomainRequestTests(TestWithUser, WebTest): self.assertEqual(domain_request_count, 2) @boto3_mocking.patching + @less_console_noise_decorator def test_domain_request_form_submission(self): """ Can fill out the entire form and submit. @@ -524,6 +528,7 @@ class DomainRequestTests(TestWithUser, WebTest): self.assertEqual(num_pages, num_pages_tested) @boto3_mocking.patching + @less_console_noise_decorator def test_domain_request_form_submission_incomplete(self): num_pages_tested = 0 # skipping elections, type_of_work, tribal_government @@ -879,6 +884,7 @@ class DomainRequestTests(TestWithUser, WebTest): self.assertEqual(num_pages, num_pages_tested) + @less_console_noise_decorator def test_domain_request_form_conditional_federal(self): """Federal branch question is shown for federal organizations.""" intro_page = self.app.get(reverse("domain-request:")) @@ -934,6 +940,7 @@ class DomainRequestTests(TestWithUser, WebTest): contact_page = federal_result.follow() self.assertContains(contact_page, "Federal agency") + @less_console_noise_decorator def test_domain_request_form_conditional_elections(self): """Election question is shown for other organizations.""" intro_page = self.app.get(reverse("domain-request:")) @@ -988,6 +995,7 @@ class DomainRequestTests(TestWithUser, WebTest): contact_page = election_result.follow() self.assertNotContains(contact_page, "Federal agency") + @less_console_noise_decorator def test_domain_request_form_section_skipping(self): """Can skip forward and back in sections""" intro_page = self.app.get(reverse("domain-request:")) @@ -1025,6 +1033,7 @@ class DomainRequestTests(TestWithUser, WebTest): 0, ) + @less_console_noise_decorator def test_domain_request_form_nonfederal(self): """Non-federal organizations don't have to provide their federal agency.""" intro_page = self.app.get(reverse("domain-request:")) @@ -1069,6 +1078,7 @@ class DomainRequestTests(TestWithUser, WebTest): self.assertEqual(contact_result.status_code, 302) self.assertEqual(contact_result["Location"], "/request/about_your_organization/") + @less_console_noise_decorator def test_domain_request_about_your_organization_special(self): """Special districts have to answer an additional question.""" intro_page = self.app.get(reverse("domain-request:")) @@ -1097,6 +1107,7 @@ class DomainRequestTests(TestWithUser, WebTest): self.assertContains(contact_page, self.TITLES[Step.ABOUT_YOUR_ORGANIZATION]) + @less_console_noise_decorator def test_federal_agency_dropdown_excludes_expected_values(self): """The Federal Agency dropdown on a domain request form should not include options for gov Administration and Non-Federal Agency""" @@ -1144,6 +1155,7 @@ class DomainRequestTests(TestWithUser, WebTest): # make sure correct federal agency options still show up self.assertContains(org_contact_page, "General Services Administration") + @less_console_noise_decorator def test_yes_no_contact_form_inits_blank_for_new_domain_request(self): """On the Other Contacts page, the yes/no form gets initialized with nothing selected for new domain requests""" @@ -1151,6 +1163,7 @@ class DomainRequestTests(TestWithUser, WebTest): other_contacts_form = other_contacts_page.forms[0] self.assertEquals(other_contacts_form["other_contacts-has_other_contacts"].value, None) + @less_console_noise_decorator def test_yes_no_additional_form_inits_blank_for_new_domain_request(self): """On the Additional Details page, the yes/no form gets initialized with nothing selected for new domain requests""" @@ -1163,6 +1176,7 @@ class DomainRequestTests(TestWithUser, WebTest): # Check the anything else yes/no field self.assertEquals(additional_form["additional_details-has_anything_else_text"].value, None) + @less_console_noise_decorator def test_yes_no_form_inits_yes_for_domain_request_with_other_contacts(self): """On the Other Contacts page, the yes/no form gets initialized with YES selected if the domain request has other contacts""" @@ -1183,6 +1197,7 @@ class DomainRequestTests(TestWithUser, WebTest): other_contacts_form = other_contacts_page.forms[0] self.assertEquals(other_contacts_form["other_contacts-has_other_contacts"].value, "True") + @less_console_noise_decorator def test_yes_no_form_inits_yes_for_cisa_representative_and_anything_else(self): """On the Additional Details page, the yes/no form gets initialized with YES selected for both yes/no radios if the domain request has a values for cisa_representative_first_name and @@ -1214,6 +1229,7 @@ class DomainRequestTests(TestWithUser, WebTest): yes_no_anything_else = additional_details_form["additional_details-has_anything_else_text"].value self.assertEquals(yes_no_anything_else, "True") + @less_console_noise_decorator def test_yes_no_form_inits_no_for_domain_request_with_no_other_contacts_rationale(self): """On the Other Contacts page, the yes/no form gets initialized with NO selected if the domain request has no other contacts""" @@ -1236,6 +1252,7 @@ class DomainRequestTests(TestWithUser, WebTest): other_contacts_form = other_contacts_page.forms[0] self.assertEquals(other_contacts_form["other_contacts-has_other_contacts"].value, "False") + @less_console_noise_decorator def test_yes_no_form_for_domain_request_with_no_cisa_representative_and_anything_else(self): """On the Additional details page, the form preselects "no" when has_cisa_representative and anything_else is no""" @@ -1271,6 +1288,7 @@ class DomainRequestTests(TestWithUser, WebTest): yes_no_anything_else = additional_details_form["additional_details-has_anything_else_text"].value self.assertEquals(yes_no_anything_else, "False") + @less_console_noise_decorator def test_submitting_additional_details_deletes_cisa_representative_and_anything_else(self): """When a user submits the Additional Details form with no selected for all fields, the domain request's data gets wiped when submitted""" @@ -1332,6 +1350,7 @@ class DomainRequestTests(TestWithUser, WebTest): self.assertEqual(domain_request.cisa_representative_last_name, None) self.assertEqual(domain_request.cisa_representative_email, None) + @less_console_noise_decorator def test_submitting_additional_details_populates_cisa_representative_and_anything_else(self): """When a user submits the Additional Details form, the domain request's data gets submitted""" @@ -1385,6 +1404,7 @@ class DomainRequestTests(TestWithUser, WebTest): self.assertEqual(domain_request.has_cisa_representative, True) self.assertEqual(domain_request.has_anything_else_text, True) + @less_console_noise_decorator def test_if_cisa_representative_yes_no_form_is_yes_then_field_is_required(self): """Applicants with a cisa representative must provide a value""" domain_request = completed_domain_request( @@ -1417,6 +1437,7 @@ class DomainRequestTests(TestWithUser, WebTest): self.assertContains(response, "Enter the first name / given name of the CISA regional representative.") self.assertContains(response, "Enter the last name / family name of the CISA regional representative.") + @less_console_noise_decorator def test_if_anything_else_yes_no_form_is_yes_then_field_is_required(self): """Applicants with a anything else must provide a value""" domain_request = completed_domain_request(name="cisareps.gov", user=self.user, has_anything_else=False) @@ -1447,6 +1468,7 @@ class DomainRequestTests(TestWithUser, WebTest): expected_message = "Provide additional details you’d like us to know. If you have nothing to add, select “No.”" self.assertContains(response, expected_message) + @less_console_noise_decorator def test_additional_details_form_fields_required(self): """When a user submits the Additional Details form without checking the has_cisa_representative and has_anything_else_text fields, the form should deny this action""" @@ -1480,6 +1502,7 @@ class DomainRequestTests(TestWithUser, WebTest): # due to screen reader information / html. self.assertContains(response, "This question is required.", count=4) + @less_console_noise_decorator def test_submitting_other_contacts_deletes_no_other_contacts_rationale(self): """When a user submits the Other Contacts form with other contacts selected, the domain request's no other contacts rationale gets deleted""" @@ -1528,6 +1551,7 @@ class DomainRequestTests(TestWithUser, WebTest): None, ) + @less_console_noise_decorator def test_submitting_no_other_contacts_rationale_deletes_other_contacts(self): """When a user submits the Other Contacts form with no other contacts selected, the domain request's other contacts get deleted for other contacts that exist and are not joined to other objects @@ -1570,6 +1594,7 @@ class DomainRequestTests(TestWithUser, WebTest): "Hello again!", ) + @less_console_noise_decorator def test_submitting_no_other_contacts_rationale_removes_reference_other_contacts_when_joined(self): """When a user submits the Other Contacts form with no other contacts selected, the domain request's other contacts references get removed for other contacts that exist and are joined to other objects""" @@ -1665,6 +1690,7 @@ class DomainRequestTests(TestWithUser, WebTest): "Hello again!", ) + @less_console_noise_decorator def test_if_yes_no_form_is_no_then_no_other_contacts_required(self): """Applicants with no other contacts have to give a reason.""" other_contacts_page = self.app.get(reverse("domain-request:other_contacts")) @@ -1680,6 +1706,7 @@ class DomainRequestTests(TestWithUser, WebTest): # Assert that it is not returned, ie the contacts form is not required self.assertNotContains(response, "Enter the first name / given name of this contact.") + @less_console_noise_decorator def test_if_yes_no_form_is_yes_then_other_contacts_required(self): """Applicants with other contacts do not have to give a reason.""" other_contacts_page = self.app.get(reverse("domain-request:other_contacts")) @@ -1695,6 +1722,7 @@ class DomainRequestTests(TestWithUser, WebTest): # Assert that it is returned, ie the contacts form is required self.assertContains(response, "Enter the first name / given name of this contact.") + @less_console_noise_decorator def test_delete_other_contact(self): """Other contacts can be deleted after being saved to database. @@ -1779,6 +1807,7 @@ class DomainRequestTests(TestWithUser, WebTest): self.assertEqual(domain_request.other_contacts.count(), 1) self.assertEqual(domain_request.other_contacts.first().first_name, "Testy3") + @less_console_noise_decorator def test_delete_other_contact_does_not_allow_zero_contacts(self): """Delete Other Contact does not allow submission with zero contacts.""" # Populate the database with a domain request that @@ -1851,6 +1880,7 @@ class DomainRequestTests(TestWithUser, WebTest): self.assertEqual(domain_request.other_contacts.count(), 1) self.assertEqual(domain_request.other_contacts.first().first_name, "Testy2") + @less_console_noise_decorator def test_delete_other_contact_sets_visible_empty_form_as_required_after_failed_submit(self): """When you: 1. add an empty contact, @@ -1928,6 +1958,7 @@ class DomainRequestTests(TestWithUser, WebTest): # Enter the first name ... self.assertContains(response, "Enter the first name / given name of this contact.") + @less_console_noise_decorator def test_edit_other_contact_in_place(self): """When you: 1. edit an existing contact which is not joined to another model, @@ -2009,6 +2040,7 @@ class DomainRequestTests(TestWithUser, WebTest): self.assertEquals(other_contact_pk, other_contact.id) self.assertEquals("Testy3", other_contact.first_name) + @less_console_noise_decorator def test_edit_other_contact_creates_new(self): """When you: 1. edit an existing contact which IS joined to another model, @@ -2089,6 +2121,7 @@ class DomainRequestTests(TestWithUser, WebTest): senior_official = domain_request.senior_official self.assertEquals("Testy", senior_official.first_name) + @less_console_noise_decorator def test_edit_senior_official_in_place(self): """When you: 1. edit a senior official which is not joined to another model, @@ -2154,6 +2187,7 @@ class DomainRequestTests(TestWithUser, WebTest): self.assertEquals(so_pk, updated_so.id) self.assertEquals("Testy2", updated_so.first_name) + @less_console_noise_decorator def test_edit_senior_official_creates_new(self): """When you: 1. edit an existing senior official which IS joined to another model, @@ -2226,6 +2260,7 @@ class DomainRequestTests(TestWithUser, WebTest): senior_official = domain_request.senior_official self.assertEquals("Testy2", senior_official.first_name) + @less_console_noise_decorator def test_edit_submitter_in_place(self): """When you: 1. edit a submitter (your contact) which is not joined to another model, @@ -2290,6 +2325,7 @@ class DomainRequestTests(TestWithUser, WebTest): self.assertEquals(submitter_pk, updated_submitter.id) self.assertEquals("Testy2", updated_submitter.first_name) + @less_console_noise_decorator def test_edit_submitter_creates_new(self): """When you: 1. edit an existing your contact which IS joined to another model, @@ -2362,6 +2398,7 @@ class DomainRequestTests(TestWithUser, WebTest): submitter = domain_request.submitter self.assertEquals("Testy2", submitter.first_name) + @less_console_noise_decorator def test_domain_request_about_your_organiztion_interstate(self): """Special districts have to answer an additional question.""" intro_page = self.app.get(reverse("domain-request:")) @@ -2390,6 +2427,7 @@ class DomainRequestTests(TestWithUser, WebTest): self.assertContains(contact_page, self.TITLES[Step.ABOUT_YOUR_ORGANIZATION]) + @less_console_noise_decorator def test_domain_request_tribal_government(self): """Tribal organizations have to answer an additional question.""" intro_page = self.app.get(reverse("domain-request:")) @@ -2421,6 +2459,7 @@ class DomainRequestTests(TestWithUser, WebTest): # and the step is on the sidebar list. self.assertContains(tribal_government_page, self.TITLES[Step.TRIBAL_GOVERNMENT]) + @less_console_noise_decorator def test_domain_request_so_dynamic_text(self): intro_page = self.app.get(reverse("domain-request:")) # django-webtest does not handle cookie-based sessions well because it keeps @@ -2493,6 +2532,7 @@ class DomainRequestTests(TestWithUser, WebTest): so_page = election_page.click(str(self.TITLES["senior_official"]), index=0) self.assertContains(so_page, "Domain requests from cities") + @less_console_noise_decorator def test_domain_request_dotgov_domain_dynamic_text(self): intro_page = self.app.get(reverse("domain-request:")) # django-webtest does not handle cookie-based sessions well because it keeps @@ -2595,6 +2635,7 @@ class DomainRequestTests(TestWithUser, WebTest): self.assertContains(dotgov_page, "CityofEudoraKS.gov") self.assertNotContains(dotgov_page, "medicare.gov") + @less_console_noise_decorator def test_domain_request_formsets(self): """Users are able to add more than one of some fields.""" current_sites_page = self.app.get(reverse("domain-request:current_sites")) @@ -2749,6 +2790,7 @@ class DomainRequestTests(TestWithUser, WebTest): # page = self.app.get(url) # self.assertNotContains(page, "VALUE") + @less_console_noise_decorator def test_long_org_name_in_domain_request(self): """ Make sure the long name is displaying in the domain request form, @@ -2771,6 +2813,7 @@ class DomainRequestTests(TestWithUser, WebTest): self.assertContains(type_page, "Federal: an agency of the U.S. government") + @less_console_noise_decorator def test_submit_modal_no_domain_text_fallback(self): """When user clicks on submit your domain request and the requested domain is null (possible through url direct access to the review page), present @@ -2790,6 +2833,7 @@ class DomainRequestTestDifferentStatuses(TestWithUser, WebTest): self.app.set_user(self.user.username) self.client.force_login(self.user) + @less_console_noise_decorator def test_domain_request_status(self): """Checking domain request status page""" domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.SUBMITTED, user=self.user) @@ -2803,6 +2847,7 @@ class DomainRequestTestDifferentStatuses(TestWithUser, WebTest): self.assertContains(detail_page, "Admin Tester") self.assertContains(detail_page, "Status:") + @less_console_noise_decorator def test_domain_request_status_with_ineligible_user(self): """Checking domain request status page whith a blocked user. The user should still have access to view.""" @@ -2819,6 +2864,7 @@ class DomainRequestTestDifferentStatuses(TestWithUser, WebTest): self.assertContains(detail_page, "Admin Tester") self.assertContains(detail_page, "Status:") + @less_console_noise_decorator def test_domain_request_withdraw(self): """Checking domain request status page""" domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.SUBMITTED, user=self.user) @@ -2849,6 +2895,7 @@ class DomainRequestTestDifferentStatuses(TestWithUser, WebTest): response = self.client.get("/get-domain-requests-json/") self.assertContains(response, "Withdrawn") + @less_console_noise_decorator def test_domain_request_withdraw_no_permissions(self): """Can't withdraw domain requests as a restricted user.""" self.user.status = User.RESTRICTED @@ -2873,6 +2920,7 @@ class DomainRequestTestDifferentStatuses(TestWithUser, WebTest): page = self.client.get(reverse(url_name, kwargs={"pk": domain_request.pk})) self.assertEqual(page.status_code, 403) + @less_console_noise_decorator def test_domain_request_status_no_permissions(self): """Can't access domain requests without being the creator.""" domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.SUBMITTED, user=self.user) @@ -2892,6 +2940,7 @@ class DomainRequestTestDifferentStatuses(TestWithUser, WebTest): page = self.client.get(reverse(url_name, kwargs={"pk": domain_request.pk})) self.assertEqual(page.status_code, 403) + @less_console_noise_decorator def test_approved_domain_request_not_in_active_requests(self): """An approved domain request is not shown in the Active Requests table on home.html.""" @@ -2917,12 +2966,14 @@ class TestWizardUnlockingSteps(TestWithUser, WebTest): def tearDown(self): super().tearDown() + @less_console_noise_decorator def test_unlocked_steps_empty_domain_request(self): """Test when all fields in the domain request are empty.""" unlocked_steps = self.wizard.db_check_for_unlocking_steps() expected_dict = [] self.assertEqual(unlocked_steps, expected_dict) + @less_console_noise_decorator def test_unlocked_steps_full_domain_request(self): """Test when all fields in the domain request are filled.""" @@ -2959,6 +3010,7 @@ class TestWizardUnlockingSteps(TestWithUser, WebTest): else: self.fail(f"Expected a redirect, but got a different response: {response}") + @less_console_noise_decorator def test_unlocked_steps_partial_domain_request(self): """Test when some fields in the domain request are filled.""" From 4e00124fdd34673ab233d99d27e1f015afd96e85 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 5 Jul 2024 14:25:03 -0400 Subject: [PATCH 022/184] epplibwrapper test_client --- src/epplibwrapper/tests/test_client.py | 155 +++++++++++++------------ 1 file changed, 80 insertions(+), 75 deletions(-) diff --git a/src/epplibwrapper/tests/test_client.py b/src/epplibwrapper/tests/test_client.py index d30ec4865..6c3c2a2f6 100644 --- a/src/epplibwrapper/tests/test_client.py +++ b/src/epplibwrapper/tests/test_client.py @@ -26,7 +26,8 @@ class TestClient(TestCase): def fake_result(self, code, msg): """Helper function to create a fake Result object""" - return Result(code=code, msg=msg, res_data=[], cl_tr_id="cl_tr_id", sv_tr_id="sv_tr_id") + with less_console_noise(): + return Result(code=code, msg=msg, res_data=[], cl_tr_id="cl_tr_id", sv_tr_id="sv_tr_id") @patch("epplibwrapper.client.Client") def test_initialize_client_success(self, mock_client): @@ -268,8 +269,9 @@ class TestClient(TestCase): Raises a ConcurrentObjectUseError, which gevent throws when accessing the same thread from two different locations. """ - # This error is thrown when two threads are being used concurrently - raise ConcurrentObjectUseError("This socket is already used by another greenlet") + with less_console_noise(): + # This error is thrown when two threads are being used concurrently + raise ConcurrentObjectUseError("This socket is already used by another greenlet") def do_nothing(self, command=None): """ @@ -281,62 +283,65 @@ class TestClient(TestCase): """ Simulates receiving a success response from EPP. """ - mock = MagicMock( - code=1000, - msg="Command completed successfully", - res_data=None, - cl_tr_id="xkw1uo#2023-10-17T15:29:09.559376", - sv_tr_id="5CcH4gxISuGkq8eqvr1UyQ==-35a", - extensions=[], - msg_q=None, - ) - return mock + with less_console_noise(): + mock = MagicMock( + code=1000, + msg="Command completed successfully", + res_data=None, + cl_tr_id="xkw1uo#2023-10-17T15:29:09.559376", + sv_tr_id="5CcH4gxISuGkq8eqvr1UyQ==-35a", + extensions=[], + msg_q=None, + ) + return mock def fake_info_domain_received(self, command=None, cleaned=None): """ Simulates receiving a response by reading from a predefined XML file. """ - location = Path(__file__).parent / "utility" / "infoDomain.xml" - xml = (location).read_bytes() - return xml + with less_console_noise(): + location = Path(__file__).parent / "utility" / "infoDomain.xml" + xml = (location).read_bytes() + return xml def get_fake_epp_result(self): """Mimics a return from EPP by returning a dictionary in the same format""" - result = { - "cl_tr_id": None, - "code": 1000, - "extensions": [], - "msg": "Command completed successfully", - "msg_q": None, - "res_data": [ - info.InfoDomainResultData( - roid="DF1340360-GOV", - statuses=[ - common.Status( - state="serverTransferProhibited", - description=None, - lang="en", - ), - common.Status(state="inactive", description=None, lang="en"), - ], - cl_id="gov2023-ote", - cr_id="gov2023-ote", - cr_date=datetime.datetime(2023, 8, 15, 23, 56, 36, tzinfo=tzlocal()), - up_id="gov2023-ote", - up_date=datetime.datetime(2023, 8, 17, 2, 3, 19, tzinfo=tzlocal()), - tr_date=None, - name="test3.gov", - registrant="TuaWnx9hnm84GCSU", - admins=[], - nsset=None, - keyset=None, - ex_date=datetime.date(2024, 8, 15), - auth_info=info.DomainAuthInfo(pw="2fooBAR123fooBaz"), - ) - ], - "sv_tr_id": "wRRNVhKhQW2m6wsUHbo/lA==-29a", - } - return result + with less_console_noise(): + result = { + "cl_tr_id": None, + "code": 1000, + "extensions": [], + "msg": "Command completed successfully", + "msg_q": None, + "res_data": [ + info.InfoDomainResultData( + roid="DF1340360-GOV", + statuses=[ + common.Status( + state="serverTransferProhibited", + description=None, + lang="en", + ), + common.Status(state="inactive", description=None, lang="en"), + ], + cl_id="gov2023-ote", + cr_id="gov2023-ote", + cr_date=datetime.datetime(2023, 8, 15, 23, 56, 36, tzinfo=tzlocal()), + up_id="gov2023-ote", + up_date=datetime.datetime(2023, 8, 17, 2, 3, 19, tzinfo=tzlocal()), + tr_date=None, + name="test3.gov", + registrant="TuaWnx9hnm84GCSU", + admins=[], + nsset=None, + keyset=None, + ex_date=datetime.date(2024, 8, 15), + auth_info=info.DomainAuthInfo(pw="2fooBAR123fooBaz"), + ) + ], + "sv_tr_id": "wRRNVhKhQW2m6wsUHbo/lA==-29a", + } + return result def test_send_command_close_failure_recovers(self): """ @@ -350,28 +355,28 @@ class TestClient(TestCase): - Subsequently, the client re-initializes the connection. - A retry of the command execution post-reinitialization succeeds. """ + with less_console_noise(): + expected_result = self.get_fake_epp_result() + wrapper = None + # Trigger a retry + # Do nothing on connect, as we aren't testing it and want to connect while + # mimicking the rest of the client as closely as possible (which is not entirely possible with MagicMock) + with patch.object(EPPLibWrapper, "_connect", self.do_nothing): + with patch.object(SocketTransport, "send", self.fake_failure_send_concurrent_threads): + wrapper = EPPLibWrapper() + tested_command = commands.InfoDomain(name="test.gov") + try: + wrapper.send(tested_command, cleaned=True) + except RegistryError as err: + expected_error = "InfoDomain failed to execute due to an unknown error." + self.assertEqual(err.args[0], expected_error) + else: + self.fail("Registry error was not thrown") - expected_result = self.get_fake_epp_result() - wrapper = None - # Trigger a retry - # Do nothing on connect, as we aren't testing it and want to connect while - # mimicking the rest of the client as closely as possible (which is not entirely possible with MagicMock) - with patch.object(EPPLibWrapper, "_connect", self.do_nothing): - with patch.object(SocketTransport, "send", self.fake_failure_send_concurrent_threads): - wrapper = EPPLibWrapper() - tested_command = commands.InfoDomain(name="test.gov") - try: - wrapper.send(tested_command, cleaned=True) - except RegistryError as err: - expected_error = "InfoDomain failed to execute due to an unknown error." - self.assertEqual(err.args[0], expected_error) - else: - self.fail("Registry error was not thrown") - - # After a retry, try sending again to see if the connection recovers - with patch.object(EPPLibWrapper, "_connect", self.do_nothing): - with patch.object(SocketTransport, "send", self.fake_success_send), patch.object( - SocketTransport, "receive", self.fake_info_domain_received - ): - result = wrapper.send(tested_command, cleaned=True) - self.assertEqual(expected_result, result.__dict__) + # After a retry, try sending again to see if the connection recovers + with patch.object(EPPLibWrapper, "_connect", self.do_nothing): + with patch.object(SocketTransport, "send", self.fake_success_send), patch.object( + SocketTransport, "receive", self.fake_info_domain_received + ): + result = wrapper.send(tested_command, cleaned=True) + self.assertEqual(expected_result, result.__dict__) From a1fca00b8fa2163da37911b68e853424b3fd8799 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 5 Jul 2024 15:28:33 -0400 Subject: [PATCH 023/184] wip --- src/registrar/assets/js/get-gov.js | 4 + src/registrar/assets/sass/_theme/_base.scss | 84 +++++-------- src/registrar/assets/sass/_theme/_header.scss | 115 ++++++++++++++++++ src/registrar/assets/sass/_theme/styles.scss | 1 + src/registrar/config/settings.py | 4 + src/registrar/context_processors.py | 29 ++++- src/registrar/models/user.py | 10 ++ src/registrar/registrar_middleware.py | 20 +-- src/registrar/templates/base.html | 50 ++------ src/registrar/templates/dashboard_base.html | 1 + src/registrar/templates/home.html | 26 +--- .../includes/domain_requests_table.html | 3 + .../templates/includes/domains_table.html | 6 +- .../templates/includes/header_basic.html | 41 +++++++ .../templates/includes/header_extended.html | 74 +++++++++++ src/registrar/templates/portfolio.html | 24 ---- src/registrar/templates/portfolio_base.html | 41 +++++++ .../templates/portfolio_domains.html | 4 +- .../templates/portfolio_requests.html | 6 +- .../templates/portfolio_sidebar.html | 37 ------ src/registrar/views/domain.py | 7 -- src/registrar/views/domain_request.py | 23 +--- src/registrar/views/domains_json.py | 3 +- src/registrar/views/index.py | 2 - src/registrar/views/portfolios.py | 17 --- src/registrar/views/user_profile.py | 6 +- src/registrar/views/utility/error_views.py | 4 - 27 files changed, 392 insertions(+), 250 deletions(-) create mode 100644 src/registrar/assets/sass/_theme/_header.scss create mode 100644 src/registrar/templates/includes/header_basic.html create mode 100644 src/registrar/templates/includes/header_extended.html delete mode 100644 src/registrar/templates/portfolio.html create mode 100644 src/registrar/templates/portfolio_base.html delete mode 100644 src/registrar/templates/portfolio_sidebar.html diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js index 7052d786f..fe2946867 100644 --- a/src/registrar/assets/js/get-gov.js +++ b/src/registrar/assets/js/get-gov.js @@ -1173,6 +1173,7 @@ document.addEventListener('DOMContentLoaded', function() { const expirationDateFormatted = expirationDate ? expirationDate.toLocaleDateString('en-US', options) : ''; const expirationDateSortValue = expirationDate ? expirationDate.getTime() : ''; const actionUrl = domain.action_url; + const suborganization = domain.suborganization ? domain.suborganization : ''; const row = document.createElement('tr'); row.innerHTML = ` @@ -1195,6 +1196,9 @@ document.addEventListener('DOMContentLoaded', function() { + + ${suborganization} +
{% endblock %} - {% block banner %} -
- -
- {% endblock banner %} +
+ {% block header %} + {% if not is_org_user %} + {% include "includes/header_basic.html" %} + {% else %} + {% include "includes/header_extended.html" %} + {% endif %} + {% endblock header %} {% block wrapper %}
diff --git a/src/registrar/templates/dashboard_base.html b/src/registrar/templates/dashboard_base.html index 6dd2ce8fd..4f24b9280 100644 --- a/src/registrar/templates/dashboard_base.html +++ b/src/registrar/templates/dashboard_base.html @@ -9,6 +9,7 @@ {% block content %} {% block messages %} {% if messages %} +

test

    {% for message in messages %}
  • diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index a5ed4c86c..4518db271 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -4,17 +4,21 @@ {% block title %} Home | {% endblock %} +{% comment %} + home and portfolio_base test for is_authenticated +{% endcomment %} + {% block content %}
    {% if user.is_authenticated %} {# the entire logged in page goes here #} {% block homepage_content %} -
    {% block messages %} {% include "includes/form_messages.html" %} {% endblock %} +

    Manage your domains

    {% comment %} @@ -32,26 +36,8 @@ {% include "includes/domains_table.html" %} {% include "includes/domain_requests_table.html" %} - {# Note: Reimplement this after MVP #} - - - - - +
    {% endblock %} -
{% else %} {# not user.is_authenticated #} {# the entire logged out page goes here #} diff --git a/src/registrar/templates/includes/domain_requests_table.html b/src/registrar/templates/includes/domain_requests_table.html index 4f091ecf6..ad91699ef 100644 --- a/src/registrar/templates/includes/domain_requests_table.html +++ b/src/registrar/templates/includes/domain_requests_table.html @@ -12,6 +12,9 @@
{% if portfolio %}
- Filter by + Filter by
+
+ {% block usa_nav %} + + {% block usa_nav_secondary %}{% endblock %} + {% endblock %} +
+ \ No newline at end of file diff --git a/src/registrar/templates/includes/header_extended.html b/src/registrar/templates/includes/header_extended.html new file mode 100644 index 000000000..96a77afe5 --- /dev/null +++ b/src/registrar/templates/includes/header_extended.html @@ -0,0 +1,74 @@ +{% load static %} + +
+
+ {% block logo %} + {% include "includes/gov_extended_logo.html" with logo_clickable=True %} + {% endblock %} + +
+ {% block usa_nav %} + + {% endblock %} +
\ No newline at end of file diff --git a/src/registrar/templates/portfolio.html b/src/registrar/templates/portfolio.html deleted file mode 100644 index 4f37c0175..000000000 --- a/src/registrar/templates/portfolio.html +++ /dev/null @@ -1,24 +0,0 @@ -{% extends 'home.html' %} - -{% load static %} - -{% block homepage_content %} - -
-
-
- {% include "portfolio_sidebar.html" with portfolio=portfolio %} -
-
- {% block messages %} - {% include "includes/form_messages.html" %} - {% endblock %} - {# Note: Reimplement commented out functionality #} - - {% block portfolio_content %} - {% endblock %} - -
-
- -{% endblock %} diff --git a/src/registrar/templates/portfolio_base.html b/src/registrar/templates/portfolio_base.html new file mode 100644 index 000000000..4cb8145f8 --- /dev/null +++ b/src/registrar/templates/portfolio_base.html @@ -0,0 +1,41 @@ +{% extends "base.html" %} + +{% comment %} + home and portfolio_base test for is_authenticated +{% endcomment %} + +{% block wrapper %} +
+ {% block content %} + +
+ {% if user.is_authenticated %} + {# the entire logged in page goes here #} + +
+ {% block messages %} + {% include "includes/form_messages.html" %} + {% endblock %} + + {% block portfolio_content %}{% endblock %} + +
+ {% else %} {# not user.is_authenticated #} + {# the entire logged out page goes here #} + +

+ Sign in +

+ + {% endif %} +
+ + {% endblock %} + +
{% block complementary %}{% endblock %}
+ + {% block content_bottom %}{% endblock %} +
+ + +{% endblock wrapper %} diff --git a/src/registrar/templates/portfolio_domains.html b/src/registrar/templates/portfolio_domains.html index 4b5e1148b..ede7886e6 100644 --- a/src/registrar/templates/portfolio_domains.html +++ b/src/registrar/templates/portfolio_domains.html @@ -1,7 +1,9 @@ -{% extends 'portfolio.html' %} +{% extends 'portfolio_base.html' %} {% load static %} +{% block title %} Domains | {% endblock %} + {% block portfolio_content %}

Domains

{% include "includes/domains_table.html" with portfolio=portfolio %} diff --git a/src/registrar/templates/portfolio_requests.html b/src/registrar/templates/portfolio_requests.html index 8d254a3e2..8c698ec83 100644 --- a/src/registrar/templates/portfolio_requests.html +++ b/src/registrar/templates/portfolio_requests.html @@ -1,7 +1,9 @@ -{% extends 'portfolio.html' %} +{% extends 'portfolio_base.html' %} {% load static %} +{% block title %} Domain requests | {% endblock %} + {% block portfolio_content %}

Domain requests

@@ -16,6 +18,6 @@ Start a new domain request

- + {% include "includes/domain_requests_table.html" with portfolio=portfolio %} {% endblock %} diff --git a/src/registrar/templates/portfolio_sidebar.html b/src/registrar/templates/portfolio_sidebar.html deleted file mode 100644 index d02f5ac0c..000000000 --- a/src/registrar/templates/portfolio_sidebar.html +++ /dev/null @@ -1,37 +0,0 @@ -{% load static url_helpers %} - -
- -
diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 2414eba2c..6d30d9c77 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -102,13 +102,6 @@ class DomainBaseView(DomainPermissionView): domain_pk = "domain:" + str(self.kwargs.get("pk")) self.session[domain_pk] = self.object - def get_context_data(self, **kwargs): - """Extend get_context_data to add has_profile_feature_flag to context""" - context = super().get_context_data(**kwargs) - # This is a django waffle flag which toggles features based off of the "flag" table - context["has_profile_feature_flag"] = flag_is_active(self.request, "profile_feature") - return context - class DomainFormBaseView(DomainBaseView, FormMixin): """ diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py index e8e82500e..40e7d7ed1 100644 --- a/src/registrar/views/domain_request.py +++ b/src/registrar/views/domain_request.py @@ -228,10 +228,8 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): if request.path_info == self.NEW_URL_NAME: # Clear context so the prop getter won't create a request here. # Creating a request will be handled in the post method for the - # intro page. Only TEMPORARY context needed is has_profile_flag - has_profile_flag = flag_is_active(self.request, "profile_feature") - context_stuff = {"has_profile_feature_flag": has_profile_flag} - return render(request, "domain_request_intro.html", context=context_stuff) + # intro page. + return render(request, "domain_request_intro.html", {}) else: return self.goto(self.steps.first) @@ -397,8 +395,6 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): "modal_description": "Once you submit this request, you won’t be able to edit it until we review it.\ You’ll only be able to withdraw your request.", "review_form_is_complete": True, - # Use the profile waffle feature flag to toggle profile features throughout domain requests - "has_profile_feature_flag": has_profile_flag, "user": self.request.user, } else: # form is not complete @@ -414,7 +410,6 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): "modal_description": 'This request cannot be submitted yet.\ Return to the request and visit the steps that are marked as "incomplete."', "review_form_is_complete": False, - "has_profile_feature_flag": has_profile_flag, "user": self.request.user, } return context_stuff @@ -740,13 +735,6 @@ class Finished(DomainRequestWizard): class DomainRequestStatus(DomainRequestPermissionView): template_name = "domain_request_status.html" - def get_context_data(self, **kwargs): - """Extend get_context_data to add has_profile_feature_flag to context""" - context = super().get_context_data(**kwargs) - # This is a django waffle flag which toggles features based off of the "flag" table - context["has_profile_feature_flag"] = flag_is_active(self.request, "profile_feature") - return context - class DomainRequestWithdrawConfirmation(DomainRequestPermissionWithdrawView): """This page will ask user to confirm if they want to withdraw @@ -757,13 +745,6 @@ class DomainRequestWithdrawConfirmation(DomainRequestPermissionWithdrawView): template_name = "domain_request_withdraw_confirmation.html" - def get_context_data(self, **kwargs): - """Extend get_context_data to add has_profile_feature_flag to context""" - context = super().get_context_data(**kwargs) - # This is a django waffle flag which toggles features based off of the "flag" table - context["has_profile_feature_flag"] = flag_is_active(self.request, "profile_feature") - return context - class DomainRequestWithdrawn(DomainRequestPermissionWithdrawView): # this view renders no template diff --git a/src/registrar/views/domains_json.py b/src/registrar/views/domains_json.py index 8b107ca67..f17185ef2 100644 --- a/src/registrar/views/domains_json.py +++ b/src/registrar/views/domains_json.py @@ -11,7 +11,7 @@ def get_domains_json(request): """Given the current request, get all domains that are associated with the UserDomainRole object""" - user_domain_roles = UserDomainRole.objects.filter(user=request.user) + user_domain_roles = UserDomainRole.objects.filter(user=request.user).select_related('domain_info__sub_organization') domain_ids = user_domain_roles.values_list("domain_id", flat=True) objects = Domain.objects.filter(id__in=domain_ids) @@ -85,6 +85,7 @@ def get_domains_json(request): "action_url": reverse("domain", kwargs={"pk": domain.id}), "action_label": ("View" if domain.state in [Domain.State.DELETED, Domain.State.ON_HOLD] else "Manage"), "svg_icon": ("visibility" if domain.state in [Domain.State.DELETED, Domain.State.ON_HOLD] else "settings"), + "suborganization": domain.domain_info.sub_organization.name if domain.domain_info and domain.domain_info.sub_organization else None, } for domain in page_obj.object_list ] diff --git a/src/registrar/views/index.py b/src/registrar/views/index.py index f975b9803..a2752168f 100644 --- a/src/registrar/views/index.py +++ b/src/registrar/views/index.py @@ -7,8 +7,6 @@ def index(request): context = {} if request.user.is_authenticated: - # This is a django waffle flag which toggles features based off of the "flag" table - context["has_profile_feature_flag"] = flag_is_active(request, "profile_feature") context["has_organization_feature_flag"] = flag_is_active(request, "organization_feature") # This controls the creation of a new domain request in the wizard diff --git a/src/registrar/views/portfolios.py b/src/registrar/views/portfolios.py index 5ecd5d1d0..1ec40580f 100644 --- a/src/registrar/views/portfolios.py +++ b/src/registrar/views/portfolios.py @@ -8,15 +8,6 @@ from django.contrib.auth.decorators import login_required def portfolio_domains(request, portfolio_id): context = {} - if request.user.is_authenticated: - # This is a django waffle flag which toggles features based off of the "flag" table - context["has_profile_feature_flag"] = flag_is_active(request, "profile_feature") - context["has_organization_feature_flag"] = flag_is_active(request, "organization_feature") - - # Retrieve the portfolio object based on the provided portfolio_id - portfolio = get_object_or_404(Portfolio, id=portfolio_id) - context["portfolio"] = portfolio - return render(request, "portfolio_domains.html", context) @@ -25,14 +16,6 @@ def portfolio_domain_requests(request, portfolio_id): context = {} if request.user.is_authenticated: - # This is a django waffle flag which toggles features based off of the "flag" table - context["has_profile_feature_flag"] = flag_is_active(request, "profile_feature") - context["has_organization_feature_flag"] = flag_is_active(request, "organization_feature") - - # Retrieve the portfolio object based on the provided portfolio_id - portfolio = get_object_or_404(Portfolio, id=portfolio_id) - context["portfolio"] = portfolio - # This controls the creation of a new domain request in the wizard request.session["new_request"] = True diff --git a/src/registrar/views/user_profile.py b/src/registrar/views/user_profile.py index 3f9aeb79f..e6909b3e9 100644 --- a/src/registrar/views/user_profile.py +++ b/src/registrar/views/user_profile.py @@ -55,10 +55,8 @@ class UserProfileView(UserProfilePermissionView, FormMixin): return super().dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): - """Extend get_context_data to include has_profile_feature_flag""" + """Extend get_context_data""" context = super().get_context_data(**kwargs) - # This is a django waffle flag which toggles features based off of the "flag" table - context["has_profile_feature_flag"] = flag_is_active(self.request, "profile_feature") # Set the profile_back_button_text based on the redirect parameter if kwargs.get("redirect") == "domain-request:": @@ -139,7 +137,7 @@ class FinishProfileSetupView(UserProfileView): base_view_name = "finish-user-profile-setup" def get_context_data(self, **kwargs): - """Extend get_context_data to include has_profile_feature_flag""" + """Extend get_context_data""" context = super().get_context_data(**kwargs) # Show back button conditional on user having finished setup diff --git a/src/registrar/views/utility/error_views.py b/src/registrar/views/utility/error_views.py index 2374277d5..3e69d307d 100644 --- a/src/registrar/views/utility/error_views.py +++ b/src/registrar/views/utility/error_views.py @@ -14,14 +14,12 @@ Rather than dealing with that, we keep everything centralized in one location. """ from django.shortcuts import render -from waffle.decorators import flag_is_active def custom_500_error_view(request, context=None): """Used to redirect 500 errors to a custom view""" if context is None: context = {} - context["has_profile_feature_flag"] = flag_is_active(request, "profile_feature") return render(request, "500.html", context=context, status=500) @@ -29,7 +27,6 @@ def custom_401_error_view(request, context=None): """Used to redirect 401 errors to a custom view""" if context is None: context = {} - context["has_profile_feature_flag"] = flag_is_active(request, "profile_feature") return render(request, "401.html", context=context, status=401) @@ -37,5 +34,4 @@ def custom_403_error_view(request, exception=None, context=None): """Used to redirect 403 errors to a custom view""" if context is None: context = {} - context["has_profile_feature_flag"] = flag_is_active(request, "profile_feature") return render(request, "403.html", context=context, status=403) From 619a71fdb601f1b4767977ac017b18fcf746580d Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 5 Jul 2024 18:08:34 -0400 Subject: [PATCH 024/184] extended header for org users --- src/registrar/assets/sass/_theme/_base.scss | 34 ------------------- .../assets/sass/_theme/_buttons.scss | 28 +++++++++++++++ src/registrar/assets/sass/_theme/_header.scss | 32 ++++++++--------- .../assets/sass/_theme/_identifier.scss | 10 ++++++ src/registrar/assets/sass/_theme/styles.scss | 1 + src/registrar/context_processors.py | 21 ++++++------ src/registrar/registrar_middleware.py | 2 +- .../templates/includes/header_basic.html | 2 +- src/registrar/views/domain.py | 2 +- src/registrar/views/domain_request.py | 1 - src/registrar/views/domains_json.py | 8 +++-- src/registrar/views/portfolios.py | 4 +-- src/registrar/views/user_profile.py | 2 +- 13 files changed, 77 insertions(+), 70 deletions(-) create mode 100644 src/registrar/assets/sass/_theme/_identifier.scss diff --git a/src/registrar/assets/sass/_theme/_base.scss b/src/registrar/assets/sass/_theme/_base.scss index 2aa3da565..12d32d0fe 100644 --- a/src/registrar/assets/sass/_theme/_base.scss +++ b/src/registrar/assets/sass/_theme/_base.scss @@ -92,10 +92,6 @@ footer { color: color('primary'); } -.usa-identifier__logo { - height: units(7); -} - abbr[title] { // workaround for underlining abbr element border-bottom: none; @@ -135,36 +131,6 @@ abbr[title] { cursor: pointer; } -.input-with-edit-button { - svg.usa-icon { - width: 1.5em !important; - height: 1.5em !important; - color: #{$dhs-green}; - position: absolute; - } - &.input-with-edit-button__error { - svg.usa-icon { - color: #{$dhs-red}; - } - div.readonly-field { - color: #{$dhs-red}; - } - } -} - -// We need to deviate from some default USWDS styles here -// in this particular case, so we have to override this. -.usa-form .usa-button.readonly-edit-button { - margin-top: 0px !important; - padding-top: 0px !important; - svg { - width: 1.25em !important; - height: 1.25em !important; - } -} - - - .padding--8-8-9 { padding: 8px 8px 9px !important; } diff --git a/src/registrar/assets/sass/_theme/_buttons.scss b/src/registrar/assets/sass/_theme/_buttons.scss index 92556556b..8ec43705f 100644 --- a/src/registrar/assets/sass/_theme/_buttons.scss +++ b/src/registrar/assets/sass/_theme/_buttons.scss @@ -162,6 +162,34 @@ a.usa-button--unstyled:visited { } } +.input-with-edit-button { + svg.usa-icon { + width: 1.5em !important; + height: 1.5em !important; + color: #{$dhs-green}; + position: absolute; + } + &.input-with-edit-button__error { + svg.usa-icon { + color: #{$dhs-red}; + } + div.readonly-field { + color: #{$dhs-red}; + } + } +} + +// We need to deviate from some default USWDS styles here +// in this particular case, so we have to override this. +.usa-form .usa-button.readonly-edit-button { + margin-top: 0px !important; + padding-top: 0px !important; + svg { + width: 1.25em !important; + height: 1.25em !important; + } +} + .usa-button--filter { width: auto; // For mobile stacking diff --git a/src/registrar/assets/sass/_theme/_header.scss b/src/registrar/assets/sass/_theme/_header.scss index a8c642804..6e12bff9e 100644 --- a/src/registrar/assets/sass/_theme/_header.scss +++ b/src/registrar/assets/sass/_theme/_header.scss @@ -67,30 +67,16 @@ } .usa-header--extended { - .usa-nav__primary { - .usa-nav-link, - .usa-nav-link:hover, - .usa-nav-link:active { - color: color('primary'); - font-weight: font-weight('normal'); - font-size: 16px; - } - .usa-current, - .usa-current:hover, - .usa-current:active { - font-weight: font-weight('bold'); - } - } @include at-media(desktop) { background-color: color('primary-darker'); border-top: solid 1px color('base'); - border-bottom: solid 1px color('base-lighter'); + border-bottom: solid 1px color('base-light'); .usa-logo__text a { color: color('white'); } .usa-nav { - background-color: color('primary-lightest'); + background-color: color('primary-lighter'); } .usa-nav__primary-item:last-child { margin-left: auto; @@ -98,6 +84,20 @@ padding-right: 0; } } + .usa-nav__primary { + .usa-nav-link, + .usa-nav-link:hover, + .usa-nav-link:active { + color: color('primary'); + font-weight: font-weight('normal'); + font-size: 16px; + } + .usa-current, + .usa-current:hover, + .usa-current:active { + font-weight: font-weight('bold'); + } + } .usa-nav__secondary { // I don't know why USWDS has this at 2 rem, which puts it out of alignment right: 3rem; diff --git a/src/registrar/assets/sass/_theme/_identifier.scss b/src/registrar/assets/sass/_theme/_identifier.scss new file mode 100644 index 000000000..e56d887b9 --- /dev/null +++ b/src/registrar/assets/sass/_theme/_identifier.scss @@ -0,0 +1,10 @@ +@use "uswds-core" as *; + +.usa-banner { + background-color: color('primary-darker'); +} + +.usa-identifier__logo { + height: units(7); + } + \ No newline at end of file diff --git a/src/registrar/assets/sass/_theme/styles.scss b/src/registrar/assets/sass/_theme/styles.scss index 142664423..4775b60c9 100644 --- a/src/registrar/assets/sass/_theme/styles.scss +++ b/src/registrar/assets/sass/_theme/styles.scss @@ -21,6 +21,7 @@ @forward "alerts"; @forward "tables"; @forward "sidenav"; +@forward "identifier"; @forward "header"; @forward "register-form"; diff --git a/src/registrar/context_processors.py b/src/registrar/context_processors.py index 8072e85d4..acea72c4e 100644 --- a/src/registrar/context_processors.py +++ b/src/registrar/context_processors.py @@ -13,6 +13,7 @@ def language_code(request): """ return {"LANGUAGE_CODE": settings.LANGUAGE_CODE} + def canonical_path(request): """Add a canonical URL to the template context. @@ -22,6 +23,7 @@ def canonical_path(request): """ return {"CANONICAL_PATH": request.build_absolute_uri(request.path)} + def is_demo_site(request): """Add a boolean if this is a demo site. @@ -31,10 +33,12 @@ def is_demo_site(request): """ return {"IS_DEMO_SITE": settings.IS_DEMO_SITE} + def is_production(request): """Add a boolean if this is our production site.""" return {"IS_PRODUCTION": settings.IS_PRODUCTION} + def org_user_status(request): if request.user.is_authenticated: is_org_user = request.user.is_org_user(request) @@ -42,20 +46,17 @@ def org_user_status(request): is_org_user = False return { - 'is_org_user': is_org_user, + "is_org_user": is_org_user, } + def add_portfolio_to_context(request): - return { - 'portfolio': getattr(request, 'portfolio', None) - } + return {"portfolio": getattr(request, "portfolio", None)} + def add_path_to_context(request): - return { - 'path': getattr(request, 'path', None) - } + return {"path": getattr(request, "path", None)} + def add_has_profile_feature_flag_to_context(request): - return { - 'has_profile_feature_flag': flag_is_active(request, "profile_feature") - } + return {"has_profile_feature_flag": flag_is_active(request, "profile_feature")} diff --git a/src/registrar/registrar_middleware.py b/src/registrar/registrar_middleware.py index 5e534ea7e..80d1fe7a9 100644 --- a/src/registrar/registrar_middleware.py +++ b/src/registrar/registrar_middleware.py @@ -144,7 +144,7 @@ class CheckPortfolioMiddleware: if request.user.is_authenticated and request.user.is_org_user(request): user_portfolios = Portfolio.objects.filter(creator=request.user) first_portfolio = user_portfolios.first() - + if first_portfolio: # Add the portfolio to the request object request.portfolio = first_portfolio diff --git a/src/registrar/templates/includes/header_basic.html b/src/registrar/templates/includes/header_basic.html index 0fef664ac..692b2cb03 100644 --- a/src/registrar/templates/includes/header_basic.html +++ b/src/registrar/templates/includes/header_basic.html @@ -16,7 +16,7 @@
  • {% if user.is_authenticated %} - {{ user.email }} + {{ user.email }}
  • {% if has_profile_feature_flag %}
  • diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 6d30d9c77..1cfb259b0 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -59,7 +59,7 @@ from epplibwrapper import ( from ..utility.email import send_templated_email, EmailSendingError from .utility import DomainPermissionView, DomainInvitationPermissionDeleteView -from waffle.decorators import flag_is_active, waffle_flag +from waffle.decorators import waffle_flag logger = logging.getLogger(__name__) diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py index 40e7d7ed1..34e2bfc37 100644 --- a/src/registrar/views/domain_request.py +++ b/src/registrar/views/domain_request.py @@ -378,7 +378,6 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): def get_context_data(self): """Define context for access on all wizard pages.""" - has_profile_flag = flag_is_active(self.request, "profile_feature") context_stuff = {} if DomainRequest._form_complete(self.domain_request, self.request): diff --git a/src/registrar/views/domains_json.py b/src/registrar/views/domains_json.py index f17185ef2..f700f4396 100644 --- a/src/registrar/views/domains_json.py +++ b/src/registrar/views/domains_json.py @@ -11,7 +11,7 @@ def get_domains_json(request): """Given the current request, get all domains that are associated with the UserDomainRole object""" - user_domain_roles = UserDomainRole.objects.filter(user=request.user).select_related('domain_info__sub_organization') + user_domain_roles = UserDomainRole.objects.filter(user=request.user).select_related("domain_info__sub_organization") domain_ids = user_domain_roles.values_list("domain_id", flat=True) objects = Domain.objects.filter(id__in=domain_ids) @@ -85,7 +85,11 @@ def get_domains_json(request): "action_url": reverse("domain", kwargs={"pk": domain.id}), "action_label": ("View" if domain.state in [Domain.State.DELETED, Domain.State.ON_HOLD] else "Manage"), "svg_icon": ("visibility" if domain.state in [Domain.State.DELETED, Domain.State.ON_HOLD] else "settings"), - "suborganization": domain.domain_info.sub_organization.name if domain.domain_info and domain.domain_info.sub_organization else None, + "suborganization": ( + domain.domain_info.sub_organization.name + if domain.domain_info and domain.domain_info.sub_organization + else None + ), } for domain in page_obj.object_list ] diff --git a/src/registrar/views/portfolios.py b/src/registrar/views/portfolios.py index 1ec40580f..d5b6cbba5 100644 --- a/src/registrar/views/portfolios.py +++ b/src/registrar/views/portfolios.py @@ -1,6 +1,4 @@ -from django.shortcuts import get_object_or_404, render -from registrar.models.portfolio import Portfolio -from waffle.decorators import flag_is_active +from django.shortcuts import render from django.contrib.auth.decorators import login_required diff --git a/src/registrar/views/user_profile.py b/src/registrar/views/user_profile.py index e6909b3e9..0529e137b 100644 --- a/src/registrar/views/user_profile.py +++ b/src/registrar/views/user_profile.py @@ -15,7 +15,7 @@ from registrar.models import ( from registrar.models.user import User from registrar.models.utility.generic_helper import replace_url_queryparams from registrar.views.utility.permission_views import UserProfilePermissionView -from waffle.decorators import flag_is_active, waffle_flag +from waffle.decorators import waffle_flag logger = logging.getLogger(__name__) From a7c29f4d219ac6e57156e5d747236feef9c598ac Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 5 Jul 2024 18:13:46 -0400 Subject: [PATCH 025/184] clenup --- src/registrar/templates/includes/header_basic.html | 3 ++- src/registrar/templates/includes/header_extended.html | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/registrar/templates/includes/header_basic.html b/src/registrar/templates/includes/header_basic.html index 692b2cb03..7076fcb89 100644 --- a/src/registrar/templates/includes/header_basic.html +++ b/src/registrar/templates/includes/header_basic.html @@ -38,4 +38,5 @@ {% block usa_nav_secondary %}{% endblock %} {% endblock %}
- \ No newline at end of file + + \ No newline at end of file diff --git a/src/registrar/templates/includes/header_extended.html b/src/registrar/templates/includes/header_extended.html index 96a77afe5..ccba93ea3 100644 --- a/src/registrar/templates/includes/header_extended.html +++ b/src/registrar/templates/includes/header_extended.html @@ -71,4 +71,5 @@
{% endblock %} - \ No newline at end of file + + \ No newline at end of file From b5d3df6d8579a326797573699fda2f4c91de9a4c Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 8 Jul 2024 08:16:58 -0600 Subject: [PATCH 026/184] Update src/registrar/assets/js/get-gov.js --- src/registrar/assets/js/get-gov.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js index 74f2715b2..7bc19d0d5 100644 --- a/src/registrar/assets/js/get-gov.js +++ b/src/registrar/assets/js/get-gov.js @@ -1851,7 +1851,6 @@ document.addEventListener('DOMContentLoaded', function() { let lastName = document.querySelector(`#id_last_name`); if (firstName && lastName) { let values = [firstName.value, middleName.value, lastName.value] - console.log(values) readonlyField.innerHTML = values.join(" "); }else { readonlyField.innerHTML = "Unknown"; From 173c0b51c0e69a9ca8f04e0becc0b042beec2f3a Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 8 Jul 2024 13:55:11 -0600 Subject: [PATCH 027/184] Update src/registrar/assets/js/get-gov.js --- src/registrar/assets/js/get-gov.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js index 7bc19d0d5..0d450b9e5 100644 --- a/src/registrar/assets/js/get-gov.js +++ b/src/registrar/assets/js/get-gov.js @@ -1846,9 +1846,9 @@ document.addEventListener('DOMContentLoaded', function() { let inputFieldValue = inputField.value if (inputFieldValue || fieldName == "full_name"){ if (fieldName == "full_name"){ - let firstName = document.querySelector(`#id_first_name`); - let middleName = document.querySelector(`#id_middle_name`); - let lastName = document.querySelector(`#id_last_name`); + let firstName = document.querySelector("#id_first_name"); + let middleName = document.querySelector("#id_middle_name"); + let lastName = document.querySelector("#id_last_name"); if (firstName && lastName) { let values = [firstName.value, middleName.value, lastName.value] readonlyField.innerHTML = values.join(" "); From e13be80558232b5a8bfbd5078de213dabdc6eba4 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Mon, 8 Jul 2024 17:33:29 -0400 Subject: [PATCH 028/184] wip --- src/registrar/tests/test_admin.py | 480 +++++++++++++---------- src/registrar/tests/test_admin_domain.py | 18 +- src/registrar/tests/test_views.py | 33 +- src/registrar/tests/test_views_domain.py | 5 + 4 files changed, 316 insertions(+), 220 deletions(-) diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index ef270e887..65a3ab351 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -108,27 +108,32 @@ class TestFsmModelResource(TestCase): class TestDomainRequestAdminForm(TestCase): - @less_console_noise_decorator - def setUp(self): - # Create a test domain request with an initial state of started - self.domain_request = completed_domain_request() - + def test_form_choices(self): with less_console_noise(): + # Create a test domain request with an initial state of started + domain_request = completed_domain_request() + # Create a form instance with the test domain request - form = DomainRequestAdminForm(instance=self.domain_request) + form = DomainRequestAdminForm(instance=domain_request) # Verify that the form choices match the available transitions for started expected_choices = [("started", "Started"), ("submitted", "Submitted")] self.assertEqual(form.fields["status"].widget.choices, expected_choices) + # cleanup + domain_request.delete() + def test_form_no_rejection_reason(self): with less_console_noise(): + # Create a test domain request with an initial state of started + domain_request = completed_domain_request() + # Create a form instance with the test domain request - form = DomainRequestAdminForm(instance=self.domain_request) + form = DomainRequestAdminForm(instance=domain_request) form = DomainRequestAdminForm( - instance=self.domain_request, + instance=domain_request, data={ "status": DomainRequest.DomainRequestStatus.REJECTED, "rejection_reason": None, @@ -140,6 +145,9 @@ class TestDomainRequestAdminForm(TestCase): rejection_reason = form.errors.get("rejection_reason") self.assertEqual(rejection_reason, ["A reason is required for this status."]) + # cleanup + domain_request.delete() + def test_form_choices_when_no_instance(self): with less_console_noise(): # Create a form instance without an instance @@ -174,25 +182,29 @@ class TestDomainRequestAdminForm(TestCase): class TestDomainInvitationAdmin(TestCase): """Tests for the DomainInvitation page""" - @less_console_noise_decorator + @classmethod + def setUpClass(cls): + cls.factory = RequestFactory() + cls.admin = ListHeaderAdmin(model=DomainInvitationAdmin, admin_site=AdminSite()) + cls.superuser = create_superuser() + def setUp(self): """Create a client object""" self.client = Client(HTTP_HOST="localhost:8080") - self.factory = RequestFactory() - self.admin = ListHeaderAdmin(model=DomainInvitationAdmin, admin_site=AdminSite()) - self.superuser = create_superuser() - + def tearDown(self): """Delete all DomainInvitation objects""" DomainInvitation.objects.all().delete() - User.objects.all().delete() Contact.objects.all().delete() + @classmethod + def tearDownClass(self): + 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""" - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) response = self.client.get( "/admin/registrar/domaininvitation/", follow=True, @@ -210,9 +222,7 @@ class TestDomainInvitationAdmin(TestCase): def test_get_filters(self): """Ensures that our filters are displaying correctly""" with less_console_noise(): - # Have to get creative to get past linter - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) response = self.client.get( "/admin/registrar/domaininvitation/", @@ -235,32 +245,32 @@ class TestDomainInvitationAdmin(TestCase): class TestHostAdmin(TestCase): + + @classmethod + def setUpClass(cls): + cls.site = AdminSite() + cls.factory = RequestFactory() + cls.admin = MyHostAdmin(model=Host, admin_site=cls.site) + cls.superuser = create_superuser() + def setUp(self): """Setup environment for a mock admin user""" super().setUp() - self.site = AdminSite() - self.factory = RequestFactory() - self.admin = MyHostAdmin(model=Host, admin_site=self.site) self.client = Client(HTTP_HOST="localhost:8080") - self.superuser = create_superuser() - self.test_helper = GenericTestHelper( - factory=self.factory, - user=self.superuser, - admin=self.admin, - url="/admin/registrar/Host/", - model=Host, - ) def tearDown(self): super().tearDown() Host.objects.all().delete() Domain.objects.all().delete() + @classmethod + def tearDownClass(cls): + 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""" - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) response = self.client.get( "/admin/registrar/host/", follow=True, @@ -282,8 +292,7 @@ class TestHostAdmin(TestCase): # Create a fake host host, _ = Host.objects.get_or_create(name="ns1.test.gov", domain=domain) - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) response = self.client.get( "/admin/registrar/host/{}/change/".format(host.pk), follow=True, @@ -292,6 +301,13 @@ class TestHostAdmin(TestCase): # Make sure the page loaded self.assertEqual(response.status_code, 200) + self.test_helper = GenericTestHelper( + factory=self.factory, + user=self.superuser, + admin=self.admin, + url="/admin/registrar/Host/", + model=Host, + ) # These should exist in the response expected_values = [ ("domain", "Domain associated with this host"), @@ -300,49 +316,26 @@ class TestHostAdmin(TestCase): class TestDomainInformationAdmin(TestCase): - @less_console_noise_decorator - def setUp(self): - """Setup environment for a mock admin user""" - self.site = AdminSite() - self.factory = RequestFactory() - self.admin = DomainInformationAdmin(model=DomainInformation, admin_site=self.site) - self.client = Client(HTTP_HOST="localhost:8080") - self.superuser = create_superuser() - self.staffuser = create_user() - self.mock_data_generator = AuditedAdminMockData() - self.test_helper = GenericTestHelper( - factory=self.factory, - user=self.superuser, - admin=self.admin, + @classmethod + def setUpClass(cls): + """Setup environment for a mock admin user""" + cls.site = AdminSite() + cls.factory = RequestFactory() + cls.admin = DomainInformationAdmin(model=DomainInformation, admin_site=cls.site) + cls.superuser = create_superuser() + cls.staffuser = create_user() + cls.mock_data_generator = AuditedAdminMockData() + cls.test_helper = GenericTestHelper( + factory=cls.factory, + user=cls.superuser, + admin=cls.admin, url="/admin/registrar/DomainInformation/", model=DomainInformation, ) - # Create fake DomainInformation objects - DomainInformation.objects.create( - creator=self.mock_data_generator.dummy_user("fake", "creator"), - domain=self.mock_data_generator.dummy_domain("Apple"), - submitter=self.mock_data_generator.dummy_contact("Zebra", "submitter"), - ) - - DomainInformation.objects.create( - creator=self.mock_data_generator.dummy_user("fake", "creator"), - domain=self.mock_data_generator.dummy_domain("Zebra"), - submitter=self.mock_data_generator.dummy_contact("Apple", "submitter"), - ) - - DomainInformation.objects.create( - creator=self.mock_data_generator.dummy_user("fake", "creator"), - domain=self.mock_data_generator.dummy_domain("Circus"), - submitter=self.mock_data_generator.dummy_contact("Xylophone", "submitter"), - ) - - DomainInformation.objects.create( - creator=self.mock_data_generator.dummy_user("fake", "creator"), - domain=self.mock_data_generator.dummy_domain("Xylophone"), - submitter=self.mock_data_generator.dummy_contact("Circus", "submitter"), - ) + def setUp(self): + self.client = Client(HTTP_HOST="localhost:8080") def tearDown(self): """Delete all Users, Domains, and UserDomainRoles""" @@ -350,6 +343,9 @@ class TestDomainInformationAdmin(TestCase): DomainRequest.objects.all().delete() Domain.objects.all().delete() Contact.objects.all().delete() + + @classmethod + def tearDownClass(cls): User.objects.all().delete() @less_console_noise_decorator @@ -362,8 +358,7 @@ class TestDomainInformationAdmin(TestCase): domain_information = DomainInformation.objects.filter(domain_request=_domain_request).get() - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) response = self.client.get( "/admin/registrar/domaininformation/{}/change/".format(domain_information.pk), follow=True, @@ -395,8 +390,7 @@ class TestDomainInformationAdmin(TestCase): _domain_request.approve() domain_information = DomainInformation.objects.filter(domain_request=_domain_request).get() - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) response = self.client.get( "/admin/registrar/domaininformation/{}/change/".format(domain_information.pk), follow=True, @@ -420,8 +414,7 @@ class TestDomainInformationAdmin(TestCase): @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) + self.client.force_login(self.superuser) response = self.client.get( "/admin/registrar/domaininformation/", follow=True, @@ -445,8 +438,7 @@ class TestDomainInformationAdmin(TestCase): domain_request.approve() domain_info = DomainInformation.objects.filter(domain=domain_request.approved_domain).get() - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) response = self.client.get( "/admin/registrar/domaininformation/{}/change/".format(domain_info.pk), follow=True, @@ -478,8 +470,7 @@ class TestDomainInformationAdmin(TestCase): # Get the other contact other_contact = domain_info.other_contacts.all().first() - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) response = self.client.get( "/admin/registrar/domaininformation/{}/change/".format(domain_info.pk), @@ -515,8 +506,7 @@ class TestDomainInformationAdmin(TestCase): domain_request.approve() domain_info = DomainInformation.objects.filter(domain=domain_request.approved_domain).get() - p = "userpass" - self.client.login(username="staffuser", password=p) + self.client.force_login(self.staffuser) response = self.client.get( "/admin/registrar/domaininformation/{}/change/".format(domain_info.pk), follow=True, @@ -527,8 +517,7 @@ class TestDomainInformationAdmin(TestCase): # To make sure that its not a fluke, swap to an admin user # and try to access the same page. This should succeed. - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) response = self.client.get( "/admin/registrar/domaininformation/{}/change/".format(domain_info.pk), follow=True, @@ -561,8 +550,7 @@ class TestDomainInformationAdmin(TestCase): domain_request.approve() domain_info = DomainInformation.objects.filter(domain=domain_request.approved_domain).get() - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) response = self.client.get( "/admin/registrar/domaininformation/{}/change/".format(domain_info.pk), follow=True, @@ -624,6 +612,11 @@ class TestDomainInformationAdmin(TestCase): # Test for the copy link self.assertContains(response, "usa-button__clipboard", count=4) + # cleanup this test + domain_info.delete() + domain_request.delete() + _creator.delete() + def test_readonly_fields_for_analyst(self): """Ensures that analysts have their permissions setup correctly""" with less_console_noise(): @@ -652,8 +645,7 @@ class TestDomainInformationAdmin(TestCase): def test_domain_sortable(self): """Tests if DomainInformation sorts by domain correctly""" with less_console_noise(): - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) # Assert that our sort works correctly self.test_helper.assert_table_sorted("1", ("domain__name",)) @@ -664,8 +656,7 @@ class TestDomainInformationAdmin(TestCase): def test_submitter_sortable(self): """Tests if DomainInformation sorts by submitter correctly""" with less_console_noise(): - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) # Assert that our sort works correctly self.test_helper.assert_table_sorted( @@ -678,32 +669,42 @@ class TestDomainInformationAdmin(TestCase): class TestUserDomainRoleAdmin(TestCase): - def setUp(self): - """Setup environment for a mock admin user""" - self.site = AdminSite() - self.factory = RequestFactory() - self.admin = UserDomainRoleAdmin(model=UserDomainRole, admin_site=self.site) - self.client = Client(HTTP_HOST="localhost:8080") - self.superuser = create_superuser() - self.test_helper = GenericTestHelper( - factory=self.factory, - user=self.superuser, - admin=self.admin, + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.site = AdminSite() + cls.factory = RequestFactory() + cls.admin = UserDomainRoleAdmin(model=UserDomainRole, admin_site=cls.site) + cls.superuser = create_superuser() + cls.test_helper = GenericTestHelper( + factory=cls.factory, + user=cls.superuser, + admin=cls.admin, url="/admin/registrar/UserDomainRole/", model=UserDomainRole, ) + + def setUp(self): + """Setup environment for a mock admin user""" + super().setUp() + self.client = Client(HTTP_HOST="localhost:8080") def tearDown(self): """Delete all Users, Domains, and UserDomainRoles""" - User.objects.all().delete() + super().tearDown() Domain.objects.all().delete() UserDomainRole.objects.all().delete() + + @classmethod + def tearDownClass(cls): + super().tearDownClass() + 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""" - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) response = self.client.get( "/admin/registrar/userdomainrole/", follow=True, @@ -721,8 +722,7 @@ class TestUserDomainRoleAdmin(TestCase): def test_domain_sortable(self): """Tests if the UserDomainrole sorts by domain correctly""" with less_console_noise(): - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) fake_user = User.objects.create( username="dummyuser", first_name="Stewart", last_name="Jones", email="AntarcticPolarBears@example.com" @@ -740,11 +740,15 @@ class TestUserDomainRoleAdmin(TestCase): # Assert that sorting in reverse works correctly self.test_helper.assert_table_sorted("-2", ("-domain__name",)) + # delete data from test + UserDomainRole.objects.all().delete() + Domain.objects.all().delete() + fake_user.delete() + def test_user_sortable(self): """Tests if the UserDomainrole sorts by user correctly""" with less_console_noise(): - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) mock_data_generator = AuditedAdminMockData() @@ -762,13 +766,17 @@ class TestUserDomainRoleAdmin(TestCase): # Assert that sorting in reverse works correctly self.test_helper.assert_table_sorted("-1", ("-user__first_name", "-user__last_name")) + # delete data from this test + UserDomainRole.objects.all().delete() + fake_domain.delete() + User.objects.exclude(username="superuser").delete() + def test_email_not_in_search(self): """Tests the search bar in Django Admin for UserDomainRoleAdmin. Should return no results for an invalid email.""" with less_console_noise(): # Have to get creative to get past linter - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) fake_user = User.objects.create( username="dummyuser", first_name="Stewart", last_name="Jones", email="AntarcticPolarBears@example.com" @@ -795,13 +803,17 @@ class TestUserDomainRoleAdmin(TestCase): # We only need to check for the end of the HTML string self.assertNotContains(response, "Stewart Jones AntarcticPolarBears@example.com") + # cleanup this test + UserDomainRole.objects.all().delete() + Domain.objects.all().delete() + fake_user.delete() + def test_email_in_search(self): """Tests the search bar in Django Admin for UserDomainRoleAdmin. Should return results for an valid email.""" with less_console_noise(): # Have to get creative to get past linter - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) fake_user = User.objects.create( username="dummyuser", first_name="Joe", last_name="Jones", email="AntarcticPolarBears@example.com" @@ -828,20 +840,38 @@ class TestUserDomainRoleAdmin(TestCase): # We only need to check for the end of the HTML string self.assertContains(response, "Joe Jones AntarcticPolarBears@example.com", count=1) + # cleanup this test + UserDomainRole.objects.all().delete() + fake_domain.delete() + fake_user.delete() class TestListHeaderAdmin(TestCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.site = AdminSite() + cls.factory = RequestFactory() + cls.admin = ListHeaderAdmin(model=DomainRequest, admin_site=None) + cls.superuser = create_superuser() + def setUp(self): - self.site = AdminSite() - self.factory = RequestFactory() - self.admin = ListHeaderAdmin(model=DomainRequest, admin_site=None) + super().setUp() self.client = Client(HTTP_HOST="localhost:8080") - self.superuser = create_superuser() + + def tearDown(self): + # delete any domain requests too + DomainInformation.objects.all().delete() + DomainRequest.objects.all().delete() + + @classmethod + def tearDownClass(cls): + super().tearDownClass() + User.objects.all().delete() def test_changelist_view(self): with less_console_noise(): - # Have to get creative to get past linter - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) # Mock a user user = mock_user() # Make the request using the Client class @@ -895,34 +925,35 @@ class TestListHeaderAdmin(TestCase): ], ) - def tearDown(self): - # delete any domain requests too - DomainInformation.objects.all().delete() - DomainRequest.objects.all().delete() - User.objects.all().delete() - class TestMyUserAdmin(MockDb): + @classmethod + def setUpClass(cls): + super().setUpClass() + admin_site = AdminSite() + cls.admin = MyUserAdmin(model=get_user_model(), admin_site=admin_site) + cls.superuser = create_superuser() + cls.staffuser = create_user() + cls.test_helper = GenericTestHelper(admin=cls.admin) + def setUp(self): super().setUp() - admin_site = AdminSite() - self.admin = MyUserAdmin(model=get_user_model(), admin_site=admin_site) self.client = Client(HTTP_HOST="localhost:8080") - self.superuser = create_superuser() - self.staffuser = create_user() - self.test_helper = GenericTestHelper(admin=self.admin) def tearDown(self): super().tearDown() DomainRequest.objects.all().delete() + + @classmethod + def tearDownClass(cls): + super().tearDownClass() 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""" - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) response = self.client.get( "/admin/registrar/user/", follow=True, @@ -942,8 +973,7 @@ class TestMyUserAdmin(MockDb): """ user = self.staffuser - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) response = self.client.get( "/admin/registrar/user/{}/change/".format(user.pk), follow=True, @@ -1049,8 +1079,7 @@ class TestMyUserAdmin(MockDb): user=self.meoward_user, domain=domain_deleted, role=UserDomainRole.Roles.MANAGER ) - p = "userpass" - self.client.login(username="staffuser", password=p) + self.client.force_login(self.staffuser) response = self.client.get( "/admin/registrar/user/{}/change/".format(self.meoward_user.id), follow=True, @@ -1111,11 +1140,23 @@ class TestMyUserAdmin(MockDb): domain_deleted.delete() class AuditedAdminTest(TestCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.site = AdminSite() + cls.factory = RequestFactory() + def setUp(self): - self.site = AdminSite() - self.factory = RequestFactory() + super().setUp() self.client = Client(HTTP_HOST="localhost:8080") + def tearDown(self): + super().tearDown() + DomainInformation.objects.all().delete() + DomainRequest.objects.all().delete() + DomainInvitation.objects.all().delete() + def order_by_desired_field_helper(self, obj_to_sort: AuditedAdmin, request, field_name, *obj_names): with less_console_noise(): formatted_sort_fields = [] @@ -1340,26 +1381,31 @@ class AuditedAdminTest(TestCase): else: return None - def tearDown(self): - DomainInformation.objects.all().delete() - DomainRequest.objects.all().delete() - DomainInvitation.objects.all().delete() - class DomainSessionVariableTest(TestCase): """Test cases for session variables in Django Admin""" + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.factory = RequestFactory() + cls.admin = DomainAdmin(Domain, None) + cls.superuser = create_superuser() + def setUp(self): - self.factory = RequestFactory() - self.admin = DomainAdmin(Domain, None) + super().setUp() self.client = Client(HTTP_HOST="localhost:8080") + @classmethod + def tearDownClass(cls): + super().tearDownClass() + User.objects.all().delete() + def test_session_vars_set_correctly(self): """Checks if session variables are being set correctly""" with less_console_noise(): - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) dummy_domain_information = generic_domain_object("information", "session") request = self.get_factory_post_edit_domain(dummy_domain_information.domain.pk) @@ -1374,8 +1420,7 @@ class DomainSessionVariableTest(TestCase): """Checks if session variables are being set correctly""" with less_console_noise(): - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) dummy_domain_information: Domain = generic_domain_object("information", "session") dummy_domain_information.domain.pk = 1 @@ -1389,8 +1434,7 @@ class DomainSessionVariableTest(TestCase): """Checks if incorrect session variables get overridden""" with less_console_noise(): - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) dummy_domain_information = generic_domain_object("information", "session") request = self.get_factory_post_edit_domain(dummy_domain_information.domain.pk) @@ -1407,8 +1451,7 @@ class DomainSessionVariableTest(TestCase): """Checks to see if session variables retain old information""" with less_console_noise(): - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) dummy_domain_information_list = multiple_unalphabetical_domain_objects("information") for item in dummy_domain_information_list: @@ -1422,8 +1465,7 @@ class DomainSessionVariableTest(TestCase): """Simulates two requests at once""" with less_console_noise(): - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) info_first = generic_domain_object("information", "session") info_second = generic_domain_object("information", "session2") @@ -1472,19 +1514,34 @@ class DomainSessionVariableTest(TestCase): class TestContactAdmin(TestCase): - def setUp(self): - self.site = AdminSite() - self.factory = RequestFactory() - self.client = Client(HTTP_HOST="localhost:8080") - self.admin = ContactAdmin(model=get_user_model(), admin_site=None) - self.superuser = create_superuser() - self.staffuser = create_user() + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.site = AdminSite() + cls.factory = RequestFactory() + cls.admin = ContactAdmin(model=get_user_model(), admin_site=None) + cls.superuser = create_superuser() + cls.staffuser = create_user() + + def setUp(self): + super().setUp() + self.client = Client(HTTP_HOST="localhost:8080") + + def tearDown(self): + super().tearDown() + DomainRequest.objects.all().delete() + Contact.objects.all().delete() + + @classmethod + def tearDownClass(cls): + super().tearDownClass() + 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""" - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) response = self.client.get( "/admin/registrar/contact/", follow=True, @@ -1555,6 +1612,10 @@ class TestContactAdmin(TestCase): f"user/{self.staffuser.pk}/change/'>staff@example.com" "", ) + + # cleanup this test + DomainRequest.objects.all().delete() + contact.delete() def test_change_view_for_joined_contact_five_or_more(self): """Create a contact, join it to 5 domain requests. The 6th join will be a user. @@ -1591,33 +1652,39 @@ class TestContactAdmin(TestCase): "" "

And 1 more...

", ) - - def tearDown(self): - DomainRequest.objects.all().delete() - Contact.objects.all().delete() - User.objects.all().delete() + # cleanup this test + DomainRequest.objects.all().delete() + contact.delete() class TestVerifiedByStaffAdmin(TestCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.site = AdminSite() + cls.superuser = create_superuser() + cls.admin = VerifiedByStaffAdmin(model=VerifiedByStaff, admin_site=cls.site) + cls.factory = RequestFactory() + cls.test_helper = GenericTestHelper(admin=cls.admin) + def setUp(self): super().setUp() - self.site = AdminSite() - self.superuser = create_superuser() - self.admin = VerifiedByStaffAdmin(model=VerifiedByStaff, admin_site=self.site) - self.factory = RequestFactory() self.client = Client(HTTP_HOST="localhost:8080") - self.test_helper = GenericTestHelper(admin=self.admin) def tearDown(self): super().tearDown() VerifiedByStaff.objects.all().delete() + + @classmethod + def tearDownClass(cls): + super().tearDownClass() 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""" - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) response = self.client.get( "/admin/registrar/verifiedbystaff/", follow=True, @@ -1639,8 +1706,7 @@ class TestVerifiedByStaffAdmin(TestCase): """ vip_instance, _ = VerifiedByStaff.objects.get_or_create(email="test@example.com", notes="Test Notes") - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) response = self.client.get( "/admin/registrar/verifiedbystaff/{}/change/".format(vip_instance.pk), follow=True, @@ -1694,8 +1760,7 @@ class TestWebsiteAdmin(TestCase): @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) + self.client.force_login(self.superuser) response = self.client.get( "/admin/registrar/website/", follow=True, @@ -1710,25 +1775,33 @@ class TestWebsiteAdmin(TestCase): class TestDraftDomain(TestCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.site = AdminSite() + cls.superuser = create_superuser() + cls.admin = DraftDomainAdmin(model=DraftDomain, admin_site=cls.site) + cls.factory = RequestFactory() + cls.test_helper = GenericTestHelper(admin=cls.admin) + def setUp(self): super().setUp() - self.site = AdminSite() - self.superuser = create_superuser() - self.admin = DraftDomainAdmin(model=DraftDomain, admin_site=self.site) - self.factory = RequestFactory() self.client = Client(HTTP_HOST="localhost:8080") - self.test_helper = GenericTestHelper(admin=self.admin) def tearDown(self): super().tearDown() DraftDomain.objects.all().delete() + + @classmethod + def tearDownClass(cls): + super().tearDownClass() 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""" - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) response = self.client.get( "/admin/registrar/draftdomain/", follow=True, @@ -1745,25 +1818,32 @@ class TestDraftDomain(TestCase): class TestFederalAgency(TestCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.site = AdminSite() + cls.superuser = create_superuser() + cls.admin = FederalAgencyAdmin(model=FederalAgency, admin_site=cls.site) + cls.factory = RequestFactory() + cls.test_helper = GenericTestHelper(admin=cls.admin) + def setUp(self): - super().setUp() - self.site = AdminSite() - self.superuser = create_superuser() - self.admin = FederalAgencyAdmin(model=FederalAgency, admin_site=self.site) - self.factory = RequestFactory() self.client = Client(HTTP_HOST="localhost:8080") - self.test_helper = GenericTestHelper(admin=self.admin) def tearDown(self): super().tearDown() FederalAgency.objects.all().delete() + + @classmethod + def tearDownClass(cls): + super().tearDownClass() 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""" - p = "adminpass" - self.client.login(username="superuser", password=p) + self.client.force_login(self.superuser) response = self.client.get( "/admin/registrar/federalagency/", follow=True, @@ -1828,8 +1908,7 @@ class TestTransitionDomain(TestCase): @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) + self.client.force_login(self.superuser) response = self.client.get( "/admin/registrar/transitiondomain/", follow=True, @@ -1860,8 +1939,7 @@ class TestUserGroup(TestCase): @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) + self.client.force_login(self.superuser) response = self.client.get( "/admin/registrar/usergroup/", follow=True, diff --git a/src/registrar/tests/test_admin_domain.py b/src/registrar/tests/test_admin_domain.py index a25c92142..c830d6688 100644 --- a/src/registrar/tests/test_admin_domain.py +++ b/src/registrar/tests/test_admin_domain.py @@ -39,14 +39,14 @@ class TestDomainAdminAsStaff(MockEppLib): @classmethod def setUpClass(self): super().setUpClass() - self.client = Client(HTTP_HOST="localhost:8080") self.staffuser = create_user() - - def setUp(self): - self.client.force_login(self.staffuser) self.site = AdminSite() self.admin = DomainAdmin(model=Domain, admin_site=self.site) self.factory = RequestFactory() + + def setUp(self): + self.client = Client(HTTP_HOST="localhost:8080") + self.client.force_login(self.staffuser) super().setUp() def tearDown(self): @@ -370,14 +370,14 @@ class TestDomainAdminWClient(TestCase): @classmethod def setUpClass(self): super().setUpClass() - self.client = Client(HTTP_HOST="localhost:8080") - self.superuser = create_superuser() - - def setUp(self): - self.client.force_login(self.superuser) self.site = AdminSite() self.admin = DomainAdmin(model=Domain, admin_site=self.site) self.factory = RequestFactory() + self.superuser = create_superuser() + + def setUp(self): + self.client = Client(HTTP_HOST="localhost:8080") + self.client.force_login(self.superuser) super().setUp() def tearDown(self): diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 61bc94a32..8b228b50c 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -30,8 +30,11 @@ logger = logging.getLogger(__name__) class TestViews(TestCase): - def setUp(self): - self.client = Client() + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.client = Client() def test_health_check_endpoint(self): response = self.client.get("/health") @@ -50,26 +53,32 @@ class TestViews(TestCase): class TestWithUser(MockEppLib): - def setUp(self): - super().setUp() + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.client = Client() + + # def setUp(self): + # super().setUp() username = "test_user" first_name = "First" last_name = "Last" email = "info@example.com" phone = "8003111234" - self.user = get_user_model().objects.create( + cls.user = get_user_model().objects.create( username=username, first_name=first_name, last_name=last_name, email=email, phone=phone ) title = "test title" - self.user.contact.title = title - self.user.contact.save() + cls.user.contact.title = title + cls.user.contact.save() username_regular_incomplete = "test_regular_user_incomplete" username_other_incomplete = "test_other_user_incomplete" first_name_2 = "Incomplete" email_2 = "unicorn@igorville.com" # in the case below, REGULAR user is 'Verified by Login.gov, ie. IAL2 - self.incomplete_regular_user = get_user_model().objects.create( + cls.incomplete_regular_user = get_user_model().objects.create( username=username_regular_incomplete, first_name=first_name_2, email=email_2, @@ -77,7 +86,7 @@ class TestWithUser(MockEppLib): ) # in the case below, other user is representative of GRANDFATHERED, # VERIFIED_BY_STAFF, INVITED, FIXTURE_USER, ie. IAL1 - self.incomplete_other_user = get_user_model().objects.create( + cls.incomplete_other_user = get_user_model().objects.create( username=username_other_incomplete, first_name=first_name_2, email=email_2, @@ -89,7 +98,11 @@ class TestWithUser(MockEppLib): super().tearDown() DomainRequest.objects.all().delete() DomainInformation.objects.all().delete() - User.objects.all().delete() + + # @classmethod + # def tearDownClass(cls): + # super() + # User.objects.all().delete() class TestEnvironmentVariablesEffects(TestCase): diff --git a/src/registrar/tests/test_views_domain.py b/src/registrar/tests/test_views_domain.py index 8f9466cc9..2cb54f16f 100644 --- a/src/registrar/tests/test_views_domain.py +++ b/src/registrar/tests/test_views_domain.py @@ -208,6 +208,11 @@ class TestDomainPermissions(TestWithDomainPermissions): class TestDomainOverview(TestWithDomainPermissions, WebTest): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.client.force_login(cls.user) def setUp(self): super().setUp() self.app.set_user(self.user.username) From dcb63d194733b94e0fa0e15840beb04c06e6a6e6 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Mon, 8 Jul 2024 18:57:46 -0400 Subject: [PATCH 029/184] Fix issues with org_user_status global context and logo_clickable profile context --- src/djangooidc/tests/test_views.py | 4 + .../assets/sass/_theme/_identifier.scss | 3 +- src/registrar/templates/base.html | 6 +- src/registrar/templates/dashboard_base.html | 1 - .../templates/finish_profile_setup.html | 4 +- .../templates/includes/header_basic.html | 73 ++++----- .../templates/includes/header_extended.html | 6 +- .../templates/includes/header_selector.html | 5 + src/registrar/templates/portfolio_base.html | 2 - src/registrar/templates/profile.html | 4 +- src/registrar/views/domains_json.py | 151 ++++++++++-------- 11 files changed, 132 insertions(+), 127 deletions(-) create mode 100644 src/registrar/templates/includes/header_selector.html diff --git a/src/djangooidc/tests/test_views.py b/src/djangooidc/tests/test_views.py index bdd61b346..6ebe25d45 100644 --- a/src/djangooidc/tests/test_views.py +++ b/src/djangooidc/tests/test_views.py @@ -429,6 +429,10 @@ class ViewsTest(TestCase): # Create a mock request request = self.factory.get("/some-url") request.session = {"acr_value": ""} + # Mock user and its attributes + mock_user = MagicMock() + mock_user.is_authenticated = True + request.user = mock_user # Ensure that the CLIENT instance used in login_callback is the mock # patch _requires_step_up_auth to return False with patch("djangooidc.views._requires_step_up_auth", return_value=False), patch( diff --git a/src/registrar/assets/sass/_theme/_identifier.scss b/src/registrar/assets/sass/_theme/_identifier.scss index e56d887b9..58ccd659c 100644 --- a/src/registrar/assets/sass/_theme/_identifier.scss +++ b/src/registrar/assets/sass/_theme/_identifier.scss @@ -6,5 +6,4 @@ .usa-identifier__logo { height: units(7); - } - \ No newline at end of file +} diff --git a/src/registrar/templates/base.html b/src/registrar/templates/base.html index a7b2bb783..e5bd1b0b9 100644 --- a/src/registrar/templates/base.html +++ b/src/registrar/templates/base.html @@ -135,11 +135,7 @@
{% block header %} - {% if not is_org_user %} - {% include "includes/header_basic.html" %} - {% else %} - {% include "includes/header_extended.html" %} - {% endif %} + {% include "includes/header_selector.html" with logo_clickable=True %} {% endblock header %} {% block wrapper %} diff --git a/src/registrar/templates/dashboard_base.html b/src/registrar/templates/dashboard_base.html index 4f24b9280..6dd2ce8fd 100644 --- a/src/registrar/templates/dashboard_base.html +++ b/src/registrar/templates/dashboard_base.html @@ -9,7 +9,6 @@ {% block content %} {% block messages %} {% if messages %} -

test

    {% for message in messages %}
  • diff --git a/src/registrar/templates/finish_profile_setup.html b/src/registrar/templates/finish_profile_setup.html index 6e35ad5da..61cc192bf 100644 --- a/src/registrar/templates/finish_profile_setup.html +++ b/src/registrar/templates/finish_profile_setup.html @@ -4,8 +4,8 @@ {% block title %} Finish setting up your profile | {% endblock %} {# Disable the redirect #} -{% block logo %} - {% include "includes/gov_extended_logo.html" with logo_clickable=user_finished_setup %} +{% block header %} + {% include "includes/header_selector.html" with logo_clickable=user_finished_setup %} {% endblock %} {# Add the new form #} diff --git a/src/registrar/templates/includes/header_basic.html b/src/registrar/templates/includes/header_basic.html index 7076fcb89..fd3b71000 100644 --- a/src/registrar/templates/includes/header_basic.html +++ b/src/registrar/templates/includes/header_basic.html @@ -1,42 +1,39 @@ {% load static %}
    -
    -
    - {% block logo %} - {% include "includes/gov_extended_logo.html" with logo_clickable=True %} - {% endblock %} - -
    - {% block usa_nav %} - - {% block usa_nav_secondary %}{% endblock %} - {% endblock %} +
    +
    + {% include "includes/gov_extended_logo.html" with logo_clickable=logo_clickable %} +
    -
    - \ No newline at end of file + {% block usa_nav %} + + {% block usa_nav_secondary %}{% endblock %} + {% endblock %} +
+ diff --git a/src/registrar/templates/includes/header_extended.html b/src/registrar/templates/includes/header_extended.html index ccba93ea3..f388a71bc 100644 --- a/src/registrar/templates/includes/header_extended.html +++ b/src/registrar/templates/includes/header_extended.html @@ -2,9 +2,7 @@
- {% block logo %} - {% include "includes/gov_extended_logo.html" with logo_clickable=True %} - {% endblock %} + {% include "includes/gov_extended_logo.html" with logo_clickable=logo_clickable %}
{% block usa_nav %} @@ -71,5 +69,5 @@
{% endblock %} - + \ No newline at end of file diff --git a/src/registrar/templates/includes/header_selector.html b/src/registrar/templates/includes/header_selector.html new file mode 100644 index 000000000..3cb2bd51b --- /dev/null +++ b/src/registrar/templates/includes/header_selector.html @@ -0,0 +1,5 @@ +{% if not is_org_user %} + {% include "includes/header_basic.html" with logo_clickable=logo_clickable %} +{% else %} + {% include "includes/header_extended.html" with logo_clickable=logo_clickable %} +{% endif %} diff --git a/src/registrar/templates/portfolio_base.html b/src/registrar/templates/portfolio_base.html index 4cb8145f8..d4ba4dab2 100644 --- a/src/registrar/templates/portfolio_base.html +++ b/src/registrar/templates/portfolio_base.html @@ -36,6 +36,4 @@ {% block content_bottom %}{% endblock %}
- - {% endblock wrapper %} diff --git a/src/registrar/templates/profile.html b/src/registrar/templates/profile.html index 41471fe88..6e1e7781f 100644 --- a/src/registrar/templates/profile.html +++ b/src/registrar/templates/profile.html @@ -6,8 +6,8 @@ Edit your User Profile | {% load static url_helpers %} {# Disable the redirect #} -{% block logo %} - {% include "includes/gov_extended_logo.html" with logo_clickable=user_finished_setup %} +{% block header %} + {% include "includes/header_selector.html" with logo_clickable=user_finished_setup %} {% endblock %} {% block content %} diff --git a/src/registrar/views/domains_json.py b/src/registrar/views/domains_json.py index f700f4396..59bc3e778 100644 --- a/src/registrar/views/domains_json.py +++ b/src/registrar/views/domains_json.py @@ -17,82 +17,15 @@ def get_domains_json(request): objects = Domain.objects.filter(id__in=domain_ids) unfiltered_total = objects.count() - # Handle sorting - sort_by = request.GET.get("sort_by", "id") # Default to 'id' - order = request.GET.get("order", "asc") # Default to 'asc' - - # Handle search term - search_term = request.GET.get("search_term") - if search_term: - objects = objects.filter(Q(name__icontains=search_term)) - - # Handle state - status_param = request.GET.get("status") - if status_param: - status_list = status_param.split(",") - - # if unknown is in status_list, append 'dns needed' since both - # unknown and dns needed display as DNS Needed, and both are - # searchable via state parameter of 'unknown' - if "unknown" in status_list: - status_list.append("dns needed") - - # Split the status list into normal states and custom states - normal_states = [state for state in status_list if state in Domain.State.values] - custom_states = [state for state in status_list if state == "expired"] - - # Construct Q objects for normal states that can be queried through ORM - state_query = Q() - if normal_states: - state_query |= Q(state__in=normal_states) - - # Handle custom states in Python, as expired can not be queried through ORM - if "expired" in custom_states: - expired_domain_ids = [domain.id for domain in objects if domain.state_display() == "Expired"] - state_query |= Q(id__in=expired_domain_ids) - - # Apply the combined query - objects = objects.filter(state_query) - - # If there are filtered states, and expired is not one of them, domains with - # state_display of 'Expired' must be removed - if "expired" not in custom_states: - expired_domain_ids = [domain.id for domain in objects if domain.state_display() == "Expired"] - objects = objects.exclude(id__in=expired_domain_ids) - - if sort_by == "state_display": - # Fetch the objects and sort them in Python - objects = list(objects) # Evaluate queryset to a list - objects.sort(key=lambda domain: domain.state_display(), reverse=(order == "desc")) - else: - if order == "desc": - sort_by = f"-{sort_by}" - objects = objects.order_by(sort_by) + objects = apply_search(objects, request) + objects = apply_state_filter(objects, request) + objects = apply_sorting(objects, request) paginator = Paginator(objects, 10) page_number = request.GET.get("page") page_obj = paginator.get_page(page_number) - # Convert objects to JSON-serializable format - domains = [ - { - "id": domain.id, - "name": domain.name, - "expiration_date": domain.expiration_date, - "state": domain.state, - "state_display": domain.state_display(), - "get_state_help_text": domain.get_state_help_text(), - "action_url": reverse("domain", kwargs={"pk": domain.id}), - "action_label": ("View" if domain.state in [Domain.State.DELETED, Domain.State.ON_HOLD] else "Manage"), - "svg_icon": ("visibility" if domain.state in [Domain.State.DELETED, Domain.State.ON_HOLD] else "settings"), - "suborganization": ( - domain.domain_info.sub_organization.name - if domain.domain_info and domain.domain_info.sub_organization - else None - ), - } - for domain in page_obj.object_list - ] + domains = [serialize_domain(domain) for domain in page_obj.object_list] return JsonResponse( { @@ -105,3 +38,79 @@ def get_domains_json(request): "unfiltered_total": unfiltered_total, } ) + + +def apply_search(queryset, request): + search_term = request.GET.get("search_term") + if search_term: + queryset = queryset.filter(Q(name__icontains=search_term)) + return queryset + + +def apply_state_filter(queryset, request): + status_param = request.GET.get("status") + if status_param: + status_list = status_param.split(",") + # if unknown is in status_list, append 'dns needed' since both + # unknown and dns needed display as DNS Needed, and both are + # searchable via state parameter of 'unknown' + if "unknown" in status_list: + status_list.append("dns needed") + # Split the status list into normal states and custom states + normal_states = [state for state in status_list if state in Domain.State.values] + custom_states = [state for state in status_list if state == "expired"] + # Construct Q objects for normal states that can be queried through ORM + state_query = Q() + if normal_states: + state_query |= Q(state__in=normal_states) + # Handle custom states in Python, as expired can not be queried through ORM + if "expired" in custom_states: + expired_domain_ids = [domain.id for domain in queryset if domain.state_display() == "Expired"] + state_query |= Q(id__in=expired_domain_ids) + # Apply the combined query + queryset = queryset.filter(state_query) + # If there are filtered states, and expired is not one of them, domains with + # state_display of 'Expired' must be removed + if "expired" not in custom_states: + expired_domain_ids = [domain.id for domain in queryset if domain.state_display() == "Expired"] + queryset = queryset.exclude(id__in=expired_domain_ids) + + return queryset + + +def apply_sorting(queryset, request): + sort_by = request.GET.get("sort_by", "id") + order = request.GET.get("order", "asc") + if sort_by == "state_display": + objects = list(queryset) + objects.sort(key=lambda domain: domain.state_display(), reverse=(order == "desc")) + return objects + else: + if order == "desc": + sort_by = f"-{sort_by}" + return queryset.order_by(sort_by) + + +def serialize_domain(domain): + suborganization_name = None + try: + domain_info = domain.domain_info + if domain_info: + suborganization = domain_info.sub_organization + if suborganization: + suborganization_name = suborganization.name + except Domain.domain_info.RelatedObjectDoesNotExist: + domain_info = None + + return { + "id": domain.id, + "name": domain.name, + "expiration_date": domain.expiration_date, + "state": domain.state, + "state_display": domain.state_display(), + "get_state_help_text": domain.get_state_help_text(), + "action_url": reverse("domain", kwargs={"pk": domain.id}), + "action_label": ("View" if domain.state in [Domain.State.DELETED, Domain.State.ON_HOLD] else "Manage"), + "svg_icon": ("visibility" if domain.state in [Domain.State.DELETED, Domain.State.ON_HOLD] else "settings"), + "suborganization": suborganization_name, + } From 6f61eb8020b7e454153ddd0679a955034e25f594 Mon Sep 17 00:00:00 2001 From: CocoByte Date: Mon, 8 Jul 2024 21:03:04 -0600 Subject: [PATCH 030/184] Enable super-admins to edit federal_agency --- src/registrar/admin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index ef7888005..0f191de30 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1326,10 +1326,11 @@ class DomainInformationAdmin(ListHeaderAdmin, ImportExportModelAdmin): ] # Readonly fields for analysts and superusers - readonly_fields = ("other_contacts", "is_election_board", "federal_agency") + readonly_fields = ("other_contacts", "is_election_board") # Read only that we'll leverage for CISA Analysts analyst_readonly_fields = [ + "federal_agency", "creator", "type_of_work", "more_organization_information", @@ -1642,13 +1643,13 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): "current_websites", "alternative_domains", "is_election_board", - "federal_agency", "status_history", "action_needed_reason_email", ) # Read only that we'll leverage for CISA Analysts analyst_readonly_fields = [ + "federal_agency", "creator", "about_your_organization", "requested_domain", From d9f963361f7c3e002ac48b72a3f247b97f6a4c03 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 9 Jul 2024 09:27:38 -0600 Subject: [PATCH 031/184] PR suggestion --- src/registrar/tests/test_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index f8f4e1775..a610cdf71 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -621,7 +621,7 @@ class FinishUserProfileTests(TestWithUser, WebTest): self.assertContains(completed_setup_page, "Manage your domain") @less_console_noise_decorator - def test_new_user_with_empty_name_profile_feature_on(self): + def test_new_user_with_empty_name_can_add_name(self): """Tests that a new user without a name can still enter this information accordingly""" self.incomplete_regular_user.contact.first_name = None self.incomplete_regular_user.contact.last_name = None From 59ed58f72c5697a8663efd0e03686b77d7db66b0 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Tue, 9 Jul 2024 12:45:34 -0400 Subject: [PATCH 032/184] Add suborg aria label and title --- src/registrar/assets/js/get-gov.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js index fe2946867..1c0f08d10 100644 --- a/src/registrar/assets/js/get-gov.js +++ b/src/registrar/assets/js/get-gov.js @@ -1197,7 +1197,7 @@ document.addEventListener('DOMContentLoaded', function() { - ${suborganization} + ${suborganization} From ee95a1aac1bcf35596235ce8885b2778b80dd624 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Tue, 9 Jul 2024 14:14:57 -0400 Subject: [PATCH 033/184] cleanup --- src/registrar/templates/includes/header_extended.html | 1 - 1 file changed, 1 deletion(-) diff --git a/src/registrar/templates/includes/header_extended.html b/src/registrar/templates/includes/header_extended.html index f388a71bc..dcf950234 100644 --- a/src/registrar/templates/includes/header_extended.html +++ b/src/registrar/templates/includes/header_extended.html @@ -70,4 +70,3 @@ {% endblock %} - \ No newline at end of file From bf5a710754cbc5c2445d52febe357c87af499705 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Tue, 9 Jul 2024 15:22:56 -0400 Subject: [PATCH 034/184] Add edit icon to domains overview --- src/registrar/templates/includes/summary_item.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/registrar/templates/includes/summary_item.html b/src/registrar/templates/includes/summary_item.html index a2f328e1f..d2804763f 100644 --- a/src/registrar/templates/includes/summary_item.html +++ b/src/registrar/templates/includes/summary_item.html @@ -112,8 +112,11 @@ From bf4628a8bcff07b94b1f734925a31b2d6e3143c0 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Tue, 9 Jul 2024 15:31:13 -0400 Subject: [PATCH 035/184] cleanup scss --- src/registrar/assets/sass/_theme/_links.scss | 18 +++++++----------- .../templates/includes/domains_table.html | 2 +- .../templates/includes/summary_item.html | 2 +- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_links.scss b/src/registrar/assets/sass/_theme/_links.scss index 6d2e75a68..e9b71733a 100644 --- a/src/registrar/assets/sass/_theme/_links.scss +++ b/src/registrar/assets/sass/_theme/_links.scss @@ -1,18 +1,14 @@ @use "uswds-core" as *; -.dotgov-table { - a { - display: flex; - align-items: flex-start; - color: color('primary'); +.dotgov-table a, +.usa-link--icon { + display: flex; + align-items: flex-start; + color: color('primary'); - &:visited { - color: color('primary'); - } + &:visited { + color: color('primary'); } -} - -a { .usa-icon { // align icon with x height margin-top: units(0.5); diff --git a/src/registrar/templates/includes/domains_table.html b/src/registrar/templates/includes/domains_table.html index 8fbfd44e8..f89bf68c1 100644 --- a/src/registrar/templates/includes/domains_table.html +++ b/src/registrar/templates/includes/domains_table.html @@ -156,7 +156,7 @@ + {% if portfolio %} + + {% endif %}
{% if portfolio %}
@@ -167,6 +178,18 @@ + + {% if not portfolio %} + + {% endif %}