Sketch test cases for Domain integration

This also includes an illustration of how action buttons
could be integrated into the admin portal.
This commit is contained in:
Seamus Johnston 2023-06-13 14:50:40 -05:00
parent 78a9c50561
commit cabe516f9f
No known key found for this signature in database
GPG key ID: 2F21225985069105
5 changed files with 507 additions and 92 deletions

View file

@ -1,4 +1,4 @@
from django.contrib import admin
from django.contrib import admin, messages
from django.contrib.auth.admin import UserAdmin
from django.contrib.contenttypes.models import ContentType
from django.http.response import HttpResponseRedirect
@ -50,13 +50,40 @@ class MyHostAdmin(AuditedAdmin):
inlines = [HostIPInline]
class DomainAdmin(AuditedAdmin):
"""Custom domain admin class to add extra buttons."""
change_form_template = "django/admin/domain_change_form.html"
readonly_fields = ["state"]
def response_change(self, request, obj):
if "_place_client_hold" in request.POST:
try:
obj.place_client_hold()
except Exception as err:
self.message_user(request, err, messages.ERROR)
else:
self.message_user(
request,
(
"%s is in client hold. This domain is no longer accessible on"
" the public internet."
)
% obj.name,
)
return HttpResponseRedirect(".")
return super().response_change(request, obj)
admin.site.register(models.User, MyUserAdmin)
admin.site.register(models.UserDomainRole, AuditedAdmin)
admin.site.register(models.Contact, AuditedAdmin)
admin.site.register(models.DomainInvitation, AuditedAdmin)
admin.site.register(models.DomainApplication, AuditedAdmin)
admin.site.register(models.DomainInformation, AuditedAdmin)
admin.site.register(models.Domain, AuditedAdmin)
admin.site.register(models.Domain, DomainAdmin)
admin.site.register(models.Host, MyHostAdmin)
admin.site.register(models.Nameserver, MyHostAdmin)
admin.site.register(models.Website, AuditedAdmin)

View file

@ -267,7 +267,7 @@ class Domain(TimeStampedModel, DomainHelper):
def place_client_hold(self):
"""This domain should not be active."""
raise NotImplementedError()
raise NotImplementedError("This is not implemented yet.")
def remove_client_hold(self):
"""This domain is okay to be active."""

View file

@ -0,0 +1,8 @@
{% extends 'admin/change_form.html' %}
{% block field_sets %}
<div class="submit-row">
<input type="submit" value="Place hold" name="_place_client_hold">
</div>
{{ block.super }}
{% endblock %}

View file

@ -12,7 +12,6 @@ from registrar.models import (
DomainInvitation,
UserDomainRole,
)
from unittest import skip
import boto3_mocking # type: ignore
from .common import MockSESClient, less_console_noise
@ -213,55 +212,3 @@ class TestInvitations(TestCase):
"""A new user's first_login callback retrieves their invitations."""
self.user.first_login()
self.assertTrue(UserDomainRole.objects.get(user=self.user, domain=self.domain))
@skip("Not implemented yet.")
class TestDomainApplicationLifeCycle(TestCase):
def test_application_approval(self):
# DomainApplication is created
# test: Domain is created and is inactive
# analyst approves DomainApplication
# test: Domain is activated
pass
def test_application_rejection(self):
# DomainApplication is created
# test: Domain is created and is inactive
# analyst rejects DomainApplication
# test: Domain remains inactive
pass
def test_application_deleted_before_approval(self):
# DomainApplication is created
# test: Domain is created and is inactive
# admin deletes DomainApplication
# test: Domain is deleted; Hosts, HostIps and Nameservers are deleted
pass
def test_application_deleted_following_approval(self):
# DomainApplication is created
# test: Domain is created and is inactive
# analyst approves DomainApplication
# admin deletes DomainApplication
# test: DomainApplication foreign key field on Domain is set to null
pass
def test_application_approval_with_conflicting_name(self):
# DomainApplication #1 is created
# test: Domain #1 is created and is inactive
# analyst approves DomainApplication #1
# test: Domain #1 is activated
# DomainApplication #2 is created, with the same domain name string
# test: Domain #2 is created and is inactive
# analyst approves DomainApplication #2
# test: error is raised
# test: DomainApplication #1 remains approved
# test: Domain #1 remains active
# test: DomainApplication #2 remains in investigating
# test: Domain #2 remains inactive
pass
def test_application_approval_with_network_errors(self):
# TODO: scenario wherein application is approved,
# but attempts to contact the registry to activate the domain fail
pass

View file

@ -1,54 +1,487 @@
"""
Feature being tested: Registry Integration
This file tests the various ways in which the registrar interacts with the registry.
"""
from django.test import TestCase
from django.db.utils import IntegrityError
from registrar.models import (
DomainApplication,
User,
Domain,
)
from unittest import skip
class TestDomain(TestCase):
def test_empty_create_fails(self):
class TestDomainCreation(TestCase):
"""Rule: An approved domain application must result in a domain"""
def setUp(self):
"""
Background:
Given that a valid domain application exists
"""
pass
@skip("not implemented yet")
def test_approved_application_creates_domain_locally(self):
"""
Scenario: Analyst approves a domain application
When the DomainApplication transitions to approved
Then a Domain exists in the database with the same `name`
But a domain object does not exist in the registry
"""
raise
@skip("not implemented yet")
def test_accessing_domain_properties_creates_domain_in_registry(self):
"""
Scenario: A registrant checks the status of a newly approved domain
Given that no domain object exists in the registry
When `domain.is_active()` is called
Then Domain sends `commands.CreateDomain` to the registry
And `domain.state` is set to `CREATED`
And `domain.is_active()` returns False
"""
raise
def test_empty_domain_creation(self):
"""Can't create a completely empty domain."""
with self.assertRaisesRegex(IntegrityError, "name"):
Domain.objects.create()
def test_minimal_create(self):
def test_minimal_creation(self):
"""Can create with just a name."""
Domain.objects.create(name="igorville.gov")
# this assertion will not work -- for now, the fact that the
# above command didn't error out is proof enough
# self.assertEquals(domain.state, Domain.State.DRAFTED)
@skip("cannot activate a domain without mock registry")
def test_get_status(self):
"""Returns proper status based on `state`."""
domain = Domain.objects.create(name="igorville.gov")
domain.save()
self.assertEqual(None, domain.status)
domain.activate()
domain.save()
self.assertIn("ok", domain.status)
def test_duplicate_creation(self):
"""Can't create domain if name is not unique."""
Domain.objects.create(name="igorville.gov")
with self.assertRaisesRegex(IntegrityError, "name"):
Domain.objects.create(name="igorville.gov")
@skip("cannot activate a domain without mock registry")
def test_fsm_activate_fail_unique(self):
"""Can't activate domain if name is not unique."""
d1, _ = Domain.objects.get_or_create(name="igorville.gov")
d2, _ = Domain.objects.get_or_create(name="igorville.gov")
d1.activate()
d1.save()
with self.assertRaises(ValueError):
d2.activate()
@skip("cannot activate a domain without mock registry")
def test_fsm_activate_fail_unapproved(self):
"""Can't activate domain if application isn't approved."""
d1, _ = Domain.objects.get_or_create(name="igorville.gov")
user, _ = User.objects.get_or_create()
application = DomainApplication.objects.create(creator=user)
d1.domain_application = application
d1.save()
with self.assertRaises(ValueError):
d1.activate()
class TestRegistrantContacts(TestCase):
"""Rule: Registrants may modify their WHOIS data"""
def setUp(self):
"""
Background:
Given the registrant is logged in
And the registrant is the admin on a domain
"""
pass
@skip("not implemented yet")
def test_no_security_email(self):
"""
Scenario: Registrant declines to add a security contact email
Given the domain exists in the registry
Then the domain has a valid security contact with CISA defaults
And disclose flags are set to keep the email address hidden
"""
raise
@skip("not implemented yet")
def test_user_adds_security_email(self):
"""
Scenario: Registrant adds a security contact email
When `domain.security_contact` is set equal to a PublicContact with the
chosen security contact email
Then Domain sends `commands.CreateContact` to the registry
And Domain sends `commands.UpdateDomain` to the registry with the newly
created contact of type 'security'
"""
raise
@skip("not implemented yet")
def test_security_email_is_idempotent(self):
"""
Scenario: Registrant adds a security contact email twice, due to a UI glitch
When `commands.CreateContact` and `commands.UpdateDomain` are sent
to the registry twice with identical data
Then no errors are raised in Domain
"""
# implementation note: this requires seeing what happens when these are actually
# sent like this, and then implementing appropriate mocks for any errors the
# registry normally sends in this case
raise
@skip("not implemented yet")
def test_user_deletes_security_email(self):
"""
Scenario: Registrant clears out an existing security contact email
Given a domain exists in the registry with a user-added security email
When `domain.security_contact` is set equal to a PublicContact with an empty
security contact email
Then Domain sends `commands.UpdateDomain` and `commands.DeleteContact`
to the registry
And the domain has a valid security contact with CISA defaults
And disclose flags are set to keep the email address hidden
"""
raise
@skip("not implemented yet")
def test_updates_security_email(self):
"""
Scenario: Registrant replaces one valid security contact email with another
Given a domain exists in the registry with a user-added security email
When `domain.security_contact` is set equal to a PublicContact with a new
security contact email
Then Domain sends `commands.UpdateContact` to the registry
"""
raise
@skip("not implemented yet")
def test_update_is_unsuccessful(self):
"""
Scenario: An update to the security contact is unsuccessful
When an error is returned from epplibwrapper
Then a user-friendly error message is returned for displaying on the web
"""
raise
class TestRegistrantNameservers(TestCase):
"""Rule: Registrants may modify their nameservers"""
def setUp(self):
"""
Background:
Given the registrant is logged in
And the registrant is the admin on a domain
"""
pass
@skip("not implemented yet")
def test_user_adds_one_nameserver(self):
"""
Scenario: Registrant adds a single nameserver
Given the domain has zero nameservers
When `domain.nameservers` is set to an array of length 1
Then `commands.CreateHost` and `commands.UpdateDomain` is sent
to the registry
And `domain.is_active` returns False
"""
raise
@skip("not implemented yet")
def test_user_adds_two_nameservers(self):
"""
Scenario: Registrant adds 2 or more nameservers, thereby activating the domain
Given the domain has zero nameservers
When `domain.nameservers` is set to an array of length 2
Then `commands.CreateHost` and `commands.UpdateDomain` is sent
to the registry
And `domain.is_active` returns True
"""
raise
@skip("not implemented yet")
def test_user_adds_too_many_nameservers(self):
"""
Scenario: Registrant adds 14 or more nameservers
Given the domain has zero nameservers
When `domain.nameservers` is set to an array of length 14
Then Domain raises a user-friendly error
"""
raise
@skip("not implemented yet")
def test_user_removes_some_nameservers(self):
"""
Scenario: Registrant removes some nameservers, while keeping at least 2
Given the domain has 3 nameservers
When `domain.nameservers` is set to an array containing nameserver #1 and #2
Then `commands.UpdateDomain` and `commands.DeleteHost` is sent
to the registry
And `domain.is_active` returns True
"""
raise
@skip("not implemented yet")
def test_user_removes_too_many_nameservers(self):
"""
Scenario: Registrant removes some nameservers, bringing the total to less than 2
Given the domain has 3 nameservers
When `domain.nameservers` is set to an array containing nameserver #1
Then `commands.UpdateDomain` and `commands.DeleteHost` is sent
to the registry
And `domain.is_active` returns False
"""
raise
@skip("not implemented yet")
def test_user_replaces_nameservers(self):
"""
Scenario: Registrant simultaneously adds and removes some nameservers
Given the domain has 3 nameservers
When `domain.nameservers` is set to an array containing nameserver #1 plus
two new nameservers
Then `commands.CreateHost` is sent to create #4 and #5
And `commands.UpdateDomain` is sent to add #4 and #5 plus remove #2 and #3
And `commands.DeleteHost` is sent to delete #2 and #3
"""
raise
@skip("not implemented yet")
def test_user_cannot_add_subordinate_without_ip(self):
"""
Scenario: Registrant adds a nameserver which is a subdomain of their .gov
Given the domain exists in the registry
When `domain.nameservers` is set to an array containing an entry
with a subdomain of the domain and no IP addresses
Then Domain raises a user-friendly error
"""
raise
@skip("not implemented yet")
def test_user_updates_ips(self):
"""
Scenario: Registrant changes IP addresses for a nameserver
Given the domain exists in the registry
And has a subordinate nameserver
When `domain.nameservers` is set to an array containing that nameserver
with a different IP address(es)
Then `commands.UpdateHost` is sent to the registry
"""
raise
@skip("not implemented yet")
def test_user_cannot_add_non_subordinate_with_ip(self):
"""
Scenario: Registrant adds a nameserver which is NOT a subdomain of their .gov
Given the domain exists in the registry
When `domain.nameservers` is set to an array containing an entry
which is not a subdomain of the domain and has IP addresses
Then Domain raises a user-friendly error
"""
raise
@skip("not implemented yet")
def test_nameservers_are_idempotent(self):
"""
Scenario: Registrant adds a set of nameservers twice, due to a UI glitch
When `commands.CreateHost` and `commands.UpdateDomain` are sent
to the registry twice with identical data
Then no errors are raised in Domain
"""
# implementation note: this requires seeing what happens when these are actually
# sent like this, and then implementing appropriate mocks for any errors the
# registry normally sends in this case
raise
@skip("not implemented yet")
def test_update_is_unsuccessful(self):
"""
Scenario: An update to the nameservers is unsuccessful
When an error is returned from epplibwrapper
Then a user-friendly error message is returned for displaying on the web
"""
raise
class TestRegistrantDNSSEC(TestCase):
"""Rule: Registrants may modify their secure DNS data"""
def setUp(self):
"""
Background:
Given the registrant is logged in
And the registrant is the admin on a domain
"""
pass
@skip("not implemented yet")
def test_user_adds_dns_data(self):
"""
Scenario: Registrant adds DNS data
...
"""
raise
@skip("not implemented yet")
def test_dnssec_is_idempotent(self):
"""
Scenario: Registrant adds DNS data twice, due to a UI glitch
...
"""
# implementation note: this requires seeing what happens when these are actually
# sent like this, and then implementing appropriate mocks for any errors the
# registry normally sends in this case
raise
@skip("not implemented yet")
def test_update_is_unsuccessful(self):
"""
Scenario: An update to the security contact is unsuccessful
When an error is returned from epplibwrapper
Then a user-friendly error message is returned for displaying on the web
"""
raise
class TestAnalystClientHold(TestCase):
"""Rule: Analysts may suspend or restore a domain by using client hold"""
def setUp(self):
"""
Background:
Given the analyst is logged in
And a domain exists in the registry
"""
pass
@skip("not implemented yet")
def test_analyst_places_client_hold(self):
"""
Scenario: Analyst takes a domain off the internet
When `domain.place_client_hold()` is called
Then `CLIENT_HOLD` is added to the domain's statuses
"""
raise
@skip("not implemented yet")
def test_analyst_places_client_hold_idempotent(self):
"""
Scenario: Analyst tries to place client hold twice
Given `CLIENT_HOLD` is already in the domain's statuses
When `domain.place_client_hold()` is called
Then Domain returns normally (without error)
"""
raise
@skip("not implemented yet")
def test_analyst_removes_client_hold(self):
"""
Scenario: Analyst restores a suspended domain
Given `CLIENT_HOLD` is in the domain's statuses
When `domain.remove_client_hold()` is called
Then `CLIENT_HOLD` is no longer in the domain's statuses
"""
raise
@skip("not implemented yet")
def test_analyst_removes_client_hold_idempotent(self):
"""
Scenario: Analyst tries to remove client hold twice
Given `CLIENT_HOLD` is not in the domain's statuses
When `domain.remove_client_hold()` is called
Then Domain returns normally (without error)
"""
raise
@skip("not implemented yet")
def test_update_is_unsuccessful(self):
"""
Scenario: An update to place or remove client hold is unsuccessful
When an error is returned from epplibwrapper
Then a user-friendly error message is returned for displaying on the web
"""
raise
class TestAnalystLock(TestCase):
"""Rule: Analysts may lock or unlock a domain to prevent or allow updates"""
def setUp(self):
"""
Background:
Given the analyst is logged in
And a domain exists in the registry
"""
pass
@skip("not implemented yet")
def test_analyst_locks_domain(self):
"""
Scenario: Analyst locks a domain to prevent edits or deletion
When `domain.lock()` is called
Then `CLIENT_DELETE_PROHIBITED` is added to the domain's statuses
And `CLIENT_TRANSFER_PROHIBITED` is added to the domain's statuses
And `CLIENT_UPDATE_PROHIBITED` is added to the domain's statuses
"""
raise
@skip("not implemented yet")
def test_analyst_locks_domain_idempotent(self):
"""
Scenario: Analyst tries to lock a domain twice
Given `CLIENT_*_PROHIBITED` is already in the domain's statuses
When `domain.lock()` is called
Then Domain returns normally (without error)
"""
raise
@skip("not implemented yet")
def test_analyst_removes_client_hold(self):
"""
Scenario: Analyst unlocks a domain to allow deletion or edits
Given `CLIENT_*_PROHIBITED` is in the domain's statuses
When `domain.unlock()` is called
Then `CLIENT_DELETE_PROHIBITED` is no longer in the domain's statuses
And `CLIENT_TRANSFER_PROHIBITED` is no longer in the domain's statuses
And `CLIENT_UPDATE_PROHIBITED` is no longer in the domain's statuses
"""
raise
@skip("not implemented yet")
def test_analyst_removes_client_hold_idempotent(self):
"""
Scenario: Analyst tries to unlock a domain twice
Given `CLIENT_*_PROHIBITED` is not in the domain's statuses
When `domain.unlock()` is called
Then Domain returns normally (without error)
"""
raise
@skip("not implemented yet")
def test_update_is_unsuccessful(self):
"""
Scenario: An update to lock or unlock a domain is unsuccessful
When an error is returned from epplibwrapper
Then a user-friendly error message is returned for displaying on the web
"""
raise
class TestAnalystDelete(TestCase):
"""Rule: Analysts may delete a domain"""
def setUp(self):
"""
Background:
Given the analyst is logged in
And a domain exists in the registry
"""
pass
@skip("not implemented yet")
def test_analyst_deletes_domain(self):
"""
Scenario: Analyst permanently deletes a domain
When `domain.delete()` is called
Then `commands.DeleteDomain` is sent to the registry
And `state` is set to `DELETED`
"""
raise
@skip("not implemented yet")
def test_analyst_deletes_domain_idempotent(self):
"""
Scenario: Analyst tries to delete an already deleted domain
Given `state` is already `DELETED`
When `domain.delete()` is called
Then `commands.DeleteDomain` is sent to the registry
And Domain returns normally (without error)
"""
raise
@skip("not implemented yet")
def test_deletion_is_unsuccessful(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`
"""
raise