mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-18 18:39:21 +02:00
Merge pull request #1722 from cisagov/za/1411-make-statuses-more-meaningful
(On getgov-backup) Ticket #1411: make statuses more meaningful
This commit is contained in:
commit
005e5a5434
8 changed files with 451 additions and 257 deletions
|
@ -129,3 +129,28 @@ abbr[title] {
|
||||||
.flex-end {
|
.flex-end {
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only apply this custom wrapping to desktop
|
||||||
|
@include at-media(desktop) {
|
||||||
|
.usa-tooltip__body {
|
||||||
|
width: 350px;
|
||||||
|
white-space: normal;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include at-media(tablet) {
|
||||||
|
.usa-tooltip__body {
|
||||||
|
width: 250px !important;
|
||||||
|
white-space: normal !important;
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include at-media(mobile) {
|
||||||
|
.usa-tooltip__body {
|
||||||
|
width: 250px !important;
|
||||||
|
white-space: normal !important;
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -53,8 +53,8 @@ a.usa-button--unstyled.disabled-link:focus {
|
||||||
}
|
}
|
||||||
|
|
||||||
.usa-button--unstyled.disabled-button,
|
.usa-button--unstyled.disabled-button,
|
||||||
.usa-button--unstyled.disabled-link:hover,
|
.usa-button--unstyled.disabled-button:hover,
|
||||||
.usa-button--unstyled.disabled-link:focus {
|
.usa-button--unstyled.disabled-button:focus {
|
||||||
cursor: not-allowed !important;
|
cursor: not-allowed !important;
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
|
|
|
@ -26,6 +26,16 @@
|
||||||
padding-bottom: units(2px);
|
padding-bottom: units(2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
td .no-click-outline-and-cursor-help {
|
||||||
|
outline: none;
|
||||||
|
cursor: help;
|
||||||
|
use {
|
||||||
|
// USWDS has weird interactions with SVGs regarding tooltips,
|
||||||
|
// and other components. In this event, we need to disable pointer interactions.
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ticket #1510
|
// Ticket #1510
|
||||||
// @include at-media('desktop') {
|
// @include at-media('desktop') {
|
||||||
// th:first-child {
|
// th:first-child {
|
||||||
|
|
|
@ -140,6 +140,24 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
# previously existed but has been deleted from the registry
|
# previously existed but has been deleted from the registry
|
||||||
DELETED = "deleted", "Deleted"
|
DELETED = "deleted", "Deleted"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_help_text(cls, state) -> str:
|
||||||
|
"""Returns a help message for a desired state. If none is found, an empty string is returned"""
|
||||||
|
help_texts = {
|
||||||
|
# For now, unknown has the same message as DNS_NEEDED
|
||||||
|
cls.UNKNOWN: ("Before this domain can be used, " "you’ll need to add name server addresses."),
|
||||||
|
cls.DNS_NEEDED: ("Before this domain can be used, " "you’ll need to add name server addresses."),
|
||||||
|
cls.READY: "This domain has name servers and is ready for use.",
|
||||||
|
cls.ON_HOLD: (
|
||||||
|
"This domain is administratively paused, "
|
||||||
|
"so it can’t be edited and won’t resolve in DNS. "
|
||||||
|
"Contact help@get.gov for details."
|
||||||
|
),
|
||||||
|
cls.DELETED: ("This domain has been removed and " "is no longer registered to your organization."),
|
||||||
|
}
|
||||||
|
|
||||||
|
return help_texts.get(state, "")
|
||||||
|
|
||||||
class Cache(property):
|
class Cache(property):
|
||||||
"""
|
"""
|
||||||
Python descriptor to turn class methods into properties.
|
Python descriptor to turn class methods into properties.
|
||||||
|
@ -1398,6 +1416,21 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
logger.info("Changing to DNS_NEEDED state")
|
logger.info("Changing to DNS_NEEDED state")
|
||||||
logger.info("able to transition to DNS_NEEDED state")
|
logger.info("able to transition to DNS_NEEDED state")
|
||||||
|
|
||||||
|
def get_state_help_text(self) -> str:
|
||||||
|
"""Returns a str containing additional information about a given state.
|
||||||
|
Returns custom content for when the domain itself is expired."""
|
||||||
|
|
||||||
|
if self.is_expired() and self.state != self.State.UNKNOWN:
|
||||||
|
# Given expired is not a physical state, but it is displayed as such,
|
||||||
|
# We need custom logic to determine this message.
|
||||||
|
help_text = (
|
||||||
|
"This domain has expired, but it is still online. " "To renew this domain, contact help@get.gov."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
help_text = Domain.State.get_help_text(self.state)
|
||||||
|
|
||||||
|
return help_text
|
||||||
|
|
||||||
def _disclose_fields(self, contact: PublicContact):
|
def _disclose_fields(self, contact: PublicContact):
|
||||||
"""creates a disclose object that can be added to a contact Create using
|
"""creates a disclose object that can be added to a contact Create using
|
||||||
.disclose= <this function> on the command before sending.
|
.disclose= <this function> on the command before sending.
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<div class="margin-top-4 tablet:grid-col-10">
|
<div class="margin-top-4 tablet:grid-col-10">
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="usa-summary-box dotgov-status-box margin-top-3 padding-left-2{% if not domain.is_expired %}{% if domain.state == domain.State.UNKNOWN or domain.state == domain.State.DNS_NEEDED %} dotgov-status-box--action-need{% endif %}{% endif %}"
|
class="usa-summary-box dotgov-status-box padding-bottom-0 margin-top-3 padding-left-2{% if not domain.is_expired %}{% if domain.state == domain.State.UNKNOWN or domain.state == domain.State.DNS_NEEDED %} dotgov-status-box--action-need{% endif %}{% endif %}"
|
||||||
role="region"
|
role="region"
|
||||||
aria-labelledby="summary-box-key-information"
|
aria-labelledby="summary-box-key-information"
|
||||||
>
|
>
|
||||||
|
@ -17,6 +17,7 @@
|
||||||
<span class="text-bold text-primary-darker">
|
<span class="text-bold text-primary-darker">
|
||||||
Status:
|
Status:
|
||||||
</span>
|
</span>
|
||||||
|
<span class="text-primary-darker">
|
||||||
{# UNKNOWN domains would not have an expiration date and thus would show 'Expired' #}
|
{# UNKNOWN domains would not have an expiration date and thus would show 'Expired' #}
|
||||||
{% if domain.is_expired and domain.state != domain.State.UNKNOWN %}
|
{% if domain.is_expired and domain.state != domain.State.UNKNOWN %}
|
||||||
Expired
|
Expired
|
||||||
|
@ -25,6 +26,12 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ domain.state|title }}
|
{{ domain.state|title }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
{% if domain.get_state_help_text %}
|
||||||
|
<div class="padding-top-1 text-primary-darker">
|
||||||
|
{{ domain.get_state_help_text }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -56,6 +56,16 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ domain.state|capfirst }}
|
{{ domain.state|capfirst }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<svg
|
||||||
|
class="usa-icon usa-tooltip text-middle margin-bottom-05 text-accent-cool no-click-outline-and-cursor-help"
|
||||||
|
data-position="top"
|
||||||
|
title="{{domain.get_state_help_text}}"
|
||||||
|
focusable="true"
|
||||||
|
aria-label="Status Information"
|
||||||
|
role="tooltip"
|
||||||
|
>
|
||||||
|
<use aria-hidden="true" xlink:href="{%static 'img/sprite.svg'%}#info_outline"></use>
|
||||||
|
</svg>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url "domain" pk=domain.pk %}">
|
<a href="{% url "domain" pk=domain.pk %}">
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
from django.test import Client, TestCase
|
from django.test import Client, TestCase
|
||||||
from django.urls import reverse
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
from .common import MockEppLib # type: ignore
|
from .common import MockEppLib # type: ignore
|
||||||
|
@ -8,11 +7,7 @@ from .common import MockEppLib # type: ignore
|
||||||
from registrar.models import (
|
from registrar.models import (
|
||||||
DomainApplication,
|
DomainApplication,
|
||||||
DomainInformation,
|
DomainInformation,
|
||||||
DraftDomain,
|
|
||||||
Contact,
|
|
||||||
User,
|
|
||||||
)
|
)
|
||||||
from .common import less_console_noise
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -55,252 +50,3 @@ class TestWithUser(MockEppLib):
|
||||||
DomainApplication.objects.all().delete()
|
DomainApplication.objects.all().delete()
|
||||||
DomainInformation.objects.all().delete()
|
DomainInformation.objects.all().delete()
|
||||||
self.user.delete()
|
self.user.delete()
|
||||||
|
|
||||||
|
|
||||||
class LoggedInTests(TestWithUser):
|
|
||||||
def setUp(self):
|
|
||||||
super().setUp()
|
|
||||||
self.client.force_login(self.user)
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
super().tearDown()
|
|
||||||
Contact.objects.all().delete()
|
|
||||||
|
|
||||||
def test_home_lists_domain_applications(self):
|
|
||||||
response = self.client.get("/")
|
|
||||||
self.assertNotContains(response, "igorville.gov")
|
|
||||||
site = DraftDomain.objects.create(name="igorville.gov")
|
|
||||||
application = DomainApplication.objects.create(creator=self.user, requested_domain=site)
|
|
||||||
response = self.client.get("/")
|
|
||||||
|
|
||||||
# count = 7 because of screenreader content
|
|
||||||
self.assertContains(response, "igorville.gov", count=7)
|
|
||||||
|
|
||||||
# clean up
|
|
||||||
application.delete()
|
|
||||||
|
|
||||||
def test_home_deletes_withdrawn_domain_application(self):
|
|
||||||
"""Tests if the user can delete a DomainApplication in the 'withdrawn' status"""
|
|
||||||
|
|
||||||
site = DraftDomain.objects.create(name="igorville.gov")
|
|
||||||
application = DomainApplication.objects.create(
|
|
||||||
creator=self.user, requested_domain=site, status=DomainApplication.ApplicationStatus.WITHDRAWN
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ensure that igorville.gov exists on the page
|
|
||||||
home_page = self.client.get("/")
|
|
||||||
self.assertContains(home_page, "igorville.gov")
|
|
||||||
|
|
||||||
# Check if the delete button exists. We can do this by checking for its id and text content.
|
|
||||||
self.assertContains(home_page, "Delete")
|
|
||||||
self.assertContains(home_page, "button-toggle-delete-domain-alert-1")
|
|
||||||
|
|
||||||
# Trigger the delete logic
|
|
||||||
response = self.client.post(reverse("application-delete", kwargs={"pk": application.pk}), follow=True)
|
|
||||||
|
|
||||||
self.assertNotContains(response, "igorville.gov")
|
|
||||||
|
|
||||||
# clean up
|
|
||||||
application.delete()
|
|
||||||
|
|
||||||
def test_home_deletes_started_domain_application(self):
|
|
||||||
"""Tests if the user can delete a DomainApplication in the 'started' status"""
|
|
||||||
|
|
||||||
site = DraftDomain.objects.create(name="igorville.gov")
|
|
||||||
application = DomainApplication.objects.create(
|
|
||||||
creator=self.user, requested_domain=site, status=DomainApplication.ApplicationStatus.STARTED
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ensure that igorville.gov exists on the page
|
|
||||||
home_page = self.client.get("/")
|
|
||||||
self.assertContains(home_page, "igorville.gov")
|
|
||||||
|
|
||||||
# Check if the delete button exists. We can do this by checking for its id and text content.
|
|
||||||
self.assertContains(home_page, "Delete")
|
|
||||||
self.assertContains(home_page, "button-toggle-delete-domain-alert-1")
|
|
||||||
|
|
||||||
# Trigger the delete logic
|
|
||||||
response = self.client.post(reverse("application-delete", kwargs={"pk": application.pk}), follow=True)
|
|
||||||
|
|
||||||
self.assertNotContains(response, "igorville.gov")
|
|
||||||
|
|
||||||
# clean up
|
|
||||||
application.delete()
|
|
||||||
|
|
||||||
def test_home_doesnt_delete_other_domain_applications(self):
|
|
||||||
"""Tests to ensure the user can't delete Applications not in the status of STARTED or WITHDRAWN"""
|
|
||||||
|
|
||||||
# Given that we are including a subset of items that can be deleted while excluding the rest,
|
|
||||||
# subTest is appropriate here as otherwise we would need many duplicate tests for the same reason.
|
|
||||||
with less_console_noise():
|
|
||||||
draft_domain = DraftDomain.objects.create(name="igorville.gov")
|
|
||||||
for status in DomainApplication.ApplicationStatus:
|
|
||||||
if status not in [
|
|
||||||
DomainApplication.ApplicationStatus.STARTED,
|
|
||||||
DomainApplication.ApplicationStatus.WITHDRAWN,
|
|
||||||
]:
|
|
||||||
with self.subTest(status=status):
|
|
||||||
application = DomainApplication.objects.create(
|
|
||||||
creator=self.user, requested_domain=draft_domain, status=status
|
|
||||||
)
|
|
||||||
|
|
||||||
# Trigger the delete logic
|
|
||||||
response = self.client.post(
|
|
||||||
reverse("application-delete", kwargs={"pk": application.pk}), follow=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check for a 403 error - the end user should not be allowed to do this
|
|
||||||
self.assertEqual(response.status_code, 403)
|
|
||||||
|
|
||||||
desired_application = DomainApplication.objects.filter(requested_domain=draft_domain)
|
|
||||||
|
|
||||||
# Make sure the DomainApplication wasn't deleted
|
|
||||||
self.assertEqual(desired_application.count(), 1)
|
|
||||||
|
|
||||||
# clean up
|
|
||||||
application.delete()
|
|
||||||
|
|
||||||
def test_home_deletes_domain_application_and_orphans(self):
|
|
||||||
"""Tests if delete for DomainApplication deletes orphaned Contact objects"""
|
|
||||||
|
|
||||||
# Create the site and contacts to delete (orphaned)
|
|
||||||
contact = Contact.objects.create(
|
|
||||||
first_name="Henry",
|
|
||||||
last_name="Mcfakerson",
|
|
||||||
)
|
|
||||||
contact_shared = Contact.objects.create(
|
|
||||||
first_name="Relative",
|
|
||||||
last_name="Aether",
|
|
||||||
)
|
|
||||||
|
|
||||||
# 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])
|
|
||||||
|
|
||||||
# Create a second application to attach contacts to
|
|
||||||
site_2 = DraftDomain.objects.create(name="teaville.gov")
|
|
||||||
application_2 = DomainApplication.objects.create(
|
|
||||||
creator=self.user,
|
|
||||||
requested_domain=site_2,
|
|
||||||
status=DomainApplication.ApplicationStatus.STARTED,
|
|
||||||
authorizing_official=contact_2,
|
|
||||||
submitter=contact_shared,
|
|
||||||
)
|
|
||||||
application_2.other_contacts.set([contact_shared])
|
|
||||||
|
|
||||||
# Ensure that igorville.gov exists on the page
|
|
||||||
home_page = self.client.get("/")
|
|
||||||
self.assertContains(home_page, "igorville.gov")
|
|
||||||
|
|
||||||
# Trigger the delete logic
|
|
||||||
response = self.client.post(reverse("application-delete", kwargs={"pk": application.pk}), follow=True)
|
|
||||||
|
|
||||||
# igorville is now deleted
|
|
||||||
self.assertNotContains(response, "igorville.gov")
|
|
||||||
|
|
||||||
# Check if the orphaned contact was deleted
|
|
||||||
orphan = Contact.objects.filter(id=contact.id)
|
|
||||||
self.assertFalse(orphan.exists())
|
|
||||||
|
|
||||||
# All non-orphan contacts should still exist and are unaltered
|
|
||||||
try:
|
|
||||||
current_user = Contact.objects.filter(id=contact_user.id).get()
|
|
||||||
except Contact.DoesNotExist:
|
|
||||||
self.fail("contact_user (a non-orphaned contact) was deleted")
|
|
||||||
|
|
||||||
self.assertEqual(current_user, contact_user)
|
|
||||||
try:
|
|
||||||
edge_case = Contact.objects.filter(id=contact_2.id).get()
|
|
||||||
except Contact.DoesNotExist:
|
|
||||||
self.fail("contact_2 (a non-orphaned contact) was deleted")
|
|
||||||
|
|
||||||
self.assertEqual(edge_case, contact_2)
|
|
||||||
|
|
||||||
def test_home_deletes_domain_application_and_shared_orphans(self):
|
|
||||||
"""Test the edge case for an object that will become orphaned after a delete
|
|
||||||
(but is not an orphan at the time of deletion)"""
|
|
||||||
|
|
||||||
# Create the site and contacts to delete (orphaned)
|
|
||||||
contact = Contact.objects.create(
|
|
||||||
first_name="Henry",
|
|
||||||
last_name="Mcfakerson",
|
|
||||||
)
|
|
||||||
contact_shared = Contact.objects.create(
|
|
||||||
first_name="Relative",
|
|
||||||
last_name="Aether",
|
|
||||||
)
|
|
||||||
|
|
||||||
# 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])
|
|
||||||
|
|
||||||
# Create a second application to attach contacts to
|
|
||||||
site_2 = DraftDomain.objects.create(name="teaville.gov")
|
|
||||||
application_2 = DomainApplication.objects.create(
|
|
||||||
creator=self.user,
|
|
||||||
requested_domain=site_2,
|
|
||||||
status=DomainApplication.ApplicationStatus.STARTED,
|
|
||||||
authorizing_official=contact_2,
|
|
||||||
submitter=contact_shared,
|
|
||||||
)
|
|
||||||
application_2.other_contacts.set([contact_shared])
|
|
||||||
|
|
||||||
home_page = self.client.get("/")
|
|
||||||
self.assertContains(home_page, "teaville.gov")
|
|
||||||
|
|
||||||
# Trigger the delete logic
|
|
||||||
response = self.client.post(reverse("application-delete", kwargs={"pk": application_2.pk}), follow=True)
|
|
||||||
|
|
||||||
self.assertNotContains(response, "teaville.gov")
|
|
||||||
|
|
||||||
# Check if the orphaned contact was deleted
|
|
||||||
orphan = Contact.objects.filter(id=contact_shared.id)
|
|
||||||
self.assertFalse(orphan.exists())
|
|
||||||
|
|
||||||
def test_application_form_view(self):
|
|
||||||
response = self.client.get("/request/", follow=True)
|
|
||||||
self.assertContains(
|
|
||||||
response,
|
|
||||||
"You’re about to start your .gov domain request.",
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_domain_application_form_with_ineligible_user(self):
|
|
||||||
"""Application form not accessible for an ineligible user.
|
|
||||||
This test should be solid enough since all application wizard
|
|
||||||
views share the same permissions class"""
|
|
||||||
self.user.status = User.RESTRICTED
|
|
||||||
self.user.save()
|
|
||||||
|
|
||||||
with less_console_noise():
|
|
||||||
response = self.client.get("/request/", follow=True)
|
|
||||||
self.assertEqual(response.status_code, 403)
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ from unittest.mock import Mock
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
from .common import MockSESClient, completed_application # type: ignore
|
from .common import MockSESClient, completed_application # type: ignore
|
||||||
from django_webtest import WebTest # type: ignore
|
from django_webtest import WebTest # type: ignore
|
||||||
|
@ -16,6 +17,7 @@ from registrar.models import (
|
||||||
Contact,
|
Contact,
|
||||||
User,
|
User,
|
||||||
Website,
|
Website,
|
||||||
|
UserDomainRole,
|
||||||
)
|
)
|
||||||
from registrar.views.application import ApplicationWizard, Step
|
from registrar.views.application import ApplicationWizard, Step
|
||||||
|
|
||||||
|
@ -2327,3 +2329,364 @@ class TestWizardUnlockingSteps(TestWithUser, WebTest):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.fail(f"Expected a redirect, but got a different response: {response}")
|
self.fail(f"Expected a redirect, but got a different response: {response}")
|
||||||
|
|
||||||
|
|
||||||
|
class HomeTests(TestWithUser):
|
||||||
|
"""A series of tests that target the two tables on home.html"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super().tearDown()
|
||||||
|
Contact.objects.all().delete()
|
||||||
|
|
||||||
|
def test_home_lists_domain_applications(self):
|
||||||
|
response = self.client.get("/")
|
||||||
|
self.assertNotContains(response, "igorville.gov")
|
||||||
|
site = DraftDomain.objects.create(name="igorville.gov")
|
||||||
|
application = DomainApplication.objects.create(creator=self.user, requested_domain=site)
|
||||||
|
response = self.client.get("/")
|
||||||
|
|
||||||
|
# count = 7 because of screenreader content
|
||||||
|
self.assertContains(response, "igorville.gov", count=7)
|
||||||
|
|
||||||
|
# clean up
|
||||||
|
application.delete()
|
||||||
|
|
||||||
|
def test_state_help_text(self):
|
||||||
|
"""Tests if each domain state has help text"""
|
||||||
|
|
||||||
|
# Get the expected text content of each state
|
||||||
|
deleted_text = "This domain has been removed and " "is no longer registered to your organization."
|
||||||
|
dns_needed_text = "Before this domain can be used, " "you’ll need to add name server addresses."
|
||||||
|
ready_text = "This domain has name servers and is ready for use."
|
||||||
|
on_hold_text = (
|
||||||
|
"This domain is administratively paused, "
|
||||||
|
"so it can’t be edited and won’t resolve in DNS. "
|
||||||
|
"Contact help@get.gov for details."
|
||||||
|
)
|
||||||
|
deleted_text = "This domain has been removed and " "is no longer registered to your organization."
|
||||||
|
# Generate a mapping of domain names, the state, and expected messages for the subtest
|
||||||
|
test_cases = [
|
||||||
|
("deleted.gov", Domain.State.DELETED, deleted_text),
|
||||||
|
("dnsneeded.gov", Domain.State.DNS_NEEDED, dns_needed_text),
|
||||||
|
("unknown.gov", Domain.State.UNKNOWN, dns_needed_text),
|
||||||
|
("onhold.gov", Domain.State.ON_HOLD, on_hold_text),
|
||||||
|
("ready.gov", Domain.State.READY, ready_text),
|
||||||
|
]
|
||||||
|
for domain_name, state, expected_message in test_cases:
|
||||||
|
with self.subTest(domain_name=domain_name, state=state, expected_message=expected_message):
|
||||||
|
# Create a domain and a UserRole with the given params
|
||||||
|
test_domain, _ = Domain.objects.get_or_create(name=domain_name, state=state)
|
||||||
|
test_domain.expiration_date = date.today()
|
||||||
|
test_domain.save()
|
||||||
|
|
||||||
|
user_role, _ = UserDomainRole.objects.get_or_create(
|
||||||
|
user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER
|
||||||
|
)
|
||||||
|
|
||||||
|
# Grab the home page
|
||||||
|
response = self.client.get("/")
|
||||||
|
|
||||||
|
# Make sure the user can actually see the domain.
|
||||||
|
# We expect two instances because of SR content.
|
||||||
|
self.assertContains(response, domain_name, count=2)
|
||||||
|
|
||||||
|
# Check that we have the right text content.
|
||||||
|
self.assertContains(response, expected_message, count=1)
|
||||||
|
|
||||||
|
# Delete the role and domain to ensure we're testing in isolation
|
||||||
|
user_role.delete()
|
||||||
|
test_domain.delete()
|
||||||
|
|
||||||
|
def test_state_help_text_expired(self):
|
||||||
|
"""Tests if each domain state has help text when expired"""
|
||||||
|
expired_text = "This domain has expired, but it is still online. " "To renew this domain, contact help@get.gov."
|
||||||
|
test_domain, _ = Domain.objects.get_or_create(name="expired.gov", state=Domain.State.READY)
|
||||||
|
test_domain.expiration_date = date(2011, 10, 10)
|
||||||
|
test_domain.save()
|
||||||
|
|
||||||
|
UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER)
|
||||||
|
|
||||||
|
# Grab the home page
|
||||||
|
response = self.client.get("/")
|
||||||
|
|
||||||
|
# Make sure the user can actually see the domain.
|
||||||
|
# We expect two instances because of SR content.
|
||||||
|
self.assertContains(response, "expired.gov", count=2)
|
||||||
|
|
||||||
|
# Check that we have the right text content.
|
||||||
|
self.assertContains(response, expired_text, count=1)
|
||||||
|
|
||||||
|
def test_state_help_text_no_expiration_date(self):
|
||||||
|
"""Tests if each domain state has help text when expiration date is None"""
|
||||||
|
|
||||||
|
# == Test a expiration of None for state ready. This should be expired. == #
|
||||||
|
expired_text = "This domain has expired, but it is still online. " "To renew this domain, contact help@get.gov."
|
||||||
|
test_domain, _ = Domain.objects.get_or_create(name="imexpired.gov", state=Domain.State.READY)
|
||||||
|
test_domain.expiration_date = None
|
||||||
|
test_domain.save()
|
||||||
|
|
||||||
|
UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER)
|
||||||
|
|
||||||
|
# Grab the home page
|
||||||
|
response = self.client.get("/")
|
||||||
|
|
||||||
|
# Make sure the user can actually see the domain.
|
||||||
|
# We expect two instances because of SR content.
|
||||||
|
self.assertContains(response, "imexpired.gov", count=2)
|
||||||
|
|
||||||
|
# Make sure the expiration date is None
|
||||||
|
self.assertEqual(test_domain.expiration_date, None)
|
||||||
|
|
||||||
|
# Check that we have the right text content.
|
||||||
|
self.assertContains(response, expired_text, count=1)
|
||||||
|
|
||||||
|
# == Test a expiration of None for state unknown. This should not display expired text. == #
|
||||||
|
unknown_text = "Before this domain can be used, " "you’ll need to add name server addresses."
|
||||||
|
test_domain_2, _ = Domain.objects.get_or_create(name="notexpired.gov", state=Domain.State.UNKNOWN)
|
||||||
|
test_domain_2.expiration_date = None
|
||||||
|
test_domain_2.save()
|
||||||
|
|
||||||
|
UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain_2, role=UserDomainRole.Roles.MANAGER)
|
||||||
|
|
||||||
|
# Grab the home page
|
||||||
|
response = self.client.get("/")
|
||||||
|
|
||||||
|
# Make sure the user can actually see the domain.
|
||||||
|
# We expect two instances because of SR content.
|
||||||
|
self.assertContains(response, "notexpired.gov", count=2)
|
||||||
|
|
||||||
|
# Make sure the expiration date is None
|
||||||
|
self.assertEqual(test_domain_2.expiration_date, None)
|
||||||
|
|
||||||
|
# Check that we have the right text content.
|
||||||
|
self.assertContains(response, unknown_text, count=1)
|
||||||
|
|
||||||
|
def test_home_deletes_withdrawn_domain_application(self):
|
||||||
|
"""Tests if the user can delete a DomainApplication in the 'withdrawn' status"""
|
||||||
|
|
||||||
|
site = DraftDomain.objects.create(name="igorville.gov")
|
||||||
|
application = DomainApplication.objects.create(
|
||||||
|
creator=self.user, requested_domain=site, status=DomainApplication.ApplicationStatus.WITHDRAWN
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ensure that igorville.gov exists on the page
|
||||||
|
home_page = self.client.get("/")
|
||||||
|
self.assertContains(home_page, "igorville.gov")
|
||||||
|
|
||||||
|
# Check if the delete button exists. We can do this by checking for its id and text content.
|
||||||
|
self.assertContains(home_page, "Delete")
|
||||||
|
self.assertContains(home_page, "button-toggle-delete-domain-alert-1")
|
||||||
|
|
||||||
|
# Trigger the delete logic
|
||||||
|
response = self.client.post(reverse("application-delete", kwargs={"pk": application.pk}), follow=True)
|
||||||
|
|
||||||
|
self.assertNotContains(response, "igorville.gov")
|
||||||
|
|
||||||
|
# clean up
|
||||||
|
application.delete()
|
||||||
|
|
||||||
|
def test_home_deletes_started_domain_application(self):
|
||||||
|
"""Tests if the user can delete a DomainApplication in the 'started' status"""
|
||||||
|
|
||||||
|
site = DraftDomain.objects.create(name="igorville.gov")
|
||||||
|
application = DomainApplication.objects.create(
|
||||||
|
creator=self.user, requested_domain=site, status=DomainApplication.ApplicationStatus.STARTED
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ensure that igorville.gov exists on the page
|
||||||
|
home_page = self.client.get("/")
|
||||||
|
self.assertContains(home_page, "igorville.gov")
|
||||||
|
|
||||||
|
# Check if the delete button exists. We can do this by checking for its id and text content.
|
||||||
|
self.assertContains(home_page, "Delete")
|
||||||
|
self.assertContains(home_page, "button-toggle-delete-domain-alert-1")
|
||||||
|
|
||||||
|
# Trigger the delete logic
|
||||||
|
response = self.client.post(reverse("application-delete", kwargs={"pk": application.pk}), follow=True)
|
||||||
|
|
||||||
|
self.assertNotContains(response, "igorville.gov")
|
||||||
|
|
||||||
|
# clean up
|
||||||
|
application.delete()
|
||||||
|
|
||||||
|
def test_home_doesnt_delete_other_domain_applications(self):
|
||||||
|
"""Tests to ensure the user can't delete Applications not in the status of STARTED or WITHDRAWN"""
|
||||||
|
|
||||||
|
# Given that we are including a subset of items that can be deleted while excluding the rest,
|
||||||
|
# subTest is appropriate here as otherwise we would need many duplicate tests for the same reason.
|
||||||
|
with less_console_noise():
|
||||||
|
draft_domain = DraftDomain.objects.create(name="igorville.gov")
|
||||||
|
for status in DomainApplication.ApplicationStatus:
|
||||||
|
if status not in [
|
||||||
|
DomainApplication.ApplicationStatus.STARTED,
|
||||||
|
DomainApplication.ApplicationStatus.WITHDRAWN,
|
||||||
|
]:
|
||||||
|
with self.subTest(status=status):
|
||||||
|
application = DomainApplication.objects.create(
|
||||||
|
creator=self.user, requested_domain=draft_domain, status=status
|
||||||
|
)
|
||||||
|
|
||||||
|
# Trigger the delete logic
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("application-delete", kwargs={"pk": application.pk}), follow=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check for a 403 error - the end user should not be allowed to do this
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
desired_application = DomainApplication.objects.filter(requested_domain=draft_domain)
|
||||||
|
|
||||||
|
# Make sure the DomainApplication wasn't deleted
|
||||||
|
self.assertEqual(desired_application.count(), 1)
|
||||||
|
|
||||||
|
# clean up
|
||||||
|
application.delete()
|
||||||
|
|
||||||
|
def test_home_deletes_domain_application_and_orphans(self):
|
||||||
|
"""Tests if delete for DomainApplication deletes orphaned Contact objects"""
|
||||||
|
|
||||||
|
# Create the site and contacts to delete (orphaned)
|
||||||
|
contact = Contact.objects.create(
|
||||||
|
first_name="Henry",
|
||||||
|
last_name="Mcfakerson",
|
||||||
|
)
|
||||||
|
contact_shared = Contact.objects.create(
|
||||||
|
first_name="Relative",
|
||||||
|
last_name="Aether",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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])
|
||||||
|
|
||||||
|
# Create a second application to attach contacts to
|
||||||
|
site_2 = DraftDomain.objects.create(name="teaville.gov")
|
||||||
|
application_2 = DomainApplication.objects.create(
|
||||||
|
creator=self.user,
|
||||||
|
requested_domain=site_2,
|
||||||
|
status=DomainApplication.ApplicationStatus.STARTED,
|
||||||
|
authorizing_official=contact_2,
|
||||||
|
submitter=contact_shared,
|
||||||
|
)
|
||||||
|
application_2.other_contacts.set([contact_shared])
|
||||||
|
|
||||||
|
# Ensure that igorville.gov exists on the page
|
||||||
|
home_page = self.client.get("/")
|
||||||
|
self.assertContains(home_page, "igorville.gov")
|
||||||
|
|
||||||
|
# Trigger the delete logic
|
||||||
|
response = self.client.post(reverse("application-delete", kwargs={"pk": application.pk}), follow=True)
|
||||||
|
|
||||||
|
# igorville is now deleted
|
||||||
|
self.assertNotContains(response, "igorville.gov")
|
||||||
|
|
||||||
|
# Check if the orphaned contact was deleted
|
||||||
|
orphan = Contact.objects.filter(id=contact.id)
|
||||||
|
self.assertFalse(orphan.exists())
|
||||||
|
|
||||||
|
# All non-orphan contacts should still exist and are unaltered
|
||||||
|
try:
|
||||||
|
current_user = Contact.objects.filter(id=contact_user.id).get()
|
||||||
|
except Contact.DoesNotExist:
|
||||||
|
self.fail("contact_user (a non-orphaned contact) was deleted")
|
||||||
|
|
||||||
|
self.assertEqual(current_user, contact_user)
|
||||||
|
try:
|
||||||
|
edge_case = Contact.objects.filter(id=contact_2.id).get()
|
||||||
|
except Contact.DoesNotExist:
|
||||||
|
self.fail("contact_2 (a non-orphaned contact) was deleted")
|
||||||
|
|
||||||
|
self.assertEqual(edge_case, contact_2)
|
||||||
|
|
||||||
|
def test_home_deletes_domain_application_and_shared_orphans(self):
|
||||||
|
"""Test the edge case for an object that will become orphaned after a delete
|
||||||
|
(but is not an orphan at the time of deletion)"""
|
||||||
|
|
||||||
|
# Create the site and contacts to delete (orphaned)
|
||||||
|
contact = Contact.objects.create(
|
||||||
|
first_name="Henry",
|
||||||
|
last_name="Mcfakerson",
|
||||||
|
)
|
||||||
|
contact_shared = Contact.objects.create(
|
||||||
|
first_name="Relative",
|
||||||
|
last_name="Aether",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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])
|
||||||
|
|
||||||
|
# Create a second application to attach contacts to
|
||||||
|
site_2 = DraftDomain.objects.create(name="teaville.gov")
|
||||||
|
application_2 = DomainApplication.objects.create(
|
||||||
|
creator=self.user,
|
||||||
|
requested_domain=site_2,
|
||||||
|
status=DomainApplication.ApplicationStatus.STARTED,
|
||||||
|
authorizing_official=contact_2,
|
||||||
|
submitter=contact_shared,
|
||||||
|
)
|
||||||
|
application_2.other_contacts.set([contact_shared])
|
||||||
|
|
||||||
|
home_page = self.client.get("/")
|
||||||
|
self.assertContains(home_page, "teaville.gov")
|
||||||
|
|
||||||
|
# Trigger the delete logic
|
||||||
|
response = self.client.post(reverse("application-delete", kwargs={"pk": application_2.pk}), follow=True)
|
||||||
|
|
||||||
|
self.assertNotContains(response, "teaville.gov")
|
||||||
|
|
||||||
|
# Check if the orphaned contact was deleted
|
||||||
|
orphan = Contact.objects.filter(id=contact_shared.id)
|
||||||
|
self.assertFalse(orphan.exists())
|
||||||
|
|
||||||
|
def test_application_form_view(self):
|
||||||
|
response = self.client.get("/request/", follow=True)
|
||||||
|
self.assertContains(
|
||||||
|
response,
|
||||||
|
"You’re about to start your .gov domain request.",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_domain_application_form_with_ineligible_user(self):
|
||||||
|
"""Application form not accessible for an ineligible user.
|
||||||
|
This test should be solid enough since all application wizard
|
||||||
|
views share the same permissions class"""
|
||||||
|
self.user.status = User.RESTRICTED
|
||||||
|
self.user.save()
|
||||||
|
|
||||||
|
with less_console_noise():
|
||||||
|
response = self.client.get("/request/", follow=True)
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue