mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-30 14:36:32 +02:00
1099 lines
44 KiB
Python
1099 lines
44 KiB
Python
from datetime import date
|
|
from django.test import TestCase, RequestFactory, Client, override_settings
|
|
from django.contrib.admin.sites import AdminSite
|
|
from api.tests.common import less_console_noise_decorator
|
|
from django_webtest import WebTest # type: ignore
|
|
from django.contrib import messages
|
|
from django.urls import reverse
|
|
from registrar.admin import (
|
|
DomainAdmin,
|
|
)
|
|
from registrar.models import (
|
|
Domain,
|
|
DomainRequest,
|
|
DomainInformation,
|
|
DomainInvitation,
|
|
User,
|
|
Host,
|
|
Portfolio,
|
|
)
|
|
from registrar.models.public_contact import PublicContact
|
|
from registrar.models.user_domain_role import UserDomainRole
|
|
from .common import (
|
|
MockSESClient,
|
|
completed_domain_request,
|
|
less_console_noise,
|
|
create_superuser,
|
|
create_user,
|
|
create_ready_domain,
|
|
MockEppLib,
|
|
GenericTestHelper,
|
|
)
|
|
from unittest.mock import ANY, call, patch
|
|
|
|
import boto3_mocking # type: ignore
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class TestDomainAdminAsStaff(MockEppLib):
|
|
"""Test DomainAdmin class as staff user.
|
|
|
|
Notes:
|
|
all tests share staffuser; do not change staffuser model in tests
|
|
tests have available staffuser, client, and admin
|
|
"""
|
|
|
|
@classmethod
|
|
def setUpClass(self):
|
|
super().setUpClass()
|
|
self.staffuser = create_user()
|
|
self.site = AdminSite()
|
|
self.admin = DomainAdmin(model=Domain, admin_site=self.site)
|
|
self.factory = RequestFactory()
|
|
|
|
def setUp(self):
|
|
self.client = Client(HTTP_HOST="localhost:8080")
|
|
self.client.force_login(self.staffuser)
|
|
super().setUp()
|
|
|
|
def tearDown(self):
|
|
super().tearDown()
|
|
Host.objects.all().delete()
|
|
PublicContact.objects.all().delete()
|
|
Domain.objects.all().delete()
|
|
DomainInformation.objects.all().delete()
|
|
DomainRequest.objects.all().delete()
|
|
|
|
@classmethod
|
|
def tearDownClass(self):
|
|
User.objects.all().delete()
|
|
super().tearDownClass()
|
|
|
|
@less_console_noise_decorator
|
|
def test_staff_can_see_cisa_region_federal(self):
|
|
"""Tests if staff can see CISA Region: N/A"""
|
|
|
|
# Create a fake domain request
|
|
_domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
|
|
_domain_request.approve()
|
|
|
|
domain = _domain_request.approved_domain
|
|
response = self.client.get(
|
|
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
|
follow=True,
|
|
)
|
|
|
|
# Make sure the page loaded, and that we're on the right page
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, domain.name)
|
|
|
|
# Test if the page has the right CISA region
|
|
expected_html = '<div class="flex-container margin-top-2"><span>CISA region: N/A</span></div>'
|
|
# Remove whitespace from expected_html
|
|
expected_html = "".join(expected_html.split())
|
|
|
|
# Remove whitespace from response content
|
|
response_content = "".join(response.content.decode().split())
|
|
|
|
# Check if response contains expected_html
|
|
self.assertIn(expected_html, response_content)
|
|
|
|
@less_console_noise_decorator
|
|
def test_staff_can_see_cisa_region_non_federal(self):
|
|
"""Tests if staff can see the correct CISA region"""
|
|
|
|
# Create a fake domain request. State will be NY (2).
|
|
_domain_request = completed_domain_request(
|
|
status=DomainRequest.DomainRequestStatus.IN_REVIEW, generic_org_type="interstate"
|
|
)
|
|
|
|
_domain_request.approve()
|
|
|
|
domain = _domain_request.approved_domain
|
|
|
|
response = self.client.get(
|
|
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
|
follow=True,
|
|
)
|
|
|
|
# Make sure the page loaded, and that we're on the right page
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, domain.name)
|
|
|
|
# Test if the page has the right CISA region
|
|
expected_html = '<div class="flex-container margin-top-2"><span>CISA region: 2</span></div>'
|
|
# Remove whitespace from expected_html
|
|
expected_html = "".join(expected_html.split())
|
|
|
|
# Remove whitespace from response content
|
|
response_content = "".join(response.content.decode().split())
|
|
|
|
# Check if response contains expected_html
|
|
self.assertIn(expected_html, response_content)
|
|
|
|
@less_console_noise_decorator
|
|
def test_analyst_can_see_inline_domain_information_in_domain_change_form(self):
|
|
"""Tests if an analyst can still see the inline domain information form"""
|
|
|
|
# Create fake creator
|
|
_creator = User.objects.create(
|
|
username="MrMeoward",
|
|
first_name="Meoward",
|
|
last_name="Jones",
|
|
)
|
|
|
|
# Create a fake domain request
|
|
_domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator)
|
|
|
|
# Creates a Domain and DomainInformation object
|
|
_domain_request.approve()
|
|
|
|
domain_information = DomainInformation.objects.filter(domain_request=_domain_request).get()
|
|
domain_information.organization_name = "MonkeySeeMonkeyDo"
|
|
domain_information.save()
|
|
|
|
# We use filter here rather than just domain_information.domain just to get the latest data.
|
|
domain = Domain.objects.filter(domain_info=domain_information).get()
|
|
|
|
response = self.client.get(
|
|
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
|
follow=True,
|
|
)
|
|
|
|
# Make sure the page loaded, and that we're on the right page
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, domain.name)
|
|
|
|
# Test for data. We only need to test one since its all interconnected.
|
|
expected_organization_name = "MonkeySeeMonkeyDo"
|
|
self.assertContains(response, expected_organization_name)
|
|
|
|
@less_console_noise_decorator
|
|
def test_deletion_is_successful(self):
|
|
"""
|
|
Scenario: Domain deletion is successful
|
|
When the domain is deleted
|
|
Then a user-friendly success message is returned for displaying on the web
|
|
And `state` is set to `DELETED`
|
|
"""
|
|
domain, _ = Domain.objects.get_or_create(name="my-nameserver.gov", state=Domain.State.READY)
|
|
# Put in client hold
|
|
domain.place_client_hold()
|
|
# Ensure everything is displaying correctly
|
|
response = self.client.get(
|
|
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
|
follow=True,
|
|
)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, domain.name)
|
|
self.assertContains(response, "Remove from registry")
|
|
|
|
# The contents of the modal should exist before and after the post.
|
|
# Check for the header
|
|
self.assertContains(response, "Are you sure you want to remove this domain from the registry?")
|
|
|
|
# Check for some of its body
|
|
self.assertContains(response, "When a domain is removed from the registry:")
|
|
|
|
# Check for some of the button content
|
|
self.assertContains(response, "Yes, remove from registry")
|
|
|
|
# Test the info dialog
|
|
request = self.factory.post(
|
|
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
|
{"_delete_domain": "Remove from registry", "name": domain.name},
|
|
follow=True,
|
|
)
|
|
request.user = self.client
|
|
with patch("django.contrib.messages.add_message") as mock_add_message:
|
|
self.admin.do_delete_domain(request, domain)
|
|
mock_add_message.assert_called_once_with(
|
|
request,
|
|
messages.INFO,
|
|
"Domain my-nameserver.gov has been deleted. Thanks!",
|
|
extra_tags="",
|
|
fail_silently=False,
|
|
)
|
|
|
|
# The modal should still exist
|
|
self.assertContains(response, "Are you sure you want to remove this domain from the registry?")
|
|
self.assertContains(response, "When a domain is removed from the registry:")
|
|
self.assertContains(response, "Yes, remove from registry")
|
|
|
|
self.assertEqual(domain.state, Domain.State.DELETED)
|
|
|
|
@less_console_noise_decorator
|
|
def test_deletion_is_unsuccessful(self):
|
|
"""
|
|
Scenario: Domain deletion is unsuccessful
|
|
When the domain is deleted and has shared subdomains
|
|
Then a user-friendly success message is returned for displaying on the web
|
|
And `state` is not set to `DELETED`
|
|
"""
|
|
domain, _ = Domain.objects.get_or_create(name="sharingiscaring.gov", state=Domain.State.ON_HOLD)
|
|
# Put in client hold
|
|
domain.place_client_hold()
|
|
# Ensure everything is displaying correctly
|
|
response = self.client.get(
|
|
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
|
follow=True,
|
|
)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, domain.name)
|
|
self.assertContains(response, "Remove from registry")
|
|
|
|
# The contents of the modal should exist before and after the post.
|
|
# Check for the header
|
|
self.assertContains(response, "Are you sure you want to remove this domain from the registry?")
|
|
|
|
# Check for some of its body
|
|
self.assertContains(response, "When a domain is removed from the registry:")
|
|
|
|
# Check for some of the button content
|
|
self.assertContains(response, "Yes, remove from registry")
|
|
|
|
# Test the info dialog
|
|
request = self.factory.post(
|
|
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
|
{"_delete_domain": "Remove from registry", "name": domain.name},
|
|
follow=True,
|
|
)
|
|
request.user = self.client
|
|
with patch("django.contrib.messages.add_message") as mock_add_message:
|
|
self.admin.do_delete_domain(request, domain)
|
|
mock_add_message.assert_called_once_with(
|
|
request,
|
|
messages.ERROR,
|
|
"Error deleting this Domain: Command failed with note: Domain has associated objects that prevent deletion.", # noqa
|
|
extra_tags="",
|
|
fail_silently=False,
|
|
)
|
|
|
|
self.assertEqual(domain.state, Domain.State.ON_HOLD)
|
|
|
|
@less_console_noise_decorator
|
|
def test_deletion_ready_fsm_failure(self):
|
|
"""
|
|
Scenario: Domain deletion is unsuccessful
|
|
When an error is returned from epplibwrapper
|
|
Then a user-friendly error message is returned for displaying on the web
|
|
And `state` is not set to `DELETED`
|
|
"""
|
|
|
|
domain = create_ready_domain()
|
|
# Ensure everything is displaying correctly
|
|
response = self.client.get(
|
|
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
|
follow=True,
|
|
)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, domain.name)
|
|
self.assertContains(response, "Remove from registry")
|
|
# Test the error
|
|
request = self.factory.post(
|
|
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
|
{"_delete_domain": "Remove from registry", "name": domain.name},
|
|
follow=True,
|
|
)
|
|
request.user = self.client
|
|
with patch("django.contrib.messages.add_message") as mock_add_message:
|
|
self.admin.do_delete_domain(request, domain)
|
|
mock_add_message.assert_called_once_with(
|
|
request,
|
|
messages.ERROR,
|
|
"Error deleting this Domain: "
|
|
"Can't switch from state 'ready' to 'deleted'"
|
|
", must be either 'dns_needed' or 'on_hold'",
|
|
extra_tags="",
|
|
fail_silently=False,
|
|
)
|
|
|
|
self.assertEqual(domain.state, Domain.State.READY)
|
|
|
|
@less_console_noise_decorator
|
|
def test_analyst_deletes_domain_idempotent(self):
|
|
"""
|
|
Scenario: Analyst tries to delete an already deleted domain
|
|
Given `state` is already `DELETED`
|
|
When `domain.deletedInEpp()` is called
|
|
Then `commands.DeleteDomain` is sent to the registry
|
|
And Domain returns normally without an error dialog
|
|
"""
|
|
domain, _ = Domain.objects.get_or_create(name="my-nameserver.gov", state=Domain.State.READY)
|
|
# Put in client hold
|
|
domain.place_client_hold()
|
|
# Ensure everything is displaying correctly
|
|
response = self.client.get(
|
|
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
|
follow=True,
|
|
)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, domain.name)
|
|
self.assertContains(response, "Remove from registry")
|
|
# Test the info dialog
|
|
request = self.factory.post(
|
|
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
|
{"_delete_domain": "Remove from registry", "name": domain.name},
|
|
follow=True,
|
|
)
|
|
request.user = self.client
|
|
# Delete it once
|
|
|
|
with patch("django.contrib.messages.add_message") as mock_add_message:
|
|
self.admin.do_delete_domain(request, domain)
|
|
mock_add_message.assert_called_once_with(
|
|
request,
|
|
messages.INFO,
|
|
"Domain my-nameserver.gov has been deleted. Thanks!",
|
|
extra_tags="",
|
|
fail_silently=False,
|
|
)
|
|
|
|
self.assertEqual(domain.state, Domain.State.DELETED)
|
|
# Try to delete it again
|
|
# Test the info dialog
|
|
request = self.factory.post(
|
|
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
|
{"_delete_domain": "Remove from registry", "name": domain.name},
|
|
follow=True,
|
|
)
|
|
request.user = self.client
|
|
with patch("django.contrib.messages.add_message") as mock_add_message:
|
|
self.admin.do_delete_domain(request, domain)
|
|
mock_add_message.assert_called_once_with(
|
|
request,
|
|
messages.INFO,
|
|
"This domain is already deleted",
|
|
extra_tags="",
|
|
fail_silently=False,
|
|
)
|
|
self.assertEqual(domain.state, Domain.State.DELETED)
|
|
|
|
|
|
class TestDomainInformationInline(MockEppLib):
|
|
"""Test DomainAdmin class, specifically the DomainInformationInline class, as staff user.
|
|
|
|
Notes:
|
|
all tests share staffuser; do not change staffuser model in tests
|
|
tests have available staffuser, client, and admin
|
|
"""
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.staffuser = create_user()
|
|
cls.site = AdminSite()
|
|
cls.admin = DomainAdmin(model=Domain, admin_site=cls.site)
|
|
cls.factory = RequestFactory()
|
|
|
|
def setUp(self):
|
|
self.client = Client(HTTP_HOST="localhost:8080")
|
|
self.client.force_login(self.staffuser)
|
|
super().setUp()
|
|
|
|
def tearDown(self):
|
|
super().tearDown()
|
|
Host.objects.all().delete()
|
|
UserDomainRole.objects.all().delete()
|
|
Domain.objects.all().delete()
|
|
DomainInformation.objects.all().delete()
|
|
DomainRequest.objects.all().delete()
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
User.objects.all().delete()
|
|
super().tearDownClass()
|
|
|
|
@less_console_noise_decorator
|
|
def test_domain_managers_display(self):
|
|
"""Tests the custom domain managers field"""
|
|
admin_user_1 = User.objects.create(
|
|
username="testuser1",
|
|
first_name="Gerald",
|
|
last_name="Meoward",
|
|
email="meoward@gov.gov",
|
|
)
|
|
|
|
domain_request = completed_domain_request(
|
|
status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=self.staffuser, name="fake.gov"
|
|
)
|
|
domain_request.approve()
|
|
_domain_info = DomainInformation.objects.filter(domain=domain_request.approved_domain).get()
|
|
domain = Domain.objects.filter(domain_info=_domain_info).get()
|
|
|
|
UserDomainRole.objects.get_or_create(user=admin_user_1, domain=domain, role=UserDomainRole.Roles.MANAGER)
|
|
|
|
admin_user_2 = User.objects.create(
|
|
username="testuser2",
|
|
first_name="Arnold",
|
|
last_name="Poopy",
|
|
email="poopy@gov.gov",
|
|
)
|
|
|
|
UserDomainRole.objects.get_or_create(user=admin_user_2, domain=domain, role=UserDomainRole.Roles.MANAGER)
|
|
|
|
# Get the first inline (DomainInformationInline)
|
|
inline_instance = self.admin.inlines[0](self.admin.model, self.admin.admin_site)
|
|
|
|
# Call the domain_managers method
|
|
domain_managers = inline_instance.domain_managers(domain.domain_info)
|
|
|
|
self.assertIn(
|
|
f'<a href="/admin/registrar/user/{admin_user_1.pk}/change/">testuser1</a>',
|
|
domain_managers,
|
|
)
|
|
self.assertIn("Gerald Meoward", domain_managers)
|
|
self.assertIn("meoward@gov.gov", domain_managers)
|
|
self.assertIn(f'<a href="/admin/registrar/user/{admin_user_2.pk}/change/">testuser2</a>', domain_managers)
|
|
self.assertIn("Arnold Poopy", domain_managers)
|
|
self.assertIn("poopy@gov.gov", domain_managers)
|
|
|
|
@less_console_noise_decorator
|
|
def test_invited_domain_managers_display(self):
|
|
"""Tests the custom invited domain managers field"""
|
|
admin_user_1 = User.objects.create(
|
|
username="testuser1",
|
|
first_name="Gerald",
|
|
last_name="Meoward",
|
|
email="meoward@gov.gov",
|
|
)
|
|
|
|
domain_request = completed_domain_request(
|
|
status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=self.staffuser, name="fake.gov"
|
|
)
|
|
domain_request.approve()
|
|
_domain_info = DomainInformation.objects.filter(domain=domain_request.approved_domain).get()
|
|
domain = Domain.objects.filter(domain_info=_domain_info).get()
|
|
|
|
# domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
|
|
UserDomainRole.objects.get_or_create(user=admin_user_1, domain=domain, role=UserDomainRole.Roles.MANAGER)
|
|
|
|
admin_user_2 = User.objects.create(
|
|
username="testuser2",
|
|
first_name="Arnold",
|
|
last_name="Poopy",
|
|
email="poopy@gov.gov",
|
|
)
|
|
|
|
UserDomainRole.objects.get_or_create(user=admin_user_2, domain=domain, role=UserDomainRole.Roles.MANAGER)
|
|
|
|
# Get the first inline (DomainInformationInline)
|
|
inline_instance = self.admin.inlines[0](self.admin.model, self.admin.admin_site)
|
|
|
|
# Call the domain_managers method
|
|
domain_managers = inline_instance.domain_managers(domain.domain_info)
|
|
# domain_managers = self.admin.get_inlinesdomain_managers(self.domain)
|
|
|
|
self.assertIn(
|
|
f'<a href="/admin/registrar/user/{admin_user_1.pk}/change/">testuser1</a>',
|
|
domain_managers,
|
|
)
|
|
self.assertIn("Gerald Meoward", domain_managers)
|
|
self.assertIn("meoward@gov.gov", domain_managers)
|
|
self.assertIn(f'<a href="/admin/registrar/user/{admin_user_2.pk}/change/">testuser2</a>', domain_managers)
|
|
self.assertIn("Arnold Poopy", domain_managers)
|
|
self.assertIn("poopy@gov.gov", domain_managers)
|
|
|
|
|
|
class TestDomainInvitationAdmin(TestCase):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.staffuser = create_user(email="staffdomainmanager@meoward.com", is_staff=True)
|
|
cls.site = AdminSite()
|
|
cls.admin = DomainAdmin(model=Domain, admin_site=cls.site)
|
|
cls.factory = RequestFactory()
|
|
|
|
def setUp(self):
|
|
self.client = Client(HTTP_HOST="localhost:8080")
|
|
self.client.force_login(self.staffuser)
|
|
super().setUp()
|
|
|
|
def test_successful_cancel_invitation_flow_in_admin(self):
|
|
"""Testing canceling a domain invitation in Django Admin."""
|
|
|
|
# 1. Create a domain and assign staff user role + domain manager
|
|
domain = Domain.objects.create(name="cancelinvitationflowviaadmin.gov")
|
|
UserDomainRole.objects.create(user=self.staffuser, domain=domain, role="manager")
|
|
|
|
# 2. Invite a domain manager to the above domain
|
|
invitation = DomainInvitation.objects.create(
|
|
email="inviteddomainmanager@meoward.com",
|
|
domain=domain,
|
|
status=DomainInvitation.DomainInvitationStatus.INVITED,
|
|
)
|
|
|
|
# 3. Go to the Domain Invitations list in /admin
|
|
domain_invitation_list_url = reverse("admin:registrar_domaininvitation_changelist")
|
|
response = self.client.get(domain_invitation_list_url)
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
# 4. Go to the change view of that invitation and make sure you can see the button
|
|
domain_invitation_change_url = reverse("admin:registrar_domaininvitation_change", args=[invitation.id])
|
|
response = self.client.get(domain_invitation_change_url)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, "Cancel invitation")
|
|
|
|
# 5. Click the cancel invitation button
|
|
response = self.client.post(domain_invitation_change_url, {"cancel_invitation": "true"}, follow=True)
|
|
|
|
# 6. Make sure we're redirect back to the change view page in /admin
|
|
self.assertRedirects(response, domain_invitation_change_url)
|
|
|
|
# 7. Confirm cancellation confirmation message appears
|
|
expected_message = f"Invitation for {invitation.email} on {domain.name} is canceled"
|
|
self.assertContains(response, expected_message)
|
|
|
|
def test_no_cancel_invitation_button_in_retrieved_state(self):
|
|
"""Shouldn't be able to see the "Cancel invitation" button if invitation is RETRIEVED state"""
|
|
|
|
# 1. Create a domain and assign staff user role + domain manager
|
|
domain = Domain.objects.create(name="retrieved.gov")
|
|
UserDomainRole.objects.create(user=self.staffuser, domain=domain, role="manager")
|
|
|
|
# 2. Invite a domain manager to the above domain and NOT in invited state
|
|
invitation = DomainInvitation.objects.create(
|
|
email="retrievedinvitation@meoward.com",
|
|
domain=domain,
|
|
status=DomainInvitation.DomainInvitationStatus.RETRIEVED,
|
|
)
|
|
|
|
# 3. Go to the Domain Invitations list in /admin
|
|
domain_invitation_list_url = reverse("admin:registrar_domaininvitation_changelist")
|
|
response = self.client.get(domain_invitation_list_url)
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
# 4. Go to the change view of that invitation and make sure you CANNOT see the button
|
|
domain_invitation_change_url = reverse("admin:registrar_domaininvitation_change", args=[invitation.id])
|
|
response = self.client.get(domain_invitation_change_url)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertNotContains(response, "Cancel invitation")
|
|
|
|
def test_no_cancel_invitation_button_in_canceled_state(self):
|
|
"""Shouldn't be able to see the "Cancel invitation" button if invitation is CANCELED state"""
|
|
|
|
# 1. Create a domain and assign staff user role + domain manager
|
|
domain = Domain.objects.create(name="canceled.gov")
|
|
UserDomainRole.objects.create(user=self.staffuser, domain=domain, role="manager")
|
|
|
|
# 2. Invite a domain manager to the above domain and NOT in invited state
|
|
invitation = DomainInvitation.objects.create(
|
|
email="canceledinvitation@meoward.com",
|
|
domain=domain,
|
|
status=DomainInvitation.DomainInvitationStatus.CANCELED,
|
|
)
|
|
|
|
# 3. Go to the Domain Invitations list in /admin
|
|
domain_invitation_list_url = reverse("admin:registrar_domaininvitation_changelist")
|
|
response = self.client.get(domain_invitation_list_url)
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
# 4. Go to the change view of that invitation and make sure you CANNOT see the button
|
|
domain_invitation_change_url = reverse("admin:registrar_domaininvitation_change", args=[invitation.id])
|
|
response = self.client.get(domain_invitation_change_url)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertNotContains(response, "Cancel invitation")
|
|
|
|
|
|
class TestDomainAdminWithClient(TestCase):
|
|
"""Test DomainAdmin class as super user.
|
|
|
|
Notes:
|
|
all tests share superuser; tests must not update superuser
|
|
tests have available superuser, client, and admin
|
|
"""
|
|
|
|
@classmethod
|
|
def setUpClass(self):
|
|
super().setUpClass()
|
|
self.site = AdminSite()
|
|
self.admin = DomainAdmin(model=Domain, admin_site=self.site)
|
|
self.factory = RequestFactory()
|
|
self.superuser = create_superuser()
|
|
|
|
def setUp(self):
|
|
self.client = Client(HTTP_HOST="localhost:8080")
|
|
self.client.force_login(self.superuser)
|
|
super().setUp()
|
|
|
|
def tearDown(self):
|
|
super().tearDown()
|
|
Host.objects.all().delete()
|
|
UserDomainRole.objects.all().delete()
|
|
Domain.objects.all().delete()
|
|
DomainInformation.objects.all().delete()
|
|
DomainRequest.objects.all().delete()
|
|
Portfolio.objects.all().delete()
|
|
|
|
@classmethod
|
|
def tearDownClass(self):
|
|
User.objects.all().delete()
|
|
super().tearDownClass()
|
|
|
|
@less_console_noise_decorator
|
|
def test_has_model_description(self):
|
|
"""Tests if this model has a model description on the table view"""
|
|
response = self.client.get(
|
|
"/admin/registrar/domain/",
|
|
follow=True,
|
|
)
|
|
|
|
# Make sure that the page is loaded correctly
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
# Test for a description snippet
|
|
self.assertContains(response, "This table contains all approved domains in the .gov registrar.")
|
|
self.assertContains(response, "Show more")
|
|
|
|
@less_console_noise_decorator
|
|
def test_contact_fields_on_domain_change_form_have_detail_table(self):
|
|
"""Tests if the contact fields in the inlined Domain information have the detail table
|
|
which displays title, email, and phone"""
|
|
|
|
# Create fake creator
|
|
_creator = User.objects.create(
|
|
username="MrMeoward",
|
|
first_name="Meoward",
|
|
last_name="Jones",
|
|
email="meoward.jones@igorville.gov",
|
|
phone="(555) 123 12345",
|
|
title="Treat inspector",
|
|
)
|
|
|
|
# Create a fake domain request
|
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator)
|
|
domain_request.approve()
|
|
_domain_info = DomainInformation.objects.filter(domain=domain_request.approved_domain).get()
|
|
domain = Domain.objects.filter(domain_info=_domain_info).get()
|
|
|
|
response = self.client.get(
|
|
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
|
follow=True,
|
|
)
|
|
|
|
# Make sure the page loaded, and that we're on the right page
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, domain.name)
|
|
|
|
# Check that the fields have the right values.
|
|
|
|
# == Check for the senior_official == #
|
|
self.assertContains(response, "testy@town.com")
|
|
self.assertContains(response, "Chief Tester")
|
|
self.assertContains(response, "(555) 555 5555")
|
|
|
|
# Includes things like readonly fields
|
|
self.assertContains(response, "Testy Tester")
|
|
|
|
# Test for the copy link
|
|
self.assertContains(response, "copy-to-clipboard")
|
|
|
|
# cleanup from this test
|
|
domain.delete()
|
|
_domain_info.delete()
|
|
domain_request.delete()
|
|
_creator.delete()
|
|
|
|
@less_console_noise_decorator
|
|
def test_domains_by_portfolio(self):
|
|
"""
|
|
Tests that domains display for a portfolio. And that domains outside the portfolio do not display.
|
|
"""
|
|
|
|
portfolio, _ = Portfolio.objects.get_or_create(organization_name="Test Portfolio", creator=self.superuser)
|
|
# Create a fake domain request and domain
|
|
_domain_request = completed_domain_request(
|
|
status=DomainRequest.DomainRequestStatus.IN_REVIEW, portfolio=portfolio
|
|
)
|
|
_domain_request.approve()
|
|
|
|
domain = _domain_request.approved_domain
|
|
domain2, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
|
|
UserDomainRole.objects.get_or_create()
|
|
UserDomainRole.objects.get_or_create(user=self.superuser, domain=domain2, role=UserDomainRole.Roles.MANAGER)
|
|
|
|
self.client.force_login(self.superuser)
|
|
response = self.client.get(
|
|
"/admin/registrar/domain/?portfolio={}".format(portfolio.pk),
|
|
follow=True,
|
|
)
|
|
|
|
# Make sure the page loaded, and that we're on the right page
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, domain.name)
|
|
self.assertNotContains(response, domain2.name)
|
|
self.assertContains(response, portfolio.organization_name)
|
|
|
|
@less_console_noise_decorator
|
|
def test_helper_text(self):
|
|
"""
|
|
Tests for the correct helper text on this page
|
|
"""
|
|
|
|
# Create a ready domain with a preset expiration date
|
|
domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
|
|
|
|
response = self.client.get(
|
|
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
|
follow=True,
|
|
)
|
|
|
|
# Make sure the page loaded, and that we're on the right page
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, domain.name)
|
|
|
|
# Contains some test tools
|
|
test_helper = GenericTestHelper(
|
|
factory=self.factory,
|
|
user=self.superuser,
|
|
admin=self.admin,
|
|
url=reverse("admin:registrar_domain_changelist"),
|
|
model=Domain,
|
|
client=self.client,
|
|
)
|
|
# These should exist in the response
|
|
expected_values = [
|
|
("expiration_date", "Date the domain expires in the registry"),
|
|
("first_ready_at", 'Date when this domain first moved into "ready" state; date will never change'),
|
|
("deleted_at", 'Will appear blank unless the domain is in "deleted" state'),
|
|
]
|
|
test_helper.assert_response_contains_distinct_values(response, expected_values)
|
|
|
|
@less_console_noise_decorator
|
|
def test_helper_text_state(self):
|
|
"""
|
|
Tests for the correct state helper text on this page
|
|
"""
|
|
|
|
# Add domain data
|
|
ready_domain, _ = Domain.objects.get_or_create(name="fakeready.gov", state=Domain.State.READY)
|
|
unknown_domain, _ = Domain.objects.get_or_create(name="fakeunknown.gov", state=Domain.State.UNKNOWN)
|
|
dns_domain, _ = Domain.objects.get_or_create(name="fakedns.gov", state=Domain.State.DNS_NEEDED)
|
|
hold_domain, _ = Domain.objects.get_or_create(name="fakehold.gov", state=Domain.State.ON_HOLD)
|
|
deleted_domain, _ = Domain.objects.get_or_create(name="fakedeleted.gov", state=Domain.State.DELETED)
|
|
|
|
# We don't need to check for all text content, just a portion of it
|
|
expected_unknown_domain_message = "The creator of the associated domain request has not logged in to"
|
|
expected_dns_message = "Before this domain can be used, name server addresses need"
|
|
expected_hold_message = "While on hold, this domain"
|
|
expected_deleted_message = "This domain was permanently removed from the registry."
|
|
expected_messages = [
|
|
(ready_domain, "This domain has name servers and is ready for use."),
|
|
(unknown_domain, expected_unknown_domain_message),
|
|
(dns_domain, expected_dns_message),
|
|
(hold_domain, expected_hold_message),
|
|
(deleted_domain, expected_deleted_message),
|
|
]
|
|
|
|
for domain, message in expected_messages:
|
|
with self.subTest(domain_state=domain.state):
|
|
response = self.client.get(
|
|
"/admin/registrar/domain/{}/change/".format(domain.id),
|
|
)
|
|
|
|
# Make sure the page loaded, and that we're on the right page
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, domain.name)
|
|
|
|
# Check that the right help text exists
|
|
self.assertContains(response, message)
|
|
|
|
@less_console_noise_decorator
|
|
def test_admin_can_see_inline_domain_information_in_domain_change_form(self):
|
|
"""Tests if an admin can still see the inline domain information form"""
|
|
# Create fake creator
|
|
_creator = User.objects.create(
|
|
username="MrMeoward",
|
|
first_name="Meoward",
|
|
last_name="Jones",
|
|
)
|
|
|
|
# Create a fake domain request
|
|
_domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator)
|
|
|
|
# Creates a Domain and DomainInformation object
|
|
_domain_request.approve()
|
|
|
|
domain_information = DomainInformation.objects.filter(domain_request=_domain_request).get()
|
|
domain_information.organization_name = "MonkeySeeMonkeyDo"
|
|
domain_information.save()
|
|
|
|
# We use filter here rather than just domain_information.domain just to get the latest data.
|
|
domain = Domain.objects.filter(domain_info=domain_information).get()
|
|
|
|
response = self.client.get(
|
|
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
|
follow=True,
|
|
)
|
|
|
|
# Make sure the page loaded, and that we're on the right page
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, domain.name)
|
|
|
|
# Test for data. We only need to test one since its all interconnected.
|
|
expected_organization_name = "MonkeySeeMonkeyDo"
|
|
self.assertContains(response, expected_organization_name)
|
|
|
|
# cleanup from this test
|
|
domain.delete()
|
|
domain_information.delete()
|
|
_domain_request.delete()
|
|
_creator.delete()
|
|
|
|
@less_console_noise_decorator
|
|
def test_custom_delete_confirmation_page_table(self):
|
|
"""Tests if we override the delete confirmation page for custom content on the table"""
|
|
# Create a ready domain
|
|
domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
|
|
|
|
# Get the index. The post expects the index to be encoded as a string
|
|
index = f"{domain.id}"
|
|
|
|
# Contains some test tools
|
|
test_helper = GenericTestHelper(
|
|
factory=self.factory,
|
|
user=self.superuser,
|
|
admin=self.admin,
|
|
url=reverse("admin:registrar_domain_changelist"),
|
|
model=Domain,
|
|
client=self.client,
|
|
)
|
|
|
|
# Simulate selecting a single record, then clicking "Delete selected domains"
|
|
response = test_helper.get_table_delete_confirmation_page("0", index)
|
|
|
|
# Check that our content exists
|
|
content_slice = "When a domain is deleted:"
|
|
self.assertContains(response, content_slice)
|
|
|
|
@less_console_noise_decorator
|
|
def test_short_org_name_in_domains_list(self):
|
|
"""
|
|
Make sure the short name is displaying in admin on the list page
|
|
"""
|
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
|
|
mock_client = MockSESClient()
|
|
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
|
domain_request.approve()
|
|
|
|
response = self.client.get("/admin/registrar/domain/")
|
|
# There are 4 template references to Federal (4) plus four references in the table
|
|
# for our actual domain_request
|
|
self.assertContains(response, "Federal", count=57)
|
|
# This may be a bit more robust
|
|
self.assertContains(response, '<td class="field-converted_generic_org_type">Federal</td>', count=1)
|
|
# Now let's make sure the long description does not exist
|
|
self.assertNotContains(response, "Federal: an agency of the U.S. government")
|
|
|
|
@override_settings(IS_PRODUCTION=True)
|
|
@less_console_noise_decorator
|
|
def test_prod_only_shows_export(self):
|
|
"""Test that production environment only displays export"""
|
|
response = self.client.get("/admin/registrar/domain/")
|
|
self.assertContains(response, ">Export<")
|
|
self.assertNotContains(response, ">Import<")
|
|
|
|
|
|
class TestDomainAdminWebTest(MockEppLib, WebTest):
|
|
"""Test DomainAdmin class as super user, using WebTest.
|
|
WebTest allows for easier handling of forms and html responses.
|
|
|
|
Notes:
|
|
all tests share superuser; tests must not update superuser
|
|
tests have available superuser, app, and admin
|
|
"""
|
|
|
|
# csrf checks do not work with WebTest.
|
|
# We disable them here. TODO for another ticket.
|
|
csrf_checks = False
|
|
|
|
@classmethod
|
|
def setUpClass(self):
|
|
super().setUpClass()
|
|
self.site = AdminSite()
|
|
self.admin = DomainAdmin(model=Domain, admin_site=self.site)
|
|
self.superuser = create_superuser()
|
|
self.factory = RequestFactory()
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.app.set_user(self.superuser.username)
|
|
|
|
def tearDown(self):
|
|
super().tearDown()
|
|
Host.objects.all().delete()
|
|
Domain.objects.all().delete()
|
|
DomainInformation.objects.all().delete()
|
|
DomainRequest.objects.all().delete()
|
|
|
|
@classmethod
|
|
def tearDownClass(self):
|
|
User.objects.all().delete()
|
|
super().tearDownClass()
|
|
|
|
@less_console_noise_decorator
|
|
@patch("registrar.admin.DomainAdmin._get_current_date", return_value=date(2024, 1, 1))
|
|
def test_extend_expiration_date_button(self, mock_date_today):
|
|
"""
|
|
Tests if extend_expiration_date modal gives an accurate date
|
|
"""
|
|
|
|
# Create a ready domain with a preset expiration date
|
|
domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
|
|
response = self.app.get(reverse("admin:registrar_domain_change", args=[domain.pk]))
|
|
# load expiration date into cache and registrar with below command
|
|
domain.registry_expiration_date
|
|
# Make sure the ex date is what we expect it to be
|
|
domain_ex_date = Domain.objects.get(id=domain.id).expiration_date
|
|
self.assertEqual(domain_ex_date, date(2023, 5, 25))
|
|
|
|
# Make sure that the page is loading as expected
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, domain.name)
|
|
self.assertContains(response, "Extend expiration date")
|
|
|
|
# Grab the form to submit
|
|
form = response.forms["domain_form"]
|
|
|
|
with patch("django.contrib.messages.add_message") as mock_add_message:
|
|
# Submit the form
|
|
response = form.submit("_extend_expiration_date")
|
|
|
|
# Follow the response
|
|
response = response.follow()
|
|
|
|
# Assert that everything on the page looks correct
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, domain.name)
|
|
self.assertContains(response, "Extend expiration date")
|
|
|
|
# Ensure the message we recieve is in line with what we expect
|
|
expected_message = "Successfully extended the expiration date."
|
|
expected_call = call(
|
|
# The WGSI request doesn't need to be tested
|
|
ANY,
|
|
messages.INFO,
|
|
expected_message,
|
|
extra_tags="",
|
|
fail_silently=False,
|
|
)
|
|
|
|
mock_add_message.assert_has_calls([expected_call], 1)
|
|
|
|
@less_console_noise_decorator
|
|
@patch("registrar.admin.DomainAdmin._get_current_date", return_value=date(2024, 1, 1))
|
|
def test_extend_expiration_date_button_epp(self, mock_date_today):
|
|
"""
|
|
Tests if extend_expiration_date button sends the right epp command
|
|
"""
|
|
|
|
# Create a ready domain with a preset expiration date
|
|
domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
|
|
|
|
response = self.app.get(reverse("admin:registrar_domain_change", args=[domain.pk]))
|
|
|
|
# Make sure that the page is loading as expected
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, domain.name)
|
|
self.assertContains(response, "Extend expiration date")
|
|
|
|
# Grab the form to submit
|
|
form = response.forms["domain_form"]
|
|
|
|
with patch("django.contrib.messages.add_message") as mock_add_message:
|
|
with patch("registrar.models.Domain.renew_domain") as renew_mock:
|
|
# Submit the form
|
|
response = form.submit("_extend_expiration_date")
|
|
|
|
# Follow the response
|
|
response = response.follow()
|
|
|
|
# Assert that it is calling the function with the default extension length.
|
|
# We only need to test the value that EPP sends, as we can assume the other
|
|
# test cases cover the "renew" function.
|
|
renew_mock.assert_has_calls([call()], any_order=False)
|
|
|
|
# We should not make duplicate calls
|
|
self.assertEqual(renew_mock.call_count, 1)
|
|
|
|
# Assert that everything on the page looks correct
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, domain.name)
|
|
self.assertContains(response, "Extend expiration date")
|
|
|
|
# Ensure the message we recieve is in line with what we expect
|
|
expected_message = "Successfully extended the expiration date."
|
|
expected_call = call(
|
|
# The WGSI request doesn't need to be tested
|
|
ANY,
|
|
messages.INFO,
|
|
expected_message,
|
|
extra_tags="",
|
|
fail_silently=False,
|
|
)
|
|
mock_add_message.assert_has_calls([expected_call], 1)
|
|
|
|
@less_console_noise_decorator
|
|
def test_custom_delete_confirmation_page(self):
|
|
"""Tests if we override the delete confirmation page for custom content"""
|
|
# Create a ready domain with a preset expiration date
|
|
domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
|
|
|
|
domain_change_page = self.app.get(reverse("admin:registrar_domain_change", args=[domain.pk]))
|
|
|
|
self.assertContains(domain_change_page, "fake.gov")
|
|
# click the "Delete" link
|
|
confirmation_page = domain_change_page.click("Delete", index=0)
|
|
|
|
content_slice = "When a domain is deleted:"
|
|
self.assertContains(confirmation_page, content_slice)
|
|
|
|
@less_console_noise_decorator
|
|
def test_on_hold_is_successful_web_test(self):
|
|
"""
|
|
Scenario: Domain on_hold is successful through webtest
|
|
"""
|
|
with less_console_noise():
|
|
domain = create_ready_domain()
|
|
|
|
response = self.app.get(reverse("admin:registrar_domain_change", args=[domain.pk]))
|
|
|
|
# Check the contents of the modal
|
|
# Check for the header
|
|
self.assertContains(response, "Are you sure you want to place this domain on hold?")
|
|
|
|
# Check for some of its body
|
|
self.assertContains(response, "When a domain is on hold:")
|
|
|
|
# Check for some of the button content
|
|
self.assertContains(response, "Yes, place hold")
|
|
|
|
# Grab the form to submit
|
|
form = response.forms["domain_form"]
|
|
|
|
# Submit the form
|
|
response = form.submit("_place_client_hold")
|
|
|
|
# Follow the response
|
|
response = response.follow()
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, domain.name)
|
|
self.assertContains(response, "Remove hold")
|
|
|
|
# The modal should still exist
|
|
# Check for the header
|
|
self.assertContains(response, "Are you sure you want to place this domain on hold?")
|
|
|
|
# Check for some of its body
|
|
self.assertContains(response, "When a domain is on hold:")
|
|
|
|
# Check for some of the button content
|
|
self.assertContains(response, "Yes, place hold")
|
|
|
|
# Web test has issues grabbing up to date data from the db, so we can test
|
|
# the returned view instead
|
|
self.assertContains(response, '<div class="readonly">On hold</div>')
|