unit tests

This commit is contained in:
Rachid Mrad 2024-02-01 17:23:09 -05:00
parent 211a4b52cf
commit 1130b5db8e
No known key found for this signature in database
4 changed files with 200 additions and 100 deletions

View file

@ -4,24 +4,27 @@
<nav aria-label="Form steps,">
<ul class="usa-sidenav">
{% for this_step in steps.all %}
{% if this_step in visited %}
<li class="usa-sidenav__item sidenav__step--locked">
<span>
{% if not this_step == steps.current %}
<svg class="usa-icon text-green" aria-hidden="true" focsuable="false" role="img" width="24" height="24">
<title id="checked-step__{{forloop.counter}}">Checked mark</title>
<use xlink:href="{%static 'img/sprite.svg'%}#check_circle"></use>
</svg>
{% endif %}
{% if this_step == steps.current %}
<a href="{% namespaced_url 'application' this_step %}"
{% if this_step == steps.current %}
class="usa-current"
{% else %}
class="link_usa-checked"
{% endif%}>
>
{{ form_titles|get_item:this_step }}
</a>
</span>
{% elif this_step in visited %}
<span>
{% if this_step != "review" %}
<svg class="usa-icon text-green" aria-hidden="true" focsuable="false" role="img" width="24" height="24">
<title id="checked-step__{{forloop.counter}}">Checked mark</title>
<use xlink:href="{%static 'img/sprite.svg'%}#check_circle"></use>
</svg>
{% endif %}
<a href="{% namespaced_url 'application' this_step %}"
class="link_usa-checked"
>
{{ form_titles|get_item:this_step }}
</a>
</span>
{% else %}
<li class="usa-sidenav__item sidenav__step--locked">
<span>

View file

@ -1,11 +1,13 @@
from unittest import skip
from unittest.mock import MagicMock, ANY, patch
from unittest.mock import MagicMock, ANY, Mock, patch
from django.conf import settings
from django.test import Client, TestCase
from django.urls import reverse
from django.contrib.auth import get_user_model
from django.contrib.sessions.models import Session
from .common import MockEppLib, MockSESClient, completed_application, create_user # type: ignore
from django_webtest import WebTest # type: ignore
import boto3_mocking # type: ignore
@ -2373,6 +2375,150 @@ class DomainApplicationTests(TestWithUser, WebTest):
self.assertContains(review_page, "You are about to submit an incomplete request")
class TestWizardUnlockingSteps(TestWithUser, WebTest):
def setUp(self):
super().setUp()
self.app.set_user(self.user.username)
self.wizard = ApplicationWizard()
# Mock the request object, its user, and session attributes appropriately
self.wizard.request = Mock(user=self.user, session={})
def tearDown(self):
super().tearDown()
def test_unlocked_steps_empty_application(self):
"""Test when all fields in the application are empty."""
unlocked_steps = self.wizard.db_check_for_unlocking_steps()
expected_dict = {
'about_your_organization': False,
'anything_else': False,
'authorizing_official': False,
'current_sites': False,
'dotgov_domain': False,
'organization_contact': False,
'organization_election': False,
'organization_federal': False,
'organization_type': False,
'other_contacts': False,
'purpose': False,
'requirements': False,
'review': False,
'tribal_government': False,
'your_contact': False
}
self.assertEqual(unlocked_steps, expected_dict)
def test_unlocked_steps_full_application(self):
"""Test when all fields in the application are filled."""
completed_application(status=DomainApplication.ApplicationStatus.STARTED, user=self.user)
# Make a request to the home page
home_page = self.app.get("/")
# 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)
# Assert that the response contains "city.gov"
self.assertContains(home_page, "city.gov")
# Click the "Edit" link
response = home_page.click("Edit", index=0)
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:
# Handle any potential errors while following the redirect
self.fail("Error following the redirect")
# Now 'detail_page' contains the response after following the redirect
self.assertEqual(detail_page.status_code, 200)
# 10 unlocked steps, one active step, the review step will have link_usa but not check_circle
self.assertContains(detail_page, "#check_circle", count=10)
# Type of organization
self.assertContains(detail_page, "usa-current", count=1)
self.assertContains(detail_page, "link_usa-checked", count=11)
else:
self.fail("Expected a redirect, but got a different response")
def test_unlocked_steps_partial_application(self):
"""Test when some fields in the application are filled."""
# Create the site and contacts to delete (orphaned)
contact = Contact.objects.create(
first_name="Henry",
last_name="Mcfakerson",
)
# Create two non-orphaned contacts
contact_2 = Contact.objects.create(
first_name="Saturn",
last_name="Mars",
)
# Attach a user object to a contact (should not be deleted)
contact_user, _ = Contact.objects.get_or_create(user=self.user)
site = DraftDomain.objects.create(name="igorville.gov")
application = DomainApplication.objects.create(
creator=self.user,
requested_domain=site,
status=DomainApplication.ApplicationStatus.WITHDRAWN,
authorizing_official=contact,
submitter=contact_user,
)
application.other_contacts.set([contact_2])
# Make a request to the home page
home_page = self.app.get("/")
# 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)
# Assert that the response contains "city.gov"
self.assertContains(home_page, "igorville.gov")
# Click the "Edit" link
response = home_page.click("Edit", index=0)
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:
# Handle any potential errors while following the redirect
self.fail("Error following the redirect")
# Now 'detail_page' contains the response after following the redirect
self.assertEqual(detail_page.status_code, 200)
# 5 unlocked steps (ao, domain, submitter, other contacts, and current sites which unlocks if domain exists),
# one active step, the review step is locked
self.assertContains(detail_page, "#check_circle", count=5)
# Type of organization
self.assertContains(detail_page, "usa-current", count=1)
self.assertContains(detail_page, "link_usa-checked", count=5)
else:
self.fail("Expected a redirect, but got a different response")
class TestWithDomainPermissions(TestWithUser):
def setUp(self):
super().setUp()

View file

@ -159,7 +159,11 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView):
def storage(self):
# marking session as modified on every access
# so that updates to nested keys are always saved
self.request.session.modified = True
# Also - check that self.request.session has the attr
# modified to account for test environments calling
# view methods
if hasattr(self.request.session, "modified"):
self.request.session.modified = True
return self.request.session.setdefault(self.prefix, {})
@storage.setter
@ -224,10 +228,8 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView):
if request.path_info == self.NEW_URL_NAME:
return render(request, "application_intro.html")
else:
logger.info('get calling self.steps.first')
return self.goto(self.steps.first)
logger.info('get setting current step')
self.steps.current = current_url
context = self.get_context_data()
context["forms"] = self.get_forms()
@ -256,7 +258,6 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView):
All arguments (**kwargs) are passed directly to `get_forms`.
"""
logger.info('get_all_forms gettig steps in self.steps')
nested = (self.get_forms(step=step, **kwargs) for step in self.steps)
flattened = [form for lst in nested for form in lst]
return flattened
@ -273,7 +274,6 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView):
An empty form will be provided if neither of those are true.
"""
logger.info('get_forms setting prefix to self.steps.current')
kwargs = {
"files": files,
"prefix": self.steps.current,
@ -335,64 +335,37 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView):
return DomainApplication.objects.filter(creator=self.request.user, status__in=check_statuses)
def db_check_for_unlocking_steps(self):
unlocked_steps = {}
if self.application.organization_type:
unlocked_steps["organization_type"] = True
if self.application.tribe_name:
unlocked_steps["tribal_government"] = True
if self.application.federal_agency:
unlocked_steps["organization_federal"] = True
if self.application.is_election_board:
unlocked_steps["organization_election"] = True
if (
self.application.organization_name
or self.application.address_line1
or self.application.city
or self.application.state_territory
or self.application.zipcode
or self.application.urbanization
):
unlocked_steps["organization_contact"] = True
if self.application.about_your_organization:
unlocked_steps["about_your_organization"] = True
if self.application.authorizing_official:
unlocked_steps["authorizing_official"] = True
# Since this field is optional, test to see if the next step has been touched
if self.application.current_websites.exists() or self.application.requested_domain:
unlocked_steps["current_sites"] = True
if self.application.requested_domain:
unlocked_steps["dotgov_domain"] = True
if self.application.purpose:
unlocked_steps["purpose"] = True
if self.application.submitter:
unlocked_steps["your_contact"] = True
if self.application.other_contacts.exists() or self.application.no_other_contacts_rationale:
unlocked_steps["other_contacts"] = True
# Since this field is optional, test to see if the next step has been touched
if self.application.anything_else or self.application.is_policy_acknowledged:
unlocked_steps["anything_else"] = True
if self.application.is_policy_acknowledged:
unlocked_steps["requirements"] = True
if self.application.submission_date:
unlocked_steps["review"] = True
return unlocked_steps
"""Helper for get_context_data
Queries the DB for an application and returns a dict for unlocked steps."""
return {
"organization_type": bool(self.application.organization_type),
"tribal_government": bool(self.application.tribe_name),
"organization_federal": bool(self.application.federal_type),
"organization_election": bool(self.application.is_election_board),
"organization_contact": (
bool(self.application.federal_agency) or bool(self.application.organization_name) or
bool(self.application.address_line1) or bool(self.application.city) or
bool(self.application.state_territory) or bool(self.application.zipcode) or
bool(self.application.urbanization)
),
"about_your_organization": bool(self.application.about_your_organization),
"authorizing_official": bool(self.application.authorizing_official),
"current_sites": (
bool(self.application.current_websites.exists()) or bool(self.application.requested_domain)
),
"dotgov_domain": bool(self.application.requested_domain),
"purpose": bool(self.application.purpose),
"your_contact": bool(self.application.submitter),
"other_contacts": (
bool(self.application.other_contacts.exists()) or bool(self.application.no_other_contacts_rationale)
),
"anything_else": (
bool(self.application.anything_else) or bool(self.application.is_policy_acknowledged)
),
"requirements": bool(self.application.is_policy_acknowledged),
"review": bool(self.application.is_policy_acknowledged),
}
def get_context_data(self):
"""Define context for access on all wizard pages."""
@ -404,10 +377,7 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView):
else:
modal_heading = "You are about to submit an incomplete request"
logger.info(f'get_context_data returning value for cisited equals to: {self.storage.get("step_history", [])}')
unlocked_steps_list = list(self.db_check_for_unlocking_steps().keys())
unlocked_steps_list = [key for key, value in self.db_check_for_unlocking_steps().items() if value]
return {
"form_titles": self.TITLES,
@ -431,7 +401,6 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView):
return step_list
def goto(self, step):
logger.info(f'goto sets self.steps.current to passed {step}')
self.steps.current = step
return redirect(reverse(f"{self.URL_NAMESPACE}:{step}"))
@ -440,7 +409,6 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView):
next = self.steps.next
if next:
self.steps.current = next
logger.info(f'goto sets self.goto_next_step.current to passed {self.steps.next}')
return self.goto(next)
else:
raise Http404()
@ -460,7 +428,6 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView):
if button == "intro_acknowledge":
if request.path_info == self.NEW_URL_NAME:
del self.storage
logger.info(f'post calling goto with {self.steps.first}')
return self.goto(self.steps.first)
# if accessing this class directly, redirect to the first step
@ -480,7 +447,6 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView):
# return them to the page they were already on
if button == "save":
messages.success(request, "Your progress has been saved!")
logger.info(f'post calling goto with {self.steps.current}')
return self.goto(self.steps.current)
# if user opted to save progress and return,
# return them to the home page

View file

@ -44,35 +44,28 @@ class StepsHelper:
"""
def __init__(self, wizard):
logger.info(f"steps_helper __init__")
self._wizard = wizard
def __dir__(self):
logger.info(f"steps_helper __dir__ {self.all}")
return self.all
def __len__(self):
logger.info(f"steps_helper __len__ {self.count}")
return self.count
def __repr__(self):
logger.info(f"steps_helper __repr__ {self._wizard} {self.all}")
return "<StepsHelper for %s (steps: %s)>" % (self._wizard, self.all)
def __getitem__(self, step):
logger.info(f"steps_helper __getitem__ {self.all[step]}")
return self.all[step]
@property
def all(self):
"""Returns the names of all steps."""
logger.info(f"steps_helper all {self._wizard.get_step_list()}")
return self._wizard.get_step_list()
@property
def count(self):
"""Returns the total number of steps/forms in this the wizard."""
logger.info(f"steps_helper count {len(self.all)}")
return len(self.all)
@property
@ -86,14 +79,12 @@ class StepsHelper:
current_url = resolve(self._wizard.request.path_info).url_name
step = current_url if current_url in self.all else self.first
self._wizard.storage["current_step"] = step
logger.info(f"steps_helper current getter {step}")
return step
@current.setter
def current(self, step: str):
"""Sets the current step. Updates step history."""
if step in self.all:
logger.info(f"steps_helper current setter {step}")
self._wizard.storage["current_step"] = step
else:
logger.debug("Invalid step name %s given to StepHelper" % str(step))
@ -106,13 +97,11 @@ class StepsHelper:
@property
def first(self):
"""Returns the name of the first step."""
logger.info(f"steps_helper first {self.all[0]}")
return self.all[0]
@property
def last(self):
"""Returns the name of the last step."""
logger.info(f"steps_helper last {self.all[-1]}")
return self.all[-1]
@property
@ -121,7 +110,6 @@ class StepsHelper:
steps = self.all
index = steps.index(self.current) + 1
if index < self.count:
logger.info(f"steps_helper next {steps[index]}")
return steps[index]
return None
@ -131,7 +119,6 @@ class StepsHelper:
steps = self.all
key = steps.index(self.current) - 1
if key >= 0:
logger.info(f"steps_helper prev {steps[key]}")
return steps[key]
return None
@ -140,12 +127,10 @@ class StepsHelper:
"""Returns the index for the current step."""
steps = self.all
if self.current in steps:
logger.info(f"steps_helper index {steps.index(self)}")
return steps.index(self)
return None
@property
def history(self):
"""Returns the list of already visited steps."""
logger.info(f"steps_helper history {self._wizard.storage.setdefault('step_history', [])}")
return self._wizard.storage.setdefault("step_history", [])