From c551a60e3acaafd15edb9684607ba9ce2323b81a Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 13 Dec 2024 12:29:08 -0700 Subject: [PATCH] Add Unit tests --- src/registrar/forms/portfolio.py | 6 +- .../models/utility/portfolio_helper.py | 5 +- src/registrar/tests/test_views_portfolio.py | 167 ++++++++++++++++++ src/registrar/views/portfolios.py | 1 - src/zap.conf | 1 + 5 files changed, 174 insertions(+), 6 deletions(-) diff --git a/src/registrar/forms/portfolio.py b/src/registrar/forms/portfolio.py index 9101bcbf8..85c0faf62 100644 --- a/src/registrar/forms/portfolio.py +++ b/src/registrar/forms/portfolio.py @@ -274,9 +274,9 @@ class BasePortfolioMemberForm(forms.Form): # Build form data based on role. form_data = { "role": role, - "member_permission_admin": member_permission.value if is_admin else None, - "domain_request_permission_admin": domain_request_permission.value if is_admin else None, - "domain_request_permission_member": domain_request_permission.value if not is_admin else None, + "member_permission_admin": getattr(member_permission, "value", None) if is_admin else None, + "domain_request_permission_admin": getattr(domain_request_permission, "value", None) if is_admin else None, + "domain_request_permission_member": getattr(domain_request_permission, "value", None) if not is_admin else None, } # Edgecase: Member uses a special form value for None called "no_access". This ensures a form selection. diff --git a/src/registrar/models/utility/portfolio_helper.py b/src/registrar/models/utility/portfolio_helper.py index 25073639b..cde28e4bd 100644 --- a/src/registrar/models/utility/portfolio_helper.py +++ b/src/registrar/models/utility/portfolio_helper.py @@ -8,6 +8,7 @@ import logging logger = logging.getLogger(__name__) + class UserPortfolioRoleChoices(models.TextChoices): """ Roles make it easier for admins to look at @@ -23,7 +24,7 @@ class UserPortfolioRoleChoices(models.TextChoices): except ValueError: logger.warning(f"Invalid portfolio role: {user_portfolio_role}") return f"Unknown ({user_portfolio_role})" - + @classmethod def get_role_description(cls, user_portfolio_role): """Returns a detailed description for a given role.""" @@ -37,7 +38,7 @@ class UserPortfolioRoleChoices(models.TextChoices): "organization domain requests and submit domain requests on behalf of the organization. Basic access " "members can’t view all members of an organization or manage them. " "Domain management can be assigned separately." - ) + ), } return descriptions.get(user_portfolio_role) diff --git a/src/registrar/tests/test_views_portfolio.py b/src/registrar/tests/test_views_portfolio.py index 834e1d049..664d50e87 100644 --- a/src/registrar/tests/test_views_portfolio.py +++ b/src/registrar/tests/test_views_portfolio.py @@ -2642,3 +2642,170 @@ class TestPortfolioInviteNewMemberView(TestWithUser, WebTest): # Validate Database has not changed invite_count_after = PortfolioInvitation.objects.count() self.assertEqual(invite_count_after, invite_count_before) + + +class TestEditPortfolioMemberView(WebTest): + + def setUp(self): + self.user = create_user() + # Create Portfolio + self.portfolio = Portfolio.objects.create(creator=self.user, organization_name="Test Portfolio") + + # Add an invited member who has been invited to manage domains + self.invited_member_email = "invited@example.com" + self.invitation = PortfolioInvitation.objects.create( + email=self.invited_member_email, + portfolio=self.portfolio, + roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER], + additional_permissions=[ + UserPortfolioPermissionChoices.VIEW_MEMBERS, + ], + ) + + # Assign permissions to the user making requests + UserPortfolioPermission.objects.create( + user=self.user, + portfolio=self.portfolio, + roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN], + additional_permissions=[ + UserPortfolioPermissionChoices.VIEW_MEMBERS, + UserPortfolioPermissionChoices.EDIT_MEMBERS, + ], + ) + + def tearDown(self): + PortfolioInvitation.objects.all().delete() + UserPortfolioPermission.objects.all().delete() + Portfolio.objects.all().delete() + User.objects.all().delete() + + @less_console_noise_decorator + @override_flag("organization_feature", active=True) + @override_flag("organization_members", active=True) + def test_edit_member_permissions_basic_to_admin(self): + """Tests converting a basic member to admin with full permissions.""" + self.client.force_login(self.user) + + # Create a basic member to edit + basic_member = create_test_user() + basic_permission = UserPortfolioPermission.objects.create( + user=basic_member, + portfolio=self.portfolio, + roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER], + additional_permissions=[UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS] + ) + + response = self.client.post( + reverse("member-permissions", kwargs={"pk": basic_permission.id}), + { + "role": UserPortfolioRoleChoices.ORGANIZATION_ADMIN, + "domain_request_permission_admin": UserPortfolioPermissionChoices.EDIT_REQUESTS, + "member_permission_admin": UserPortfolioPermissionChoices.EDIT_MEMBERS, + } + ) + + # Verify redirect and success message + self.assertEqual(response.status_code, 302) + + # Verify database changes + basic_permission.refresh_from_db() + self.assertEqual(basic_permission.roles, [UserPortfolioRoleChoices.ORGANIZATION_ADMIN]) + # We expect view permissions to be added automagically + self.assertEqual( + set(basic_permission.additional_permissions), + { + UserPortfolioPermissionChoices.EDIT_REQUESTS, + UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS, + UserPortfolioPermissionChoices.EDIT_MEMBERS, + UserPortfolioPermissionChoices.VIEW_MEMBERS, + } + ) + + @less_console_noise_decorator + @override_flag("organization_feature", active=True) + @override_flag("organization_members", active=True) + def test_edit_member_permissions_validation(self): + """Tests form validation for required fields based on role.""" + self.client.force_login(self.user) + + member = create_test_user() + permission = UserPortfolioPermission.objects.create( + user=member, + portfolio=self.portfolio, + roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER] + ) + + # Test missing required admin permissions + response = self.client.post( + reverse("member-permissions", kwargs={"pk": permission.id}), + { + "role": UserPortfolioRoleChoices.ORGANIZATION_ADMIN, + # Missing required admin fields + } + ) + + self.assertEqual(response.status_code, 200) + self.assertEqual( + response.context["form"].errors["domain_request_permission_admin"][0], + "Admin domain request permission is required" + ) + self.assertEqual( + response.context["form"].errors["member_permission_admin"][0], + "Admin member permission is required" + ) + + @less_console_noise_decorator + @override_flag("organization_feature", active=True) + @override_flag("organization_members", active=True) + def test_edit_invited_member_permissions(self): + """Tests editing permissions for an invited (but not yet joined) member.""" + self.client.force_login(self.user) + + # Test updating invitation permissions + response = self.client.post( + reverse("invitedmember-permissions", kwargs={"pk": self.invitation.id}), + { + "role": UserPortfolioRoleChoices.ORGANIZATION_ADMIN, + "domain_request_permission_admin": UserPortfolioPermissionChoices.EDIT_REQUESTS, + "member_permission_admin": UserPortfolioPermissionChoices.EDIT_MEMBERS, + } + ) + + self.assertEqual(response.status_code, 302) + + # Verify invitation was updated + self.invitation.refresh_from_db() + self.assertEqual(self.invitation.roles, [UserPortfolioRoleChoices.ORGANIZATION_ADMIN]) + self.assertEqual( + set(self.invitation.additional_permissions), + { + UserPortfolioPermissionChoices.EDIT_REQUESTS, + UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS, + UserPortfolioPermissionChoices.EDIT_MEMBERS, + UserPortfolioPermissionChoices.VIEW_MEMBERS, + } + ) + + @less_console_noise_decorator + @override_flag("organization_feature", active=True) + @override_flag("organization_members", active=True) + def test_admin_removing_own_admin_role(self): + """Tests an admin removing their own admin role redirects to home.""" + self.client.force_login(self.user) + + # Get the user's admin permission + admin_permission = UserPortfolioPermission.objects.get( + user=self.user, + portfolio=self.portfolio + ) + + response = self.client.post( + reverse("member-permissions", kwargs={"pk": admin_permission.id}), + { + "role": UserPortfolioRoleChoices.ORGANIZATION_MEMBER, + "domain_request_permission_member": UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS, + } + ) + + self.assertEqual(response.status_code, 302) + self.assertEqual(response["Location"], reverse("home")) diff --git a/src/registrar/views/portfolios.py b/src/registrar/views/portfolios.py index 95b7238ff..53c500f51 100644 --- a/src/registrar/views/portfolios.py +++ b/src/registrar/views/portfolios.py @@ -10,7 +10,6 @@ from registrar.models import Portfolio, User from registrar.models.portfolio_invitation import PortfolioInvitation from registrar.models.user_portfolio_permission import UserPortfolioPermission from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices -from registrar.utility.email import EmailSendingError from registrar.views.utility.mixins import PortfolioMemberPermission from registrar.views.utility.permission_views import ( PortfolioDomainRequestsPermissionView, diff --git a/src/zap.conf b/src/zap.conf index 65468773a..a0a60bdc7 100644 --- a/src/zap.conf +++ b/src/zap.conf @@ -70,6 +70,7 @@ 10038 OUTOFSCOPE http://app:8080/org-name-address 10038 OUTOFSCOPE http://app:8080/domain_requests/ 10038 OUTOFSCOPE http://app:8080/domains/ +10038 OUTOFSCOPE http://app:8080/domains/edit 10038 OUTOFSCOPE http://app:8080/organization/ 10038 OUTOFSCOPE http://app:8080/permissions 10038 OUTOFSCOPE http://app:8080/suborganization/