Preliminary changes

This commit is contained in:
zandercymatics 2023-09-27 14:45:33 -06:00
parent 9c1e3fa013
commit c6baf9c98b
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
5 changed files with 197 additions and 36 deletions

View file

@ -6,11 +6,12 @@ from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.http.response import HttpResponseRedirect from django.http.response import HttpResponseRedirect
from django.urls import reverse from django.urls import reverse
from epplibwrapper.errors import ErrorCode, RegistryError
from registrar.models.utility.admin_sort_fields import AdminSortFields from registrar.models.utility.admin_sort_fields import AdminSortFields
from . import models from . import models
from auditlog.models import LogEntry # type: ignore from auditlog.models import LogEntry # type: ignore
from auditlog.admin import LogEntryAdmin # type: ignore from auditlog.admin import LogEntryAdmin # type: ignore
from django_fsm import TransitionNotAllowed # type: ignore
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -717,10 +718,46 @@ class DomainAdmin(ListHeaderAdmin):
def do_delete_domain(self, request, obj): def do_delete_domain(self, request, obj):
try: try:
obj.deleted() obj.deletedInEpp()
obj.save() obj.save()
except Exception as err: except RegistryError as err:
if err.is_connection_error():
self.message_user(
request,
"Error connecting to the registry",
messages.ERROR,
)
elif err.code == ErrorCode.OBJECT_STATUS_PROHIBITS_OPERATION:
self.message_user(
request,
"Error deleting this Domain: "
f"Cannot delete Domain when in status {obj.status}",
messages.ERROR,
)
elif err.code == ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION:
self.message_user(
request,
"Error deleting this Domain: "
f" This subdomain is being used as a hostname on another domain",
messages.ERROR,
)
elif err.code:
self.message_user(
request,
f"Error deleting this Domain: {err}",
messages.ERROR,
)
else:
# all other type error messages, display the error
self.message_user(request, err, messages.ERROR)
except ValueError as err:
self.message_user(request, err, messages.ERROR) self.message_user(request, err, messages.ERROR)
except TransitionNotAllowed
self.message_user(
request,
f"Error deleting this Domain: {err}",
messages.ERROR,
)
else: else:
self.message_user( self.message_user(
request, request,

View file

@ -609,11 +609,6 @@ class Domain(TimeStampedModel, DomainHelper):
""" """
return self.state == self.State.READY return self.state == self.State.READY
def delete_request(self):
"""Delete from host. Possibly a duplicate of _delete_host?"""
# TODO fix in ticket #901
pass
def transfer(self): def transfer(self):
"""Going somewhere. Not implemented.""" """Going somewhere. Not implemented."""
raise NotImplementedError() raise NotImplementedError()
@ -658,7 +653,8 @@ class Domain(TimeStampedModel, DomainHelper):
"""This domain should be deleted from the registry """This domain should be deleted from the registry
may raises RegistryError, should be caught or handled correctly by caller""" may raises RegistryError, should be caught or handled correctly by caller"""
request = commands.DeleteDomain(name=self.name) request = commands.DeleteDomain(name=self.name)
registry.send(request) response = registry.send(request, cleaned=True)
return response
def __str__(self) -> str: def __str__(self) -> str:
return self.name return self.name
@ -773,6 +769,8 @@ class Domain(TimeStampedModel, DomainHelper):
self.addAllDefaults() self.addAllDefaults()
def addAllDefaults(self): def addAllDefaults(self):
security_contact = self.get_default_security_contact() security_contact = self.get_default_security_contact()
security_contact.save() security_contact.save()
@ -805,15 +803,34 @@ class Domain(TimeStampedModel, DomainHelper):
# TODO -on the client hold ticket any additional error handling here # TODO -on the client hold ticket any additional error handling here
@transition(field="state", source=State.ON_HOLD, target=State.DELETED) @transition(field="state", source=State.ON_HOLD, target=State.DELETED)
def deleted(self): def deletedInEpp(self):
"""domain is deleted in epp but is saved in our database""" """domain is deleted in epp but is saved in our database.
# TODO Domains may not be deleted if: Returns the request_code"""
# a child host is being used by valid_delete_states = [
# another .gov domains. The host must be first removed self.State.ON_HOLD,
# and/or renamed before the parent domain may be deleted. self.State.DNS_NEEDED
logger.info("pendingCreate()-> inside pending create") ]
self._delete_domain() # Check that the domain contacts a valid status
# TODO - delete ticket any additional error handling here if (self.state not in valid_delete_states):
raise ValueError(
f"Invalid domain state of {self.state}. Cannot delete."
)
try:
logger.info("deletedInEpp()-> inside _delete_domain")
self._delete_domain()
except RegistryError as err:
logger.error(
f"Could not delete domain. Registry returned error: {err}"
)
raise err
except Exception as err:
logger.error(
f"Could not delete domain. An unspecified error occured: {err}"
)
raise err
else:
self._invalidate_cache()
@transition( @transition(
field="state", field="state",

View file

@ -598,7 +598,11 @@ class DomainApplication(TimeStampedModel):
"emails/domain_request_withdrawn.txt", "emails/domain_request_withdrawn.txt",
"emails/domain_request_withdrawn_subject.txt", "emails/domain_request_withdrawn_subject.txt",
) )
# TODO
#def delete(self, *args, **kwargs):
#super().delete(*args, **kwargs)
@transition( @transition(
field="status", field="status",
source=[IN_REVIEW, APPROVED], source=[IN_REVIEW, APPROVED],
@ -612,7 +616,7 @@ class DomainApplication(TimeStampedModel):
(will cascade), and send an email notification.""" (will cascade), and send an email notification."""
if self.status == self.APPROVED: if self.status == self.APPROVED:
self.approved_domain.delete_request() self.approved_domain.deletedInEpp()
self.approved_domain.delete() self.approved_domain.delete()
self.approved_domain = None self.approved_domain = None

View file

@ -604,6 +604,13 @@ class MockEppLib(TestCase):
# use this for when a contact is being updated # use this for when a contact is being updated
# sets the second send() to fail # sets the second send() to fail
raise RegistryError(code=ErrorCode.OBJECT_EXISTS) raise RegistryError(code=ErrorCode.OBJECT_EXISTS)
elif (
isinstance(_request, commands.DeleteDomain)
and getattr(_request, "name", None) == "fail.gov"
):
raise RegistryError(
code=ErrorCode.OBJECT_STATUS_PROHIBITS_OPERATION
)
return MagicMock(res_data=[self.mockDataInfoHosts]) return MagicMock(res_data=[self.mockDataInfoHosts])
def setUp(self): def setUp(self):

View file

@ -940,39 +940,95 @@ class TestAnalystLock(TestCase):
raise raise
class TestAnalystDelete(TestCase): class TestAnalystDelete(MockEppLib):
"""Rule: Analysts may delete a domain""" """Rule: Analysts may delete a domain"""
def setUp(self): def setUp(self):
""" """
Background: Background:
Given the analyst is logged in Given the analyst is logged in
And a domain exists in the registry And a domain exists in the registry
""" """
pass super().setUp()
self.domain, _ = Domain.objects.get_or_create(
name="fake.gov", state=Domain.State.READY
)
self.domain_on_hold, _ = Domain.objects.get_or_create(
name="fake-on-hold.gov", state=Domain.State.ON_HOLD
)
def tearDown(self):
Domain.objects.all().delete()
super().tearDown()
@skip("not implemented yet")
def test_analyst_deletes_domain(self): def test_analyst_deletes_domain(self):
""" """
Scenario: Analyst permanently deletes a domain Scenario: Analyst permanently deletes a domain
When `domain.delete()` is called When `domain.deletedInEpp()` is called
Then `commands.DeleteDomain` is sent to the registry Then `commands.DeleteDomain` is sent to the registry
And `state` is set to `DELETED` And `state` is set to `DELETED`
""" """
raise # Put the domain in client hold
self.domain.place_client_hold()
# Delete it...
self.domain.deletedInEpp()
self.mockedSendFunction.assert_has_calls(
[
call(
commands.DeleteDomain(name="fake.gov"),
cleaned=True,
)
]
)
# Domain itself should not be deleted
self.assertNotEqual(self.domain, None)
# Domain should have the right state
self.assertEqual(self.domain.state, Domain.State.DELETED)
@skip("not implemented yet")
def test_analyst_deletes_domain_idempotent(self): def test_analyst_deletes_domain_idempotent(self):
""" """
Scenario: Analyst tries to delete an already deleted domain Scenario: Analyst tries to delete an already deleted domain
Given `state` is already `DELETED` Given `state` is already `DELETED`
When `domain.delete()` is called When `domain.deletedInEpp()` is called
Then `commands.DeleteDomain` is sent to the registry Then `commands.DeleteDomain` is sent to the registry
And Domain returns normally (without error) And Domain returns normally (without error)
""" """
raise # Put the domain in client hold
self.domain.place_client_hold()
# Delete it...
self.domain.deletedInEpp()
self.mockedSendFunction.assert_has_calls(
[
call(
commands.DeleteDomain(name="fake.gov"),
cleaned=True,
)
]
)
# Domain itself should not be deleted
self.assertNotEqual(self.domain, None)
# Domain should have the right state
self.assertEqual(self.domain.state, Domain.State.DELETED)
# Delete it again - monitoring for errors
try:
self.domain.deletedInEpp()
except Exception as err:
self.fail("deletedInEpp() threw an error")
raise err
self.mockedSendFunction.assert_has_calls(
[
call(
commands.DeleteDomain(name="fake.gov"),
cleaned=True,
)
]
)
# Domain itself should not be deleted
self.assertNotEqual(self.domain, None)
# Domain should have the right state
self.assertEqual(self.domain.state, Domain.State.DELETED)
@skip("not implemented yet")
def test_deletion_is_unsuccessful(self): def test_deletion_is_unsuccessful(self):
""" """
Scenario: Domain deletion is unsuccessful Scenario: Domain deletion is unsuccessful
@ -980,4 +1036,44 @@ class TestAnalystDelete(TestCase):
Then a user-friendly error message is returned for displaying on the web Then a user-friendly error message is returned for displaying on the web
And `state` is not set to `DELETED` And `state` is not set to `DELETED`
""" """
raise domain, _ = Domain.objects.get_or_create(
name="fail.gov", state=Domain.State.ON_HOLD
)
# Put the domain in client hold
domain.place_client_hold()
# Delete it...
with self.assertRaises(RegistryError) as err:
domain.deletedInEpp()
self.assertTrue(
err.is_client_error()
and err.code == ErrorCode.OBJECT_STATUS_PROHIBITS_OPERATION
)
# TODO - check UI for error
# Domain itself should not be deleted
self.assertNotEqual(domain, None)
# State should not have changed
self.assertEqual(domain.state, Domain.State.ON_HOLD)
@skip("not implemented yet")
def test_deletion_ready_fsm_failure(self):
"""
Scenario: Domain deletion is unsuccessful due to FSM rules
Given state is 'ready'
When `domain.deletedInEpp()` is called
Then a user-friendly error message is returned for displaying on the web
And `state` is not set to `DELETED`
"""
self.domain.deletedInEpp()
self.mockedSendFunction.assert_has_calls(
[
call(
commands.DeleteDomain(name="fake.gov", auth_info=None),
cleaned=True,
)
]
)
# Domain should not be deleted
self.assertNotEqual(self.domain, None)
# Domain should have the right state
self.assertEqual(self.domain.state, "DELETED")