diff --git a/src/registrar/config/urls.py b/src/registrar/config/urls.py index b25f6e0ea..e7f1883cd 100644 --- a/src/registrar/config/urls.py +++ b/src/registrar/config/urls.py @@ -60,15 +60,8 @@ for step, view in [ (Step.ADDITIONAL_DETAILS, views.AdditionalDetails), (Step.REQUIREMENTS, views.Requirements), (Step.REVIEW, views.Review), - # Portfolio steps (PortfolioDomainRequestStep.REQUESTING_ENTITY, views.RequestingEntity), - # (PortfolioDomainRequestStep.CURRENT_SITES, views.CurrentSites), - # (PortfolioDomainRequestStep.DOTGOV_DOMAIN, views.DotgovDomain), - # (PortfolioDomainRequestStep.PURPOSE, views.Purpose), - # (PortfolioDomainRequestStep.ADDITIONAL_DETAILS, views.AdditionalDetails), - # (PortfolioDomainRequestStep.REQUIREMENTS, views.Requirements), - # (PortfolioDomainRequestStep.REVIEW, views.Review), ]: domain_request_urls.append(path(f"{step}/", view.as_view(), name=step)) diff --git a/src/registrar/templates/domain_request_requesting_entity.html b/src/registrar/templates/domain_request_requesting_entity.html index 375f7ce74..ed8dd771c 100644 --- a/src/registrar/templates/domain_request_requesting_entity.html +++ b/src/registrar/templates/domain_request_requesting_entity.html @@ -13,4 +13,4 @@ {% input_with_errors forms.0.organization_name %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/src/registrar/tests/test_views_request.py b/src/registrar/tests/test_views_request.py index 8530859e2..6ff9ae218 100644 --- a/src/registrar/tests/test_views_request.py +++ b/src/registrar/tests/test_views_request.py @@ -2742,6 +2742,64 @@ class DomainRequestTests(TestWithUser, WebTest): self.assertContains(review_page, "toggle-submit-domain-request") self.assertContains(review_page, "Your request form is incomplete") + @override_flag("organization_feature", active=True) + @override_flag("organization_requests", active=True) + def test_portfolio_user_missing_edit_permissions(self): + """Tests that a portfolio user without edit request permissions cannot edit or add new requests""" + portfolio, _ = Portfolio.objects.get_or_create(creator=self.user, organization_name="Test Portfolio") + portfolio_perm, _ = UserPortfolioPermission.objects.get_or_create( + user=self.user, portfolio=portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER] + ) + # This user should be forbidden from creating new domain requests + intro_page = self.app.get(reverse("domain-request:"), expect_errors=True) + self.assertEqual(intro_page.status_code, 403) + + # This user should also be forbidden from editing existing ones + domain_request = completed_domain_request(user=self.user) + edit_page = self.app.get(reverse("edit-domain-request", kwargs={"id": domain_request.pk}), expect_errors=True) + self.assertEqual(edit_page.status_code, 403) + + # Cleanup + portfolio_perm.delete() + portfolio.delete() + + @override_flag("organization_feature", active=True) + @override_flag("organization_requests", active=True) + def test_portfolio_user_with_edit_permissions(self): + """Tests that a portfolio user with edit request permissions can edit and add new requests""" + portfolio, _ = Portfolio.objects.get_or_create(creator=self.user, organization_name="Test Portfolio") + portfolio_perm, _ = UserPortfolioPermission.objects.get_or_create( + user=self.user, portfolio=portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN] + ) + + # This user should be allowed to create new domain requests + intro_page = self.app.get(reverse("domain-request:")) + self.assertEqual(intro_page.status_code, 200) + + # This user should also be allowed to edit existing ones + domain_request = completed_domain_request(user=self.user) + edit_page = self.app.get(reverse("edit-domain-request", kwargs={"id": domain_request.pk})) + self.assertEqual(edit_page.status_code, 200) + + # Cleanup + portfolio_perm.delete() + portfolio.delete() + + def test_non_creator_access(self): + """Tests that a user cannot edit a domain request they didn't create""" + other_user = User.objects.create_user(username="other_user", password="password") + domain_request = completed_domain_request(user=other_user) + + edit_page = self.app.get(reverse("edit-domain-request", kwargs={"id": domain_request.pk}), expect_errors=True) + self.assertEqual(edit_page.status_code, 403) + + def test_creator_access(self): + """Tests that a user can edit a domain request they created""" + domain_request = completed_domain_request(user=self.user) + + edit_page = self.app.get(reverse("edit-domain-request", kwargs={"id": domain_request.pk})) + self.assertEqual(edit_page.status_code, 200) + class DomainRequestTestDifferentStatuses(TestWithUser, WebTest): def setUp(self): @@ -2904,7 +2962,7 @@ class DomainRequestTestDifferentStatuses(TestWithUser, WebTest): self.assertNotContains(home_page, "city.gov") -class TestWizardUnlockingSteps(TestWithUser, WebTest): +class TestDomainRequestWizard(TestWithUser, WebTest): def setUp(self): super().setUp() self.app.set_user(self.user.username) @@ -2928,7 +2986,10 @@ class TestWizardUnlockingSteps(TestWithUser, WebTest): def test_unlocked_steps_full_domain_request(self): """Test when all fields in the domain request are filled.""" - domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.STARTED, user=self.user) + domain_request = completed_domain_request( + status=DomainRequest.DomainRequestStatus.STARTED, + user=self.user + ) domain_request.anything_else = False domain_request.has_anything_else_text = False domain_request.save() @@ -3026,6 +3087,93 @@ class TestWizardUnlockingSteps(TestWithUser, WebTest): else: self.fail(f"Expected a redirect, but got a different response: {response}") + @less_console_noise_decorator + @override_flag("organization_feature", active=True) + @override_flag("organization_requests", active=True) + def test_wizard_steps_portfolio(self): + """ + Tests the behavior of the domain request wizard for portfolios. + Ensures that: + - The user can access the organization page. + - The expected number of steps are locked/unlocked (implicit test for expected steps). + - The user lands on the "Requesting entity" page + - The user does not see the Domain and Domain requests buttons + """ + + # This should unlock 4 steps by default. + # Purpose, .gov domain, current websites, and requirements for operating + domain_request = completed_domain_request( + status=DomainRequest.DomainRequestStatus.STARTED, + user=self.user, + ) + + federal_agency = FederalAgency.objects.get(agency="Non-Federal Agency") + # Add a portfolio + portfolio = Portfolio.objects.create( + creator=self.user, + organization_name="test portfolio", + federal_agency=federal_agency, + ) + + user_portfolio_permission = UserPortfolioPermission.objects.create( + user=self.user, + portfolio=portfolio, + roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN], + ) + + response = self.app.get(f"/domain-request/{domain_request.id}/edit/") + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + # Check if the response is a redirect + if response.status_code == 302: + # Follow the redirect manually + try: + detail_page = response.follow() + + self.wizard.get_context_data() + except Exception as err: + # Handle any potential errors while following the redirect + self.fail(f"Error following the redirect {err}") + + # Now 'detail_page' contains the response after following the redirect + self.assertEqual(detail_page.status_code, 200) + + # Assert that we're on the organization page + self.assertContains(detail_page, portfolio.organization_name) + print(f"what is the page? {detail_page}") + + # We should only see one unlocked step + self.assertContains(detail_page, "#check_circle", count=4) + + # One pages should still be locked (additional details) + self.assertContains(detail_page, "#lock", 1) + + # The current option should be selected + self.assertContains(detail_page, "usa-current", count=1) + + # We default to the requesting entity page + expected_url = reverse("domain-request:requesting_entity") + # This returns the entire url, thus "in" + self.assertIn(expected_url, detail_page.request.url) + + # We shouldn't show the "domains" and "domain requests" buttons + # on this page. + self.assertNotContains(detail_page, "Domains") + self.assertNotContains(detail_page, "Domain requests") + else: + self.fail(f"Expected a redirect, but got a different response: {response}") + + # Data cleanup + user_portfolio_permission.delete() + portfolio.delete() + federal_agency.delete() + domain_request.delete() + class TestPortfolioDomainRequestViewonly(TestWithUser, WebTest): diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py index 263e57be1..1a5cf48cb 100644 --- a/src/registrar/views/domain_request.py +++ b/src/registrar/views/domain_request.py @@ -136,8 +136,8 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): portfolio=portfolio, ) - # TODO - is this needed? - if portfolio: + # Question for reviewers: we should probably be doing this right? + if portfolio and not self._domain_request.generic_org_type: self._domain_request.generic_org_type = portfolio.organization_type self._domain_request.save() else: @@ -505,17 +505,14 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): # which button did the user press? button: str = request.POST.get("submit_button", "") - # If a user hits the new request url directly + if "new_request" not in request.session: request.session["new_request"] = True + # if user has acknowledged the intro message if button == "intro_acknowledge": - if request.path_info == self.NEW_URL_NAME: - - if self.request.session["new_request"] is True: - # This will trigger the domain_request getter into creating a new DomainRequest - del self.storage - return self.goto(self.steps.first) + # Split into a function: C901 'DomainRequestWizard.post' is too complex (11) + self.handle_intro_acknowledge(request) # if accessing this class directly, redirect to the first step if self.__class__ == DomainRequestWizard: @@ -546,6 +543,14 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView): # otherwise, proceed as normal return self.goto_next_step() + def handle_intro_acknowledge(self, request): + """If we are starting a new request, clear storage + and redirect to the first step""" + if request.path_info == self.NEW_URL_NAME: + if self.request.session["new_request"] is True: + del self.storage + return self.goto(self.steps.first) + def save(self, forms: list): """ Unpack the form responses onto the model object properties. @@ -576,11 +581,13 @@ class PortfolioDomainRequestWizard(DomainRequestWizard): self.steps = StepsHelper(self) self._domain_request = None # for caching + # Portfolio pages class RequestingEntity(DomainRequestWizard): template_name = "domain_request_requesting_entity.html" forms = [forms.RequestingEntityForm] + # Non-portfolio pages class OrganizationType(DomainRequestWizard): template_name = "domain_request_org_type.html"