mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-22 18:56:15 +02:00
linting, cleanup, initial unit tests
This commit is contained in:
parent
bc8df41001
commit
c75b6043fd
5 changed files with 191 additions and 22 deletions
|
@ -2827,8 +2827,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
*/
|
*/
|
||||||
(function handleNewMemberModal() {
|
(function handleNewMemberModal() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Populates contents of the "Add Member" confirmation modal
|
Populates contents of the "Add Member" confirmation modal
|
||||||
*/
|
*/
|
||||||
|
@ -2909,12 +2907,13 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
document.getElementById("add_member_form").addEventListener("submit", function(event) {
|
document.getElementById("add_member_form").addEventListener("submit", function(event) {
|
||||||
event.preventDefault(); // Prevents the form from submitting
|
event.preventDefault(); // Prevents the form from submitting
|
||||||
|
const form = document.getElementById("add_member_form")
|
||||||
|
const formData = new FormData(form);
|
||||||
|
|
||||||
const formData = new FormData(this);
|
// Check if the form is valid
|
||||||
|
// If the form is valid, open the confirmation modal
|
||||||
// Check if the form is valid and trigger events
|
// If the form is invalid, submit it to trigger error
|
||||||
// (like a confirmation modal) accordingly
|
fetch(form.action, {
|
||||||
fetch(this.action, {
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formData,
|
body: formData,
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -2929,7 +2928,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
openAddMemberConfirmationModal();
|
openAddMemberConfirmationModal();
|
||||||
} else {
|
} else {
|
||||||
// If the form is not valid, trigger error messages by firing a submit event
|
// If the form is not valid, trigger error messages by firing a submit event
|
||||||
this.submit();
|
form.submit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -126,11 +126,6 @@ urlpatterns = [
|
||||||
views.NewMemberView.as_view(),
|
views.NewMemberView.as_view(),
|
||||||
name="new-member",
|
name="new-member",
|
||||||
),
|
),
|
||||||
path(
|
|
||||||
"members/new-member/validate",
|
|
||||||
views.NewMemberView.as_view(http_method_names=["post"]),
|
|
||||||
name="new-member-validate",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"requests/",
|
"requests/",
|
||||||
views.PortfolioDomainRequestsView.as_view(),
|
views.PortfolioDomainRequestsView.as_view(),
|
||||||
|
|
|
@ -125,7 +125,7 @@
|
||||||
aria-controls="invite-member-modal"
|
aria-controls="invite-member-modal"
|
||||||
data-open-modal
|
data-open-modal
|
||||||
>Trigger invite member modal</a>
|
>Trigger invite member modal</a>
|
||||||
<button type="submit" class="usa-button">Invite Member</button>
|
<button id="invite_new_member_submit" type="submit" class="usa-button">Invite Member</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
|
@ -1876,3 +1876,156 @@ class TestRequestingEntity(WebTest):
|
||||||
self.assertContains(response, "Requesting entity")
|
self.assertContains(response, "Requesting entity")
|
||||||
self.assertContains(response, "moon")
|
self.assertContains(response, "moon")
|
||||||
self.assertContains(response, "kepler, AL")
|
self.assertContains(response, "kepler, AL")
|
||||||
|
|
||||||
|
|
||||||
|
class TestPortfolioInviteNewMemberView(TestWithUser, WebTest):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
|
||||||
|
|
||||||
|
# Create Portfolio
|
||||||
|
cls.portfolio = Portfolio.objects.create(creator=cls.user, organization_name="Test Portfolio")
|
||||||
|
|
||||||
|
# Add an invited member who has been invited to manage domains
|
||||||
|
cls.invited_member_email = "invited@example.com"
|
||||||
|
cls.invitation = PortfolioInvitation.objects.create(
|
||||||
|
email=cls.invited_member_email,
|
||||||
|
portfolio=cls.portfolio,
|
||||||
|
roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER],
|
||||||
|
additional_permissions=[
|
||||||
|
UserPortfolioPermissionChoices.VIEW_MEMBERS,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.new_member_email = "new_user@example.com"
|
||||||
|
|
||||||
|
# Assign permissions to the user making requests
|
||||||
|
UserPortfolioPermission.objects.create(
|
||||||
|
user=cls.user,
|
||||||
|
portfolio=cls.portfolio,
|
||||||
|
roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
|
||||||
|
additional_permissions=[
|
||||||
|
UserPortfolioPermissionChoices.VIEW_MEMBERS,
|
||||||
|
UserPortfolioPermissionChoices.EDIT_MEMBERS,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
PortfolioInvitation.objects.all().delete()
|
||||||
|
UserPortfolioPermission.objects.all().delete()
|
||||||
|
Portfolio.objects.all().delete()
|
||||||
|
User.objects.all().delete()
|
||||||
|
super().tearDownClass()
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
@override_flag("organization_feature", active=True)
|
||||||
|
@override_flag("organization_members", active=True)
|
||||||
|
def test_member_invite_for_new_users(self):
|
||||||
|
"""Tests the member invitation flow for new users."""
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
|
||||||
|
# Simulate a session to ensure continuity
|
||||||
|
session_id = self.client.session.session_key
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
|
||||||
|
# Step 1: Access the "New Member" page
|
||||||
|
new_member_page = self.app.get(reverse("new-member"))
|
||||||
|
self.assertEqual(new_member_page.status_code, 200)
|
||||||
|
self.assertContains(new_member_page, "Add a new member")
|
||||||
|
self.assertContains(new_member_page, f'<p class="margin-top-0" id="modalEmail"></p>')
|
||||||
|
|
||||||
|
# Step 2: Fill out the "New Member" form
|
||||||
|
new_member_form = new_member_page.forms[0]
|
||||||
|
new_member_form["member_access_level"] = "basic"
|
||||||
|
new_member_form["basic_org_domain_request_permissions"] = "view_only"
|
||||||
|
new_member_form["email"] = self.new_member_email
|
||||||
|
|
||||||
|
# Simulate form submission, which would trigger the modal in JavaScript
|
||||||
|
response = new_member_form.submit().follow()
|
||||||
|
self.assertEqual(response.status_code, 301) # Ensure the page does not redirect
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: test the modal somehow
|
||||||
|
# self.assertContains(new_member_page, f'<p class="margin-top-0" id="modalEmail">{self.new_member_email}</p>')
|
||||||
|
# form_data = {field.name: field.value() for field in new_member_form}
|
||||||
|
|
||||||
|
|
||||||
|
# Simulate user confirming the modal action (frontend JavaScript would normally handle this)
|
||||||
|
# Re-submit the form to simulate final submission
|
||||||
|
final_response = self.client.post(reverse("new-member"), {
|
||||||
|
"member_access_level": "basic",
|
||||||
|
"basic_org_domain_request_permissions": "view_only",
|
||||||
|
"email": self.new_member_email
|
||||||
|
})
|
||||||
|
|
||||||
|
# Ensure the final submission is successful
|
||||||
|
self.assertEqual(final_response.status_code, 302) # redirects after success
|
||||||
|
|
||||||
|
# TODO: verify messages
|
||||||
|
|
||||||
|
# Step 4: Validate Database Changes
|
||||||
|
portfolio_invite = PortfolioInvitation.objects.filter(
|
||||||
|
email=self.new_member_email, portfolio=self.portfolio
|
||||||
|
).first()
|
||||||
|
self.assertIsNotNone(portfolio_invite)
|
||||||
|
self.assertEqual(portfolio_invite.email, self.new_member_email)
|
||||||
|
# self.assertEqual(portfolio_invite.access_level, "basic") # TODO: test that roles and permissions are in the portfolio invite
|
||||||
|
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
@override_flag("organization_feature", active=True)
|
||||||
|
@override_flag("organization_members", active=True)
|
||||||
|
def test_member_invite_for_previously_invited_member(self):
|
||||||
|
"""Tests the member invitation flow for existing portfolio member."""
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
|
||||||
|
# Simulate a session to ensure continuity
|
||||||
|
session_id = self.client.session.session_key
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
|
||||||
|
invite_count_before = PortfolioInvitation.objects.count()
|
||||||
|
|
||||||
|
# Simulate submission of member invite for user who has already been invited
|
||||||
|
response = self.client.post(reverse("new-member"), {
|
||||||
|
"member_access_level": "basic",
|
||||||
|
"basic_org_domain_request_permissions": "view_only",
|
||||||
|
"email": self.invited_member_email
|
||||||
|
})
|
||||||
|
self.assertEqual(response.status_code, 302) # Redirects
|
||||||
|
|
||||||
|
# TODO: verify messages
|
||||||
|
|
||||||
|
# Validate Database has not changed
|
||||||
|
invite_count_after = PortfolioInvitation.objects.count()
|
||||||
|
self.assertEqual(invite_count_after, invite_count_before)
|
||||||
|
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
@override_flag("organization_feature", active=True)
|
||||||
|
@override_flag("organization_members", active=True)
|
||||||
|
def test_member_invite_for_existing_member(self):
|
||||||
|
"""Tests the member invitation flow for existing portfolio member."""
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
|
||||||
|
# Simulate a session to ensure continuity
|
||||||
|
session_id = self.client.session.session_key
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
|
||||||
|
invite_count_before = PortfolioInvitation.objects.count()
|
||||||
|
|
||||||
|
# Simulate submission of member invite for user who has already been invited
|
||||||
|
response = self.client.post(reverse("new-member"), {
|
||||||
|
"member_access_level": "basic",
|
||||||
|
"basic_org_domain_request_permissions": "view_only",
|
||||||
|
"email": self.user.email
|
||||||
|
})
|
||||||
|
self.assertEqual(response.status_code, 302) # Redirects
|
||||||
|
|
||||||
|
# TODO: verify messages
|
||||||
|
|
||||||
|
# Validate Database has not changed
|
||||||
|
invite_count_after = PortfolioInvitation.objects.count()
|
||||||
|
self.assertEqual(invite_count_after, invite_count_before)
|
||||||
|
|
|
@ -23,9 +23,17 @@ from registrar.views.utility.permission_views import (
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
from django.views.generic.edit import FormMixin
|
from django.views.generic.edit import FormMixin
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ---Logger
|
||||||
|
import logging
|
||||||
|
from venv import logger
|
||||||
|
from registrar.management.commands.utility.terminal_helper import TerminalColors, TerminalHelper
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PortfolioDomainsView(PortfolioDomainsPermissionView, View):
|
class PortfolioDomainsView(PortfolioDomainsPermissionView, View):
|
||||||
|
|
||||||
template_name = "portfolio_domains.html"
|
template_name = "portfolio_domains.html"
|
||||||
|
@ -418,6 +426,7 @@ class NewMemberView(PortfolioMembersPermissionView, FormMixin):
|
||||||
"""Handle POST requests to process form submission."""
|
"""Handle POST requests to process form submission."""
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
form = self.get_form()
|
form = self.get_form()
|
||||||
|
TerminalHelper.colorful_logger(logger.info, TerminalColors.OKCYAN, f"**post")
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
return self.form_valid(form)
|
return self.form_valid(form)
|
||||||
|
@ -433,6 +442,17 @@ class NewMemberView(PortfolioMembersPermissionView, FormMixin):
|
||||||
else:
|
else:
|
||||||
return super().form_invalid(form) # Handle non-AJAX requests normally
|
return super().form_invalid(form) # Handle non-AJAX requests normally
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
|
||||||
|
TerminalHelper.colorful_logger(logger.info, TerminalColors.OKCYAN, f"VALIDATING")
|
||||||
|
|
||||||
|
|
||||||
|
if self.is_ajax():
|
||||||
|
TerminalHelper.colorful_logger(logger.info, TerminalColors.OKCYAN, f"IS AJAX")
|
||||||
|
return JsonResponse({"is_valid": True}) # Return a JSON response
|
||||||
|
else:
|
||||||
|
return self.submit_new_member(form)
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
"""Redirect to members table."""
|
"""Redirect to members table."""
|
||||||
return reverse("members")
|
return reverse("members")
|
||||||
|
@ -446,6 +466,8 @@ class NewMemberView(PortfolioMembersPermissionView, FormMixin):
|
||||||
raises EmailSendingError
|
raises EmailSendingError
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
TerminalHelper.colorful_logger(logger.info, TerminalColors.OKCYAN, f"_send_portfolio_invitation_email")
|
||||||
|
|
||||||
# Set a default email address to send to for staff
|
# Set a default email address to send to for staff
|
||||||
requestor_email = settings.DEFAULT_FROM_EMAIL
|
requestor_email = settings.DEFAULT_FROM_EMAIL
|
||||||
|
|
||||||
|
@ -469,12 +491,13 @@ class NewMemberView(PortfolioMembersPermissionView, FormMixin):
|
||||||
add_success = False
|
add_success = False
|
||||||
messages.warning(
|
messages.warning(
|
||||||
self.request,
|
self.request,
|
||||||
f"{email} is already a manager for this domain.",
|
f"{email} is already a manager for this portfolio.",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
add_success = False
|
add_success = False
|
||||||
# else if it has been sent but not accepted
|
# else if it has been sent but not accepted
|
||||||
messages.warning(self.request, f"{email} has already been invited to this domain")
|
TerminalHelper.colorful_logger(logger.info, TerminalColors.OKCYAN, f"has already been invited to this portfolio")
|
||||||
|
messages.warning(self.request, f"{email} has already been invited to this portfolio")
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.error("An error occured")
|
logger.error("An error occured")
|
||||||
|
|
||||||
|
@ -513,16 +536,11 @@ class NewMemberView(PortfolioMembersPermissionView, FormMixin):
|
||||||
PortfolioInvitation.objects.get_or_create(email=email_address, portfolio=self.object)
|
PortfolioInvitation.objects.get_or_create(email=email_address, portfolio=self.object)
|
||||||
return redirect(self.get_success_url())
|
return redirect(self.get_success_url())
|
||||||
|
|
||||||
def form_valid(self, form):
|
|
||||||
if self.is_ajax():
|
|
||||||
return JsonResponse({"is_valid": True}) # Return a JSON response
|
|
||||||
else:
|
|
||||||
return self.submit_new_member(form)
|
|
||||||
|
|
||||||
def submit_new_member(self, form):
|
def submit_new_member(self, form):
|
||||||
"""Add the specified user as a member
|
"""Add the specified user as a member
|
||||||
for this portfolio.
|
for this portfolio.
|
||||||
Throws EmailSendingError."""
|
Throws EmailSendingError."""
|
||||||
|
TerminalHelper.colorful_logger(logger.info, TerminalColors.OKCYAN, f"Submit new member")
|
||||||
|
|
||||||
requested_email = form.cleaned_data["email"]
|
requested_email = form.cleaned_data["email"]
|
||||||
requestor = self.request.user
|
requestor = self.request.user
|
||||||
|
@ -532,6 +550,8 @@ class NewMemberView(PortfolioMembersPermissionView, FormMixin):
|
||||||
requested_user = User.objects.get(email=requested_email)
|
requested_user = User.objects.get(email=requested_email)
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
# no matching user, go make an invitation
|
# no matching user, go make an invitation
|
||||||
|
TerminalHelper.colorful_logger(logger.info, TerminalColors.OKCYAN, f"..making invitation")
|
||||||
|
|
||||||
return self._make_invitation(requested_email, requestor)
|
return self._make_invitation(requested_email, requestor)
|
||||||
else:
|
else:
|
||||||
# If user already exists, check to see if they are part of the portfolio already
|
# If user already exists, check to see if they are part of the portfolio already
|
||||||
|
@ -539,9 +559,11 @@ class NewMemberView(PortfolioMembersPermissionView, FormMixin):
|
||||||
existing_user = UserPortfolioPermission.objects.get(user=requested_user, portfolio=self.object)
|
existing_user = UserPortfolioPermission.objects.get(user=requested_user, portfolio=self.object)
|
||||||
if existing_user:
|
if existing_user:
|
||||||
messages.warning(self.request, "User is already a member of this portfolio.")
|
messages.warning(self.request, "User is already a member of this portfolio.")
|
||||||
|
TerminalHelper.colorful_logger(logger.info, TerminalColors.OKCYAN, f"already a member")
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
self._send_portfolio_invitation_email(requested_email, requestor, add_success=False)
|
self._send_portfolio_invitation_email(requested_email, requestor, add_success=False)
|
||||||
|
TerminalHelper.colorful_logger(logger.info, TerminalColors.OKCYAN, f"..SEnding invitation")
|
||||||
except EmailSendingError:
|
except EmailSendingError:
|
||||||
logger.warn(
|
logger.warn(
|
||||||
"Could not send email invitation (EmailSendingError)",
|
"Could not send email invitation (EmailSendingError)",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue