diff --git a/src/registrar/admin.py b/src/registrar/admin.py index f0f8a3673..fdadde436 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1893,6 +1893,7 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): "suborganization_city", "suborganization_state_territory", ] + def get_fieldsets(self, request, obj=None): fieldsets = super().get_fieldsets(request, obj) diff --git a/src/registrar/forms/domain_request_wizard.py b/src/registrar/forms/domain_request_wizard.py index 62b9056bf..72421e7c9 100644 --- a/src/registrar/forms/domain_request_wizard.py +++ b/src/registrar/forms/domain_request_wizard.py @@ -26,6 +26,7 @@ class RequestingEntityForm(RegistrarForm): and some (hidden by default) input fields that allow the user to request for a suborganization. All of these fields are not required by default, but as we use javascript to conditionally show and hide some of these, they then become required in certain circumstances.""" + sub_organization = forms.ModelChoiceField( label="Suborganization name", # not required because this field won't be filled out unless diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index d7c3a950b..8ac12e085 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -6,7 +6,6 @@ from django.conf import settings from django.db import models from django_fsm import FSMField, transition # type: ignore from django.utils import timezone -from waffle import flag_is_active from registrar.models.domain import Domain from registrar.models.federal_agency import FederalAgency from registrar.models.utility.generic_helper import CreateOrUpdateOrganizationTypeHelper diff --git a/src/registrar/templates/emails/submission_confirmation.txt b/src/registrar/templates/emails/submission_confirmation.txt index 911269bf1..ef9736a9d 100644 --- a/src/registrar/templates/emails/submission_confirmation.txt +++ b/src/registrar/templates/emails/submission_confirmation.txt @@ -31,11 +31,7 @@ THANK YOU ---------------------------------------------------------------- -{% if is_org_user %} - {% include 'emails/includes/portfolio_domain_request_summary.txt' %} -{% else %} - {% include 'emails/includes/domain_request_summary.txt' %} -{% endif %} +{% if is_org_user %}{% include 'emails/includes/portfolio_domain_request_summary.txt' %}{% else %}{% include 'emails/includes/domain_request_summary.txt' %}{% endif %} ---------------------------------------------------------------- The .gov team diff --git a/src/registrar/tests/test_views_portfolio.py b/src/registrar/tests/test_views_portfolio.py index 9d7122451..d770dd677 100644 --- a/src/registrar/tests/test_views_portfolio.py +++ b/src/registrar/tests/test_views_portfolio.py @@ -2,6 +2,7 @@ from django.urls import reverse from api.tests.common import less_console_noise_decorator from registrar.config import settings from registrar.models import Portfolio, SeniorOfficial +from unittest.mock import MagicMock from django_webtest import WebTest # type: ignore from registrar.models import ( DomainRequest, @@ -9,12 +10,14 @@ from registrar.models import ( DomainInformation, UserDomainRole, User, + Suborganization, + AllowedEmail, ) from registrar.models.portfolio_invitation import PortfolioInvitation from registrar.models.user_group import UserGroup from registrar.models.user_portfolio_permission import UserPortfolioPermission from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices -from .common import MockSESClient, completed_domain_request, create_test_user +from .common import MockSESClient, completed_domain_request, create_test_user, create_user from waffle.testutils import override_flag from django.contrib.sessions.middleware import SessionMiddleware import boto3_mocking # type: ignore @@ -1392,86 +1395,283 @@ class TestPortfolio(WebTest): class TestRequestingEntity(WebTest): """The requesting entity page is a domain request form that only exists within the context of a portfolio.""" + def setUp(self): super().setUp() self.client = Client() - self.user = create_test_user() - self.domain, _ = Domain.objects.get_or_create(name="igorville.gov") + self.user = create_user() self.portfolio, _ = Portfolio.objects.get_or_create(creator=self.user, organization_name="Hotel California") - self.role, _ = UserDomainRole.objects.get_or_create( - user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER + self.portfolio_2, _ = Portfolio.objects.get_or_create(creator=self.user, organization_name="Hotel Alaska") + self.suborganization, _ = Suborganization.objects.get_or_create( + name="Rocky road", + portfolio=self.portfolio, + ) + self.suborganization_2, _ = Suborganization.objects.get_or_create( + name="Vanilla", + portfolio=self.portfolio, + ) + self.unrelated_suborganization, _ = Suborganization.objects.get_or_create( + name="Cold", + portfolio=self.portfolio_2, + ) + self.portfolio_role = UserPortfolioPermission.objects.create( + portfolio=self.portfolio, user=self.user, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN] ) # Login the current user self.app.set_user(self.user.username) + self.mock_client_class = MagicMock() + self.mock_client = self.mock_client_class.return_value + def tearDown(self): UserDomainRole.objects.all().delete() DomainRequest.objects.all().delete() DomainInformation.objects.all().delete() Domain.objects.all().delete() UserPortfolioPermission.objects.all().delete() + Suborganization.objects.all().delete() Portfolio.objects.all().delete() User.objects.all().delete() super().tearDown() - - # need a test that starts a new domain request - @override_flag("organization_feature", active=True) - @override_flag("organization_requests", active=True) - def test_requesting_entity_page(self): - """Tests that the requesting entity page loads correctly""" - pass @override_flag("organization_feature", active=True) @override_flag("organization_requests", active=True) - def test_requesting_entity_page_submission(self): - """Tests that you can submit a form on this page""" - pass + @less_console_noise_decorator + def test_requesting_entity_page_new_request(self): + """Tests that the requesting entity page loads correctly when a new request is started""" + + response = self.app.get(reverse("domain-request:")) + + # Navigate past the intro page + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + intro_form = response.forms[0] + response = intro_form.submit().follow() + + # Test the requesting entiy page + self.assertContains(response, "Who will use the domain you’re requesting?") + self.assertContains(response, "Add suborganization information") + # We expect to see the portfolio name in two places: + # the header, and as one of the radio button options. + self.assertContains(response, self.portfolio.organization_name, count=2) + + # We expect the dropdown list to contain the suborganizations that currently exist on this portfolio + self.assertContains(response, self.suborganization.name, count=1) + self.assertContains(response, self.suborganization_2.name, count=1) + + # However, we should only see suborgs that are on the actual portfolio + self.assertNotContains(response, self.unrelated_suborganization.name) @override_flag("organization_feature", active=True) @override_flag("organization_requests", active=True) + @less_console_noise_decorator + def test_requesting_entity_page_existing_suborg_submission(self): + """Tests that you can submit a form on this page and set a suborg""" + response = self.app.get(reverse("domain-request:")) + + # Navigate past the intro page + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + form = response.forms[0] + response = form.submit().follow() + + # Check that we're on the right page + self.assertContains(response, "Who will use the domain you’re requesting?") + form = response.forms[0] + + # Test selecting an existing suborg + form["portfolio_requesting_entity-is_suborganization"] = True + form["portfolio_requesting_entity-sub_organization"] = f"{self.suborganization.id}" + form["portfolio_requesting_entity-is_custom_suborganization"] = False + + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + response = form.submit().follow() + + # Ensure that the post occurred successfully by checking that we're on the following page. + self.assertContains(response, "Current websites") + created_domain_request_exists = DomainRequest.objects.filter( + organization_name__isnull=True, sub_organization=self.suborganization + ).exists() + self.assertTrue(created_domain_request_exists) + + @override_flag("organization_feature", active=True) + @override_flag("organization_requests", active=True) + @less_console_noise_decorator + def test_requesting_entity_page_new_suborg_submission(self): + """Tests that you can submit a form on this page and set a new suborg""" + response = self.app.get(reverse("domain-request:")) + + # Navigate past the intro page + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + form = response.forms[0] + response = form.submit().follow() + + # Check that we're on the right page + self.assertContains(response, "Who will use the domain you’re requesting?") + form = response.forms[0] + + # Test selecting an existing suborg + form["portfolio_requesting_entity-is_suborganization"] = True + form["portfolio_requesting_entity-is_custom_suborganization"] = True + form["portfolio_requesting_entity-sub_organization"] = "" + + form["portfolio_requesting_entity-requested_suborganization"] = "moon" + form["portfolio_requesting_entity-suborganization_city"] = "kepler" + form["portfolio_requesting_entity-suborganization_state_territory"] = "AL" + + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + response = form.submit().follow() + + # Ensure that the post occurred successfully by checking that we're on the following page. + self.assertContains(response, "Current websites") + created_domain_request_exists = DomainRequest.objects.filter( + organization_name__isnull=True, + sub_organization__isnull=True, + requested_suborganization="moon", + suborganization_city="kepler", + suborganization_state_territory=DomainRequest.StateTerritoryChoices.ALABAMA, + ).exists() + self.assertTrue(created_domain_request_exists) + + @override_flag("organization_feature", active=True) + @override_flag("organization_requests", active=True) + @less_console_noise_decorator + def test_requesting_entity_page_organization_submission(self): + """Tests submitting an organization on the requesting org form""" + response = self.app.get(reverse("domain-request:")) + + # Navigate past the intro page + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + form = response.forms[0] + response = form.submit().follow() + + # Check that we're on the right page + self.assertContains(response, "Who will use the domain you’re requesting?") + form = response.forms[0] + + # Test selecting an existing suborg + form["portfolio_requesting_entity-is_suborganization"] = False + + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + response = form.submit().follow() + + # Ensure that the post occurred successfully by checking that we're on the following page. + self.assertContains(response, "Current websites") + created_domain_request_exists = DomainRequest.objects.filter( + organization_name=self.portfolio.organization_name, + ).exists() + self.assertTrue(created_domain_request_exists) + + @override_flag("organization_feature", active=True) + @override_flag("organization_requests", active=True) + @less_console_noise_decorator def test_requesting_entity_page_errors(self): """Tests that we get the expected form errors on requesting entity""" domain_request = completed_domain_request(user=self.user, portfolio=self.portfolio) - UserPortfolioPermission.objects.create(portfolio=self.portfolio, user=self.user, roles=[ - UserPortfolioRoleChoices.ORGANIZATION_ADMIN - ]) + UserPortfolioPermission.objects.create( + portfolio=self.portfolio, user=self.user, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN] + ) response = self.app.get(reverse("edit-domain-request", kwargs={"id": domain_request.pk})).follow() form = response.forms[0] session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) # Test missing suborganization selection - form['portfolio_requesting_entity-is_suborganization'] = True - form['portfolio_requesting_entity-sub_organization'] = "" + form["portfolio_requesting_entity-is_suborganization"] = True + form["portfolio_requesting_entity-sub_organization"] = "" response = form.submit() self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.assertContains(response, "Select a suborganization.", status_code=200) # Test missing custom suborganization details - form['portfolio_requesting_entity-is_custom_suborganization'] = True + form["portfolio_requesting_entity-is_custom_suborganization"] = True response = form.submit() self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.assertContains(response, "Enter details for your organization name.", status_code=200) self.assertContains(response, "Enter details for your city.", status_code=200) self.assertContains(response, "Enter details for your state or territory.", status_code=200) - domain_request.delete() - - @override_flag("organization_feature", active=True) - @override_flag("organization_requests", active=True) - def test_requesting_entity_submission_email_sent(self, mock_send_email): - """Tests that an email is sent out on form submission""" - pass + @override_flag("organization_feature", active=True) + @override_flag("organization_requests", active=True) + @boto3_mocking.patching + @less_console_noise_decorator + def test_requesting_entity_submission_email_sent(self): + """Tests that an email is sent out on successful form submission""" + AllowedEmail.objects.create(email=self.user.email) + domain_request = completed_domain_request( + user=self.user, + # This is the additional details field + has_anything_else=True, + ) + domain_request.portfolio = self.portfolio + domain_request.requested_suborganization = "moon" + domain_request.suborganization_city = "kepler" + domain_request.suborganization_state_territory = DomainRequest.StateTerritoryChoices.ALABAMA + domain_request.save() + domain_request.refresh_from_db() + + with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): + domain_request.submit() + _, kwargs = self.mock_client.send_email.call_args + body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] + + self.assertNotIn("Anything else", body) + self.assertIn("kepler, AL", body) + self.assertIn("Requesting entity:", body) + self.assertIn("Administrators from your organization:", body) @override_flag("organization_feature", active=True) @override_flag("organization_requests", active=True) + @boto3_mocking.patching + @less_console_noise_decorator def test_requesting_entity_viewonly(self): """Tests the review steps page on under our viewonly context""" - pass + domain_request = completed_domain_request( + user=create_test_user(), + # This is the additional details field + has_anything_else=True, + ) + domain_request.portfolio = self.portfolio + domain_request.requested_suborganization = "moon" + domain_request.suborganization_city = "kepler" + domain_request.suborganization_state_territory = DomainRequest.StateTerritoryChoices.ALABAMA + domain_request.save() + domain_request.refresh_from_db() + + domain_request.submit() + + response = self.app.get(reverse("domain-request-status-viewonly", kwargs={"pk": domain_request.pk})) + self.assertContains(response, "Requesting entity") + self.assertContains(response, "moon") + self.assertContains(response, "kepler, AL") @override_flag("organization_feature", active=True) @override_flag("organization_requests", active=True) + @boto3_mocking.patching + @less_console_noise_decorator def test_requesting_entity_manage(self): """Tests the review steps page on under our manage context""" - pass + domain_request = completed_domain_request( + user=self.user, + # This is the additional details field + has_anything_else=True, + ) + domain_request.portfolio = self.portfolio + domain_request.requested_suborganization = "moon" + domain_request.suborganization_city = "kepler" + domain_request.suborganization_state_territory = DomainRequest.StateTerritoryChoices.ALABAMA + domain_request.save() + domain_request.refresh_from_db() + + domain_request.submit() + + response = self.app.get(reverse("domain-request-status", kwargs={"pk": domain_request.pk})) + self.assertContains(response, "Requesting entity") + self.assertContains(response, "moon") + self.assertContains(response, "kepler, AL") diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py index 0a4728d81..bdb496c9e 100644 --- a/src/registrar/views/domain_request.py +++ b/src/registrar/views/domain_request.py @@ -11,7 +11,6 @@ from registrar.forms import domain_request_wizard as forms from registrar.forms.utility.wizard_form_helper import request_step_list from registrar.models import DomainRequest from registrar.models.contact import Contact -from registrar.models.suborganization import Suborganization from registrar.models.user import User from registrar.views.utility import StepsHelper from registrar.views.utility.permission_views import DomainRequestPermissionDeleteView @@ -601,14 +600,16 @@ class RequestingEntity(DomainRequestWizard): sub_organization = cleaned_data.get("sub_organization") requested_suborganization = cleaned_data.get("requested_suborganization") - # If no suborganization presently exists but the user filled out org information then create a suborg automatically. + # If no suborganization presently exists but the user filled out + # org information then create a suborg automatically. if is_suborganization and (sub_organization or requested_suborganization): # Cleanup the organization name field, as this isn't for suborganizations. self.domain_request.organization_name = None self.domain_request.sub_organization = sub_organization else: # If the user doesn't intend to create a suborg, simply don't make one and do some data cleanup - self.domain_request.organization_name = self.domain_request.portfolio.organization_name + if self.domain_request.portfolio: + self.domain_request.organization_name = self.domain_request.portfolio.organization_name self.domain_request.sub_organization = None self.domain_request.requested_suborganization = None