tests for PortfolioMemberDeleteView PortfolioInvitedMemberDeleteView

This commit is contained in:
David Kennedy 2025-02-01 16:02:49 -05:00
parent ff9c402c40
commit d29205f75e
No known key found for this signature in database
GPG key ID: 6528A5386E66B96B
2 changed files with 309 additions and 5 deletions

View file

@ -711,7 +711,7 @@ class SendPortfolioAdminAdditionEmailsTests(unittest.TestCase):
)
class SendPortfolioAdminRemovalEmailsTests(unittest.TestCase):
class SendPortfolioAdminRemovalEmailsToAdminsTests(unittest.TestCase):
"""Unit tests for _send_portfolio_admin_removal_emails_to_portfolio_admins function."""
def setUp(self):

View file

@ -1631,10 +1631,33 @@ class TestPortfolio(WebTest):
# Assert that the toggleable alert ID exists
self.assertContains(response, '<div id="toggleable-alert"')
class TestPortfolioMemberDeleteView(WebTest):
def setUp(self):
super().setUp()
self.client = Client()
self.user = create_test_user()
self.domain, _ = Domain.objects.get_or_create(name="igorville.gov")
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
)
def tearDown(self):
UserPortfolioPermission.objects.all().delete()
Portfolio.objects.all().delete()
UserDomainRole.objects.all().delete()
DomainRequest.objects.all().delete()
DomainInformation.objects.all().delete()
Domain.objects.all().delete()
User.objects.all().delete()
super().tearDown()
@less_console_noise_decorator
@override_flag("organization_feature", active=True)
@override_flag("organization_members", active=True)
def test_portfolio_member_delete_view_members_table_active_requests(self):
@patch("registrar.views.portfolios.send_portfolio_admin_removal_emails")
def test_portfolio_member_delete_view_members_table_active_requests(self, send_removal_emails):
"""Error state w/ deleting a member with active request on Members Table"""
# I'm a user
UserPortfolioPermission.objects.get_or_create(
@ -1672,10 +1695,14 @@ class TestPortfolio(WebTest):
self.assertContains(response, expected_error_message, status_code=400)
# assert that send_portfolio_admin_removal_emails is not called
send_removal_emails.assert_not_called()
@less_console_noise_decorator
@override_flag("organization_feature", active=True)
@override_flag("organization_members", active=True)
def test_portfolio_member_delete_view_members_table_only_admin(self):
@patch("registrar.views.portfolios.send_portfolio_admin_removal_emails")
def test_portfolio_member_delete_view_members_table_only_admin(self, send_removal_emails):
"""Error state w/ deleting a member that's the only admin on Members Table"""
# I'm a user with admin permission
@ -1703,10 +1730,14 @@ class TestPortfolio(WebTest):
)
self.assertContains(response, expected_error_message, status_code=400)
# assert that send_portfolio_admin_removal_emails is not called
send_removal_emails.assert_not_called()
@less_console_noise_decorator
@override_flag("organization_feature", active=True)
@override_flag("organization_members", active=True)
def test_portfolio_member_table_delete_view_success(self):
@patch("registrar.views.portfolios.send_portfolio_admin_removal_emails")
def test_portfolio_member_table_delete_member_success(self, mock_send_removal_emails):
"""Success state with deleting on Members Table page bc no active request AND not only admin"""
# I'm a user
@ -1750,6 +1781,134 @@ class TestPortfolio(WebTest):
expected_success_message = f"You've removed {member.email} from the organization."
self.assertContains(response, expected_success_message, status_code=200)
# assert that send_portfolio_admin_removal_emails is not called
# because member being removed is not an admin
mock_send_removal_emails.assert_not_called()
@less_console_noise_decorator
@override_flag("organization_feature", active=True)
@override_flag("organization_members", active=True)
@patch("registrar.views.portfolios.send_portfolio_admin_removal_emails")
def test_portfolio_member_table_delete_admin_success(self, mock_send_removal_emails):
"""Success state with deleting on Members Table page bc no active request AND
not only admin. Because admin, removal emails are sent."""
# 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,
],
)
# Creating an admin that can be deleted (see patch)
member_email = "deleteable_admin@example.com"
member, _ = User.objects.get_or_create(email=member_email)
# Set up the member in the portfolio
upp, _ = UserPortfolioPermission.objects.get_or_create(
user=member,
portfolio=self.portfolio,
roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
)
mock_send_removal_emails.return_value = True
# And set that the member has no active requests AND it's not the only admin
with patch.object(User, "get_active_requests_count_in_portfolio", return_value=0), patch.object(
User, "is_only_admin_of_portfolio", return_value=False
):
# Attempt to delete
self.client.force_login(self.user)
response = self.client.post(
# We check X_REQUESTED_WITH bc those return JSON responses
reverse("member-delete", kwargs={"pk": upp.pk}),
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
# Check for a successful deletion
self.assertEqual(response.status_code, 200)
expected_success_message = f"You've removed {member.email} from the organization."
self.assertContains(response, expected_success_message, status_code=200)
# assert that send_portfolio_admin_removal_emails is called
mock_send_removal_emails.assert_called_once()
# Get the arguments passed to send_portfolio_admin_addition_emails
_, called_kwargs = mock_send_removal_emails.call_args
# Assert the email content
self.assertEqual(called_kwargs["email"], member_email)
self.assertEqual(called_kwargs["requestor"], self.user)
self.assertEqual(called_kwargs["portfolio"], self.portfolio)
@less_console_noise_decorator
@override_flag("organization_feature", active=True)
@override_flag("organization_members", active=True)
@patch("registrar.views.portfolios.send_portfolio_admin_removal_emails")
def test_portfolio_member_table_delete_admin_success_removal_email_fail(self, mock_send_removal_emails):
"""Success state with deleting on Members Table page bc no active request AND
not only admin. Because admin, removal emails are sent, but fail to send."""
# 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,
],
)
# Creating an admin that can be deleted (see patch)
member_email = "deleteable_admin@example.com"
member, _ = User.objects.get_or_create(email=member_email)
# Set up the member in the portfolio
upp, _ = UserPortfolioPermission.objects.get_or_create(
user=member,
portfolio=self.portfolio,
roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
)
mock_send_removal_emails.return_value = False
# And set that the member has no active requests AND it's not the only admin
with patch.object(User, "get_active_requests_count_in_portfolio", return_value=0), patch.object(
User, "is_only_admin_of_portfolio", return_value=False
):
# Attempt to delete
self.client.force_login(self.user)
response = self.client.post(
# We check X_REQUESTED_WITH bc those return JSON responses
reverse("member-delete", kwargs={"pk": upp.pk}),
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
# Check for a successful deletion
self.assertEqual(response.status_code, 200)
expected_success_message = f"You've removed {member.email} from the organization."
self.assertContains(response, expected_success_message, status_code=200)
# assert that send_portfolio_admin_removal_emails is called
mock_send_removal_emails.assert_called_once()
# Get the arguments passed to send_portfolio_admin_addition_emails
_, called_kwargs = mock_send_removal_emails.call_args
# Assert the email content
self.assertEqual(called_kwargs["email"], member_email)
self.assertEqual(called_kwargs["requestor"], self.user)
self.assertEqual(called_kwargs["portfolio"], self.portfolio)
@less_console_noise_decorator
@override_flag("organization_feature", active=True)
@override_flag("organization_members", active=True)
@ -1848,10 +2007,33 @@ class TestPortfolio(WebTest):
# and then confirm that we're still on the Manage Members page
self.assertEqual(response.headers["Location"], reverse("member", kwargs={"pk": admin_perm_user.pk}))
class TestPortfolioInvitedMemberDeleteView(WebTest):
def setUp(self):
super().setUp()
self.client = Client()
self.user = create_test_user()
self.domain, _ = Domain.objects.get_or_create(name="igorville.gov")
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
)
def tearDown(self):
UserPortfolioPermission.objects.all().delete()
Portfolio.objects.all().delete()
UserDomainRole.objects.all().delete()
DomainRequest.objects.all().delete()
DomainInformation.objects.all().delete()
Domain.objects.all().delete()
User.objects.all().delete()
super().tearDown()
@less_console_noise_decorator
@override_flag("organization_feature", active=True)
@override_flag("organization_members", active=True)
def test_portfolio_member_delete_view_manage_members_page_invitedmember(self):
@patch("registrar.views.portfolios.send_portfolio_admin_removal_emails")
def test_portfolio_member_delete_view_manage_members_page_invitedmember(self, mock_send_removal_emails):
"""Success state w/ deleting invited member on Manage Members page should redirect back to Members Table"""
# I'm a user
@ -1892,6 +2074,128 @@ class TestPortfolio(WebTest):
# and then confirm that we're now on Members Table page
self.assertEqual(response.headers["Location"], reverse("members"))
# assert send_portfolio_admin_removal_emails not called since invitation
# is for a basic member
mock_send_removal_emails.assert_not_called()
@less_console_noise_decorator
@override_flag("organization_feature", active=True)
@override_flag("organization_members", active=True)
@patch("registrar.views.portfolios.send_portfolio_admin_removal_emails")
def test_portfolio_member_delete_view_manage_members_page_invitedadmin(self, mock_send_removal_emails):
"""Success state w/ deleting invited admin on Manage Members page should redirect back to Members Table"""
# 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,
],
)
mock_send_removal_emails.return_value = True
# Invite an admin under same portfolio
invited_member_email = "invited_member@example.com"
invitation = PortfolioInvitation.objects.create(
email=invited_member_email,
portfolio=self.portfolio,
roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
)
with patch("django.contrib.messages.success") as mock_success:
self.client.force_login(self.user)
response = self.client.post(
reverse("invitedmember-delete", kwargs={"pk": invitation.pk}),
)
self.assertEqual(response.status_code, 302)
expected_success_message = f"You've removed {invitation.email} from the organization."
args, kwargs = mock_success.call_args
# Check if first arg is a WSGIRequest, confirms request object passed correctly
# WSGIRequest protocol is basically the HTTPRequest but in Django form (ie POST '/member/1/delete')
self.assertIsInstance(args[0], WSGIRequest)
# Check that the error message matches the expected error message
self.assertEqual(args[1], expected_success_message)
# Location is used for a 3xx HTTP status code to indicate that the URL was redirected
# and then confirm that we're now on Members Table page
self.assertEqual(response.headers["Location"], reverse("members"))
# assert send_portfolio_admin_removal_emails is called since invitation
# is for an admin
mock_send_removal_emails.assert_called_once()
# Get the arguments passed to send_portfolio_admin_addition_emails
_, called_kwargs = mock_send_removal_emails.call_args
# Assert the email content
self.assertEqual(called_kwargs["email"], invited_member_email)
self.assertEqual(called_kwargs["requestor"], self.user)
self.assertEqual(called_kwargs["portfolio"], self.portfolio)
@less_console_noise_decorator
@override_flag("organization_feature", active=True)
@override_flag("organization_members", active=True)
@patch("registrar.views.portfolios.send_portfolio_admin_removal_emails")
def test_portfolio_member_delete_view_manage_members_page_invitedadmin_email_fails(self, mock_send_removal_emails):
"""Success state w/ deleting invited admin on Manage Members page should redirect back to Members Table"""
# 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,
],
)
mock_send_removal_emails.return_value = False
# Invite an admin under same portfolio
invited_member_email = "invited_member@example.com"
invitation = PortfolioInvitation.objects.create(
email=invited_member_email,
portfolio=self.portfolio,
roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
)
with patch("django.contrib.messages.success") as mock_success:
self.client.force_login(self.user)
response = self.client.post(
reverse("invitedmember-delete", kwargs={"pk": invitation.pk}),
)
self.assertEqual(response.status_code, 302)
expected_success_message = f"You've removed {invitation.email} from the organization."
args, kwargs = mock_success.call_args
# Check if first arg is a WSGIRequest, confirms request object passed correctly
# WSGIRequest protocol is basically the HTTPRequest but in Django form (ie POST '/member/1/delete')
self.assertIsInstance(args[0], WSGIRequest)
# Check that the error message matches the expected error message
self.assertEqual(args[1], expected_success_message)
# Location is used for a 3xx HTTP status code to indicate that the URL was redirected
# and then confirm that we're now on Members Table page
self.assertEqual(response.headers["Location"], reverse("members"))
# assert send_portfolio_admin_removal_emails is called since invitation
# is for an admin
mock_send_removal_emails.assert_called_once()
# Get the arguments passed to send_portfolio_admin_addition_emails
_, called_kwargs = mock_send_removal_emails.call_args
# Assert the email content
self.assertEqual(called_kwargs["email"], invited_member_email)
self.assertEqual(called_kwargs["requestor"], self.user)
self.assertEqual(called_kwargs["portfolio"], self.portfolio)
class TestPortfolioMemberDomainsView(TestWithUser, WebTest):
@classmethod