+ +
+Members
diff --git a/src/registrar/templates/profile.html b/src/registrar/templates/profile.html index 6e1e7781f..7d365d9c1 100644 --- a/src/registrar/templates/profile.html +++ b/src/registrar/templates/profile.html @@ -51,11 +51,11 @@ >
+
Add contact information
-
+
.Gov domain registrants must maintain accurate contact information in the .gov registrar.
Before you can manage your domain, we need you to add your contact information.
diff --git a/src/registrar/tests/test_models.py b/src/registrar/tests/test_models.py
index 53206359b..0c1bdec2a 100644
--- a/src/registrar/tests/test_models.py
+++ b/src/registrar/tests/test_models.py
@@ -824,6 +824,92 @@ def test_user_with_portfolio_roles_but_no_portfolio(self):
cm.exception.message, "When portfolio roles or additional permissions are assigned, portfolio is required."
)
+ @less_console_noise_decorator
+ def test_get_active_requests_count_in_portfolio_returns_zero_if_no_portfolio(self):
+ # There is no portfolio referenced in session so should return 0
+ request = self.factory.get("/")
+ request.session = {}
+
+ count = self.user.get_active_requests_count_in_portfolio(request)
+ self.assertEqual(count, 0)
+
+ @less_console_noise_decorator
+ def test_get_active_requests_count_in_portfolio_returns_count_if_portfolio(self):
+ request = self.factory.get("/")
+ request.session = {"portfolio": self.portfolio}
+
+ # Create active requests
+ domain_1, _ = DraftDomain.objects.get_or_create(name="meoward1.gov")
+ domain_2, _ = DraftDomain.objects.get_or_create(name="meoward2.gov")
+ domain_3, _ = DraftDomain.objects.get_or_create(name="meoward3.gov")
+ domain_4, _ = DraftDomain.objects.get_or_create(name="meoward4.gov")
+
+ # Create 3 active requests + 1 that isn't
+ DomainRequest.objects.create(
+ creator=self.user,
+ requested_domain=domain_1,
+ status=DomainRequest.DomainRequestStatus.SUBMITTED,
+ portfolio=self.portfolio,
+ )
+ DomainRequest.objects.create(
+ creator=self.user,
+ requested_domain=domain_2,
+ status=DomainRequest.DomainRequestStatus.IN_REVIEW,
+ portfolio=self.portfolio,
+ )
+ DomainRequest.objects.create(
+ creator=self.user,
+ requested_domain=domain_3,
+ status=DomainRequest.DomainRequestStatus.ACTION_NEEDED,
+ portfolio=self.portfolio,
+ )
+ DomainRequest.objects.create( # This one should not be counted
+ creator=self.user,
+ requested_domain=domain_4,
+ status=DomainRequest.DomainRequestStatus.REJECTED,
+ portfolio=self.portfolio,
+ )
+
+ count = self.user.get_active_requests_count_in_portfolio(request)
+ self.assertEqual(count, 3)
+
+ @less_console_noise_decorator
+ def test_is_only_admin_of_portfolio_returns_true(self):
+ # Create user as the only admin of the portfolio
+ UserPortfolioPermission.objects.create(
+ user=self.user, portfolio=self.portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
+ )
+ self.assertTrue(self.user.is_only_admin_of_portfolio(self.portfolio))
+
+ @less_console_noise_decorator
+ def test_is_only_admin_of_portfolio_returns_false_if_no_admins(self):
+ # No admin for the portfolio
+ self.assertFalse(self.user.is_only_admin_of_portfolio(self.portfolio))
+
+ @less_console_noise_decorator
+ def test_is_only_admin_of_portfolio_returns_false_if_multiple_admins(self):
+ # Create multiple admins for the same portfolio
+ UserPortfolioPermission.objects.create(
+ user=self.user, portfolio=self.portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
+ )
+ # Create another user within this test
+ other_user = User.objects.create(email="second_admin@igorville.gov", username="second_admin")
+ UserPortfolioPermission.objects.create(
+ user=other_user, portfolio=self.portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
+ )
+ self.assertFalse(self.user.is_only_admin_of_portfolio(self.portfolio))
+
+ @less_console_noise_decorator
+ def test_is_only_admin_of_portfolio_returns_false_if_user_not_admin(self):
+ # Create other_user for same portfolio and is given admin access
+ other_user = User.objects.create(email="second_admin@igorville.gov", username="second_admin")
+
+ UserPortfolioPermission.objects.create(
+ user=other_user, portfolio=self.portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
+ )
+ # User doesn't have admin access so should return false
+ self.assertFalse(self.user.is_only_admin_of_portfolio(self.portfolio))
+
class TestContact(TestCase):
@less_console_noise_decorator
diff --git a/src/registrar/tests/test_views_portfolio.py b/src/registrar/tests/test_views_portfolio.py
index 402d23b70..772779903 100644
--- a/src/registrar/tests/test_views_portfolio.py
+++ b/src/registrar/tests/test_views_portfolio.py
@@ -2,8 +2,9 @@
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 unittest.mock import MagicMock, patch
from django_webtest import WebTest # type: ignore
+from django.core.handlers.wsgi import WSGIRequest
from registrar.models import (
DomainRequest,
Domain,
@@ -959,7 +960,7 @@ def test_can_view_member_page_when_user_has_edit_members(self):
)
# Assert buttons and links within the page are correct
- self.assertContains(response, "usa-button--more-actions") # test that 3 dot is present
+ self.assertContains(response, "wrapper-delete-action") # test that 3 dot is present
self.assertContains(response, "sprite.svg#edit") # test that Edit link is present
self.assertContains(response, "sprite.svg#settings") # test that Manage link is present
self.assertNotContains(response, "sprite.svg#visibility") # test that View link is not present
@@ -1077,9 +1078,8 @@ def test_can_view_invitedmember_page_when_user_has_edit_members(self):
self.assertContains(
response, 'This member does not manage any domains. To assign this member a domain, click "Manage"'
)
-
# Assert buttons and links within the page are correct
- self.assertContains(response, "usa-button--more-actions") # test that 3 dot is present
+ self.assertContains(response, "wrapper-delete-action") # test that 3 dot is present
self.assertContains(response, "sprite.svg#edit") # test that Edit link is present
self.assertContains(response, "sprite.svg#settings") # test that Manage link is present
self.assertNotContains(response, "sprite.svg#visibility") # test that View link is not present
@@ -1392,6 +1392,510 @@ def test_org_user_cannot_delete_others_domain_requests(self):
self.assertTrue(DomainRequest.objects.filter(pk=domain_request.pk).exists())
domain_request.delete()
+ @less_console_noise_decorator
+ @override_flag("organization_feature", active=True)
+ @override_flag("organization_members", active=True)
+ def test_members_table_contains_hidden_permissions_js_hook(self):
+ # In the members_table.html we use data-has-edit-permission as a boolean
+ # to indicate if a user has permission to edit members in the specific portfolio
+
+ # 1. User w/ edit permission
+ UserPortfolioPermission.objects.get_or_create(
+ user=self.user,
+ portfolio=self.portfolio,
+ roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
+ additional_permissions=[
+ UserPortfolioPermissionChoices.VIEW_MEMBERS,
+ UserPortfolioPermissionChoices.EDIT_MEMBERS,
+ ],
+ )
+
+ # Create a member under same portfolio
+ member_email = "a_member@example.com"
+ member, _ = User.objects.get_or_create(username="a_member", email=member_email)
+
+ UserPortfolioPermission.objects.get_or_create(
+ user=member,
+ portfolio=self.portfolio,
+ roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER],
+ )
+
+ # I log in as the User so I can see the Members Table
+ self.client.force_login(self.user)
+
+ # Specifically go to the Member Table page
+ response = self.client.get(reverse("members"))
+
+ self.assertContains(response, 'data-has-edit-permission="True"')
+
+ # 2. User w/o edit permission (additional permission of EDIT_MEMBERS removed)
+ permission = UserPortfolioPermission.objects.get(user=self.user, portfolio=self.portfolio)
+
+ # Remove the EDIT_MEMBERS additional permission
+ permission.additional_permissions = [
+ perm for perm in permission.additional_permissions if perm != UserPortfolioPermissionChoices.EDIT_MEMBERS
+ ]
+
+ # Save the updated permissions list
+ permission.save()
+
+ # Re-fetch the page to check for updated permissions
+ response = self.client.get(reverse("members"))
+
+ self.assertContains(response, 'data-has-edit-permission="False"')
+
+ @less_console_noise_decorator
+ @override_flag("organization_feature", active=True)
+ @override_flag("organization_members", active=True)
+ def test_member_page_has_kebab_wrapper_for_member_if_user_has_edit_permission(self):
+ """Test that the kebab wrapper displays for a member with edit permissions"""
+
+ # I'm a user
+ UserPortfolioPermission.objects.get_or_create(
+ user=self.user,
+ portfolio=self.portfolio,
+ roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
+ additional_permissions=[
+ UserPortfolioPermissionChoices.VIEW_MEMBERS,
+ UserPortfolioPermissionChoices.EDIT_MEMBERS,
+ ],
+ )
+
+ # Create a member under same portfolio
+ member_email = "a_member@example.com"
+ member, _ = User.objects.get_or_create(username="a_member", email=member_email)
+
+ upp, _ = UserPortfolioPermission.objects.get_or_create(
+ user=member,
+ portfolio=self.portfolio,
+ roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER],
+ )
+
+ # I log in as the User so I can see the Manage Member page
+ self.client.force_login(self.user)
+
+ # Specifically go to the Manage Member page
+ response = self.client.get(reverse("member", args=[upp.id]), follow=True)
+
+ self.assertEqual(response.status_code, 200)
+
+ # Check for email AND member type (which here is just member)
+ self.assertContains(response, f'data-member-name="{member_email}"')
+ self.assertContains(response, 'data-member-type="member"')
+
+ @less_console_noise_decorator
+ @override_flag("organization_feature", active=True)
+ @override_flag("organization_members", active=True)
+ def test_member_page_has_kebab_wrapper_for_invited_member_if_user_has_edit_permission(self):
+ """Test that the kebab wrapper displays for an invitedmember with edit permissions"""
+
+ # I'm a user
+ UserPortfolioPermission.objects.get_or_create(
+ user=self.user,
+ portfolio=self.portfolio,
+ roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
+ additional_permissions=[
+ UserPortfolioPermissionChoices.VIEW_MEMBERS,
+ UserPortfolioPermissionChoices.EDIT_MEMBERS,
+ ],
+ )
+
+ # Invite a member under same portfolio
+ invited_member_email = "invited_member@example.com"
+ invitation = PortfolioInvitation.objects.create(
+ email=invited_member_email,
+ portfolio=self.portfolio,
+ roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER],
+ )
+
+ # I log in as the User so I can see the Manage Member page
+ self.client.force_login(self.user)
+ response = self.client.get(reverse("invitedmember", args=[invitation.id]), follow=True)
+
+ self.assertEqual(response.status_code, 200)
+
+ # Assert the invited members email + invitedmember type
+ self.assertContains(response, f'data-member-name="{invited_member_email}"')
+ self.assertContains(response, 'data-member-type="invitedmember"')
+
+ @less_console_noise_decorator
+ @override_flag("organization_feature", active=True)
+ @override_flag("organization_members", active=True)
+ def test_member_page_does_not_have_kebab_wrapper(self):
+ """Test that the kebab does not display."""
+
+ # I'm a user
+ UserPortfolioPermission.objects.get_or_create(
+ user=self.user,
+ portfolio=self.portfolio,
+ roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
+ additional_permissions=[
+ UserPortfolioPermissionChoices.VIEW_MEMBERS,
+ UserPortfolioPermissionChoices.EDIT_MEMBERS,
+ ],
+ )
+
+ # That creates a member with only view access
+ member_email = "member_with_view_access@example.com"
+ member, _ = User.objects.get_or_create(username="test_member_with_view_access", email=member_email)
+
+ upp, _ = UserPortfolioPermission.objects.get_or_create(
+ user=member,
+ portfolio=self.portfolio,
+ roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER],
+ additional_permissions=[
+ UserPortfolioPermissionChoices.VIEW_MEMBERS,
+ ],
+ )
+
+ # I log in as the Member with only view permissions to evaluate the pages behaviour
+ # when viewed by someone who doesn't have edit perms
+ self.client.force_login(member)
+
+ # Go to the Manage Member page
+ response = self.client.get(reverse("member", args=[upp.id]), follow=True)
+
+ self.assertEqual(response.status_code, 200)
+
+ # Assert that the kebab edit options are unavailable
+ self.assertNotContains(response, 'data-member-type="member"')
+ self.assertNotContains(response, 'data-member-type="invitedmember"')
+ self.assertNotContains(response, f'data-member-name="{member_email}"')
+
+ @less_console_noise_decorator
+ @override_flag("organization_feature", active=True)
+ @override_flag("organization_members", active=True)
+ def test_member_page_has_correct_form_wrapper(self):
+ """Test that the manage members page the right form wrapper"""
+
+ # I'm a user
+ UserPortfolioPermission.objects.get_or_create(
+ user=self.user,
+ portfolio=self.portfolio,
+ roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
+ additional_permissions=[
+ UserPortfolioPermissionChoices.VIEW_MEMBERS,
+ UserPortfolioPermissionChoices.EDIT_MEMBERS,
+ ],
+ )
+
+ # That creates a member
+ member_email = "a_member@example.com"
+ member, _ = User.objects.get_or_create(email=member_email)
+
+ upp, _ = UserPortfolioPermission.objects.get_or_create(
+ user=member,
+ portfolio=self.portfolio,
+ roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER],
+ )
+
+ # Login as the User to see the Manage Member page
+ self.client.force_login(self.user)
+
+ # Specifically go to the Manage Member page
+ response = self.client.get(reverse("member", args=[upp.id]), follow=True)
+
+ # Check for a 200 response
+ self.assertEqual(response.status_code, 200)
+
+ # Check for form method + that its "post" and id "member-delete-form"
+ self.assertContains(response, "
+
.Gov domain registrants must maintain accurate contact information in the .gov registrar. Before you can manage your domain, we need you to add your contact information.
diff --git a/src/registrar/tests/test_models.py b/src/registrar/tests/test_models.py index 53206359b..0c1bdec2a 100644 --- a/src/registrar/tests/test_models.py +++ b/src/registrar/tests/test_models.py @@ -824,6 +824,92 @@ def test_user_with_portfolio_roles_but_no_portfolio(self): cm.exception.message, "When portfolio roles or additional permissions are assigned, portfolio is required." ) + @less_console_noise_decorator + def test_get_active_requests_count_in_portfolio_returns_zero_if_no_portfolio(self): + # There is no portfolio referenced in session so should return 0 + request = self.factory.get("/") + request.session = {} + + count = self.user.get_active_requests_count_in_portfolio(request) + self.assertEqual(count, 0) + + @less_console_noise_decorator + def test_get_active_requests_count_in_portfolio_returns_count_if_portfolio(self): + request = self.factory.get("/") + request.session = {"portfolio": self.portfolio} + + # Create active requests + domain_1, _ = DraftDomain.objects.get_or_create(name="meoward1.gov") + domain_2, _ = DraftDomain.objects.get_or_create(name="meoward2.gov") + domain_3, _ = DraftDomain.objects.get_or_create(name="meoward3.gov") + domain_4, _ = DraftDomain.objects.get_or_create(name="meoward4.gov") + + # Create 3 active requests + 1 that isn't + DomainRequest.objects.create( + creator=self.user, + requested_domain=domain_1, + status=DomainRequest.DomainRequestStatus.SUBMITTED, + portfolio=self.portfolio, + ) + DomainRequest.objects.create( + creator=self.user, + requested_domain=domain_2, + status=DomainRequest.DomainRequestStatus.IN_REVIEW, + portfolio=self.portfolio, + ) + DomainRequest.objects.create( + creator=self.user, + requested_domain=domain_3, + status=DomainRequest.DomainRequestStatus.ACTION_NEEDED, + portfolio=self.portfolio, + ) + DomainRequest.objects.create( # This one should not be counted + creator=self.user, + requested_domain=domain_4, + status=DomainRequest.DomainRequestStatus.REJECTED, + portfolio=self.portfolio, + ) + + count = self.user.get_active_requests_count_in_portfolio(request) + self.assertEqual(count, 3) + + @less_console_noise_decorator + def test_is_only_admin_of_portfolio_returns_true(self): + # Create user as the only admin of the portfolio + UserPortfolioPermission.objects.create( + user=self.user, portfolio=self.portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN] + ) + self.assertTrue(self.user.is_only_admin_of_portfolio(self.portfolio)) + + @less_console_noise_decorator + def test_is_only_admin_of_portfolio_returns_false_if_no_admins(self): + # No admin for the portfolio + self.assertFalse(self.user.is_only_admin_of_portfolio(self.portfolio)) + + @less_console_noise_decorator + def test_is_only_admin_of_portfolio_returns_false_if_multiple_admins(self): + # Create multiple admins for the same portfolio + UserPortfolioPermission.objects.create( + user=self.user, portfolio=self.portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN] + ) + # Create another user within this test + other_user = User.objects.create(email="second_admin@igorville.gov", username="second_admin") + UserPortfolioPermission.objects.create( + user=other_user, portfolio=self.portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN] + ) + self.assertFalse(self.user.is_only_admin_of_portfolio(self.portfolio)) + + @less_console_noise_decorator + def test_is_only_admin_of_portfolio_returns_false_if_user_not_admin(self): + # Create other_user for same portfolio and is given admin access + other_user = User.objects.create(email="second_admin@igorville.gov", username="second_admin") + + UserPortfolioPermission.objects.create( + user=other_user, portfolio=self.portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN] + ) + # User doesn't have admin access so should return false + self.assertFalse(self.user.is_only_admin_of_portfolio(self.portfolio)) + class TestContact(TestCase): @less_console_noise_decorator diff --git a/src/registrar/tests/test_views_portfolio.py b/src/registrar/tests/test_views_portfolio.py index 402d23b70..772779903 100644 --- a/src/registrar/tests/test_views_portfolio.py +++ b/src/registrar/tests/test_views_portfolio.py @@ -2,8 +2,9 @@ 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 unittest.mock import MagicMock, patch from django_webtest import WebTest # type: ignore +from django.core.handlers.wsgi import WSGIRequest from registrar.models import ( DomainRequest, Domain, @@ -959,7 +960,7 @@ def test_can_view_member_page_when_user_has_edit_members(self): ) # Assert buttons and links within the page are correct - self.assertContains(response, "usa-button--more-actions") # test that 3 dot is present + self.assertContains(response, "wrapper-delete-action") # test that 3 dot is present self.assertContains(response, "sprite.svg#edit") # test that Edit link is present self.assertContains(response, "sprite.svg#settings") # test that Manage link is present self.assertNotContains(response, "sprite.svg#visibility") # test that View link is not present @@ -1077,9 +1078,8 @@ def test_can_view_invitedmember_page_when_user_has_edit_members(self): self.assertContains( response, 'This member does not manage any domains. To assign this member a domain, click "Manage"' ) - # Assert buttons and links within the page are correct - self.assertContains(response, "usa-button--more-actions") # test that 3 dot is present + self.assertContains(response, "wrapper-delete-action") # test that 3 dot is present self.assertContains(response, "sprite.svg#edit") # test that Edit link is present self.assertContains(response, "sprite.svg#settings") # test that Manage link is present self.assertNotContains(response, "sprite.svg#visibility") # test that View link is not present @@ -1392,6 +1392,510 @@ def test_org_user_cannot_delete_others_domain_requests(self): self.assertTrue(DomainRequest.objects.filter(pk=domain_request.pk).exists()) domain_request.delete() + @less_console_noise_decorator + @override_flag("organization_feature", active=True) + @override_flag("organization_members", active=True) + def test_members_table_contains_hidden_permissions_js_hook(self): + # In the members_table.html we use data-has-edit-permission as a boolean + # to indicate if a user has permission to edit members in the specific portfolio + + # 1. User w/ edit permission + UserPortfolioPermission.objects.get_or_create( + user=self.user, + portfolio=self.portfolio, + roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN], + additional_permissions=[ + UserPortfolioPermissionChoices.VIEW_MEMBERS, + UserPortfolioPermissionChoices.EDIT_MEMBERS, + ], + ) + + # Create a member under same portfolio + member_email = "a_member@example.com" + member, _ = User.objects.get_or_create(username="a_member", email=member_email) + + UserPortfolioPermission.objects.get_or_create( + user=member, + portfolio=self.portfolio, + roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER], + ) + + # I log in as the User so I can see the Members Table + self.client.force_login(self.user) + + # Specifically go to the Member Table page + response = self.client.get(reverse("members")) + + self.assertContains(response, 'data-has-edit-permission="True"') + + # 2. User w/o edit permission (additional permission of EDIT_MEMBERS removed) + permission = UserPortfolioPermission.objects.get(user=self.user, portfolio=self.portfolio) + + # Remove the EDIT_MEMBERS additional permission + permission.additional_permissions = [ + perm for perm in permission.additional_permissions if perm != UserPortfolioPermissionChoices.EDIT_MEMBERS + ] + + # Save the updated permissions list + permission.save() + + # Re-fetch the page to check for updated permissions + response = self.client.get(reverse("members")) + + self.assertContains(response, 'data-has-edit-permission="False"') + + @less_console_noise_decorator + @override_flag("organization_feature", active=True) + @override_flag("organization_members", active=True) + def test_member_page_has_kebab_wrapper_for_member_if_user_has_edit_permission(self): + """Test that the kebab wrapper displays for a member with edit permissions""" + + # I'm a user + UserPortfolioPermission.objects.get_or_create( + user=self.user, + portfolio=self.portfolio, + roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN], + additional_permissions=[ + UserPortfolioPermissionChoices.VIEW_MEMBERS, + UserPortfolioPermissionChoices.EDIT_MEMBERS, + ], + ) + + # Create a member under same portfolio + member_email = "a_member@example.com" + member, _ = User.objects.get_or_create(username="a_member", email=member_email) + + upp, _ = UserPortfolioPermission.objects.get_or_create( + user=member, + portfolio=self.portfolio, + roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER], + ) + + # I log in as the User so I can see the Manage Member page + self.client.force_login(self.user) + + # Specifically go to the Manage Member page + response = self.client.get(reverse("member", args=[upp.id]), follow=True) + + self.assertEqual(response.status_code, 200) + + # Check for email AND member type (which here is just member) + self.assertContains(response, f'data-member-name="{member_email}"') + self.assertContains(response, 'data-member-type="member"') + + @less_console_noise_decorator + @override_flag("organization_feature", active=True) + @override_flag("organization_members", active=True) + def test_member_page_has_kebab_wrapper_for_invited_member_if_user_has_edit_permission(self): + """Test that the kebab wrapper displays for an invitedmember with edit permissions""" + + # I'm a user + UserPortfolioPermission.objects.get_or_create( + user=self.user, + portfolio=self.portfolio, + roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN], + additional_permissions=[ + UserPortfolioPermissionChoices.VIEW_MEMBERS, + UserPortfolioPermissionChoices.EDIT_MEMBERS, + ], + ) + + # Invite a member under same portfolio + invited_member_email = "invited_member@example.com" + invitation = PortfolioInvitation.objects.create( + email=invited_member_email, + portfolio=self.portfolio, + roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER], + ) + + # I log in as the User so I can see the Manage Member page + self.client.force_login(self.user) + response = self.client.get(reverse("invitedmember", args=[invitation.id]), follow=True) + + self.assertEqual(response.status_code, 200) + + # Assert the invited members email + invitedmember type + self.assertContains(response, f'data-member-name="{invited_member_email}"') + self.assertContains(response, 'data-member-type="invitedmember"') + + @less_console_noise_decorator + @override_flag("organization_feature", active=True) + @override_flag("organization_members", active=True) + def test_member_page_does_not_have_kebab_wrapper(self): + """Test that the kebab does not display.""" + + # I'm a user + UserPortfolioPermission.objects.get_or_create( + user=self.user, + portfolio=self.portfolio, + roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN], + additional_permissions=[ + UserPortfolioPermissionChoices.VIEW_MEMBERS, + UserPortfolioPermissionChoices.EDIT_MEMBERS, + ], + ) + + # That creates a member with only view access + member_email = "member_with_view_access@example.com" + member, _ = User.objects.get_or_create(username="test_member_with_view_access", email=member_email) + + upp, _ = UserPortfolioPermission.objects.get_or_create( + user=member, + portfolio=self.portfolio, + roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER], + additional_permissions=[ + UserPortfolioPermissionChoices.VIEW_MEMBERS, + ], + ) + + # I log in as the Member with only view permissions to evaluate the pages behaviour + # when viewed by someone who doesn't have edit perms + self.client.force_login(member) + + # Go to the Manage Member page + response = self.client.get(reverse("member", args=[upp.id]), follow=True) + + self.assertEqual(response.status_code, 200) + + # Assert that the kebab edit options are unavailable + self.assertNotContains(response, 'data-member-type="member"') + self.assertNotContains(response, 'data-member-type="invitedmember"') + self.assertNotContains(response, f'data-member-name="{member_email}"') + + @less_console_noise_decorator + @override_flag("organization_feature", active=True) + @override_flag("organization_members", active=True) + def test_member_page_has_correct_form_wrapper(self): + """Test that the manage members page the right form wrapper""" + + # I'm a user + UserPortfolioPermission.objects.get_or_create( + user=self.user, + portfolio=self.portfolio, + roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN], + additional_permissions=[ + UserPortfolioPermissionChoices.VIEW_MEMBERS, + UserPortfolioPermissionChoices.EDIT_MEMBERS, + ], + ) + + # That creates a member + member_email = "a_member@example.com" + member, _ = User.objects.get_or_create(email=member_email) + + upp, _ = UserPortfolioPermission.objects.get_or_create( + user=member, + portfolio=self.portfolio, + roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER], + ) + + # Login as the User to see the Manage Member page + self.client.force_login(self.user) + + # Specifically go to the Manage Member page + response = self.client.get(reverse("member", args=[upp.id]), follow=True) + + # Check for a 200 response + self.assertEqual(response.status_code, 200) + + # Check for form method + that its "post" and id "member-delete-form" + self.assertContains(response, "