update deletion process and tests

This commit is contained in:
matthewswspence 2024-12-03 15:04:25 -06:00
parent ede01e3181
commit b5e4f8b40c
No known key found for this signature in database
GPG key ID: FB458202A7852BA4
3 changed files with 84 additions and 14 deletions

View file

@ -254,7 +254,7 @@ class Domain(TimeStampedModel, DomainHelper):
return not cls.available(domain)
@Cache
def contacts(self) -> dict[str, str]:
def registry_contacts(self) -> dict[str, str]:
"""
Get a dictionary of registry IDs for the contacts for this domain.
@ -263,7 +263,10 @@ class Domain(TimeStampedModel, DomainHelper):
{ PublicContact.ContactTypeChoices.REGISTRANT: "jd1234",
PublicContact.ContactTypeChoices.ADMINISTRATIVE: "sh8013",...}
"""
raise NotImplementedError()
if self._cache.get("contacts"):
return self._cache.get("contacts")
else:
return self._get_property("contacts")
@Cache
def creation_date(self) -> date:
@ -1032,17 +1035,19 @@ class Domain(TimeStampedModel, DomainHelper):
logger.error(f"registry error removing client hold: {err}")
raise (err)
def _delete_contacts(self):
"""Contacts associated with this domain will be deleted.
RegistryErrors will be logged and raised. Additional
error handling should be provided by the caller.
def _delete_nonregistrant_contacts(self):
"""Non-registrant contacts associated with this domain will be deleted.
RegistryErrors will be logged and raised. Error
handling should be provided by the caller.
"""
logger.info("Deleting contacts for %s", self.name)
contacts = self._cache.get("contacts")
contacts = self.registry_contacts
logger.debug("Contacts to delete for %s inside _delete_contacts -> %s", self.name, contacts)
if contacts:
for contact in contacts:
self._delete_contact(contact)
for contact, id in contacts.items():
# registrants have to be deleted after the domain
if contact != PublicContact.ContactTypeChoices.REGISTRANT:
self._delete_contact(contact, id)
def _delete_subdomains(self):
@ -1067,6 +1072,13 @@ class Domain(TimeStampedModel, DomainHelper):
request = commands.DeleteDomain(name=self.name)
registry.send(request, cleaned=True)
def _delete_domain_registrant(self):
"""This domain's registrant should be deleted from the registry
may raises RegistryError, should be caught or handled correctly by caller"""
registrantID = self.registrant_contact.registry_id
request = commands.DeleteContact(id=registrantID)
registry.send(request, cleaned=True)
def __str__(self) -> str:
return self.name
@ -1475,8 +1487,9 @@ class Domain(TimeStampedModel, DomainHelper):
try:
logger.info("deletedInEpp()-> inside _delete_domain")
self._delete_subdomains()
self._delete_contacts()
self._delete_nonregistrant_contacts()
self._delete_domain()
self._delete_domain_registrant()
self.deleted = timezone.now()
except RegistryError as err:
logger.error(f"Could not delete domain. Registry returned error: {err}")
@ -1678,15 +1691,15 @@ class Domain(TimeStampedModel, DomainHelper):
raise e
def _delete_contact(self, contact: PublicContact):
def _delete_contact(self, contact_name: str, registry_id: str):
"""Try to delete a contact from the registry.
raises:
RegistryError: if the registry is unable to delete the contact
"""
logger.info("_delete_contact() -> Attempting to delete contact for %s from domain %s", contact.name, contact.domain)
logger.info("_delete_contact() -> Attempting to delete contact for %s from domain %s", contact_name, self.name)
try:
req = commands.DeletContact(id=contact.registry_id)
req = commands.DeleteContact(id=registry_id)
return registry.send(req, cleaned=True).res_data[0]
except RegistryError as error:
logger.error(

View file

@ -1229,6 +1229,7 @@ class MockEppLib(TestCase):
common.Status(state="serverTransferProhibited", description="", lang="en"),
common.Status(state="inactive", description="", lang="en"),
],
registrant="regContact",
ex_date=date(2023, 5, 25),
)
@ -1610,6 +1611,8 @@ class MockEppLib(TestCase):
return self.mockInfoContactCommands(_request, cleaned)
case commands.CreateContact:
return self.mockCreateContactCommands(_request, cleaned)
case commands.DeleteContact:
return self.mockDeleteContactCommands(_request, cleaned)
case commands.UpdateDomain:
return self.mockUpdateDomainCommands(_request, cleaned)
case commands.CreateHost:
@ -1731,6 +1734,7 @@ class MockEppLib(TestCase):
# Define a dictionary to map request names to data and extension values
request_mappings = {
"fake.gov": (self.mockDataInfoDomain, None),
"security.gov": (self.infoDomainNoContact, None),
"dnssec-dsdata.gov": (
self.mockDataInfoDomain,
@ -1811,6 +1815,15 @@ class MockEppLib(TestCase):
# mocks a contact error on creation
raise ContactError(code=ContactErrorCodes.CONTACT_TYPE_NONE)
return MagicMock(res_data=[self.mockDataInfoHosts])
def mockDeleteContactCommands(self, _request, cleaned):
if getattr(_request, "id", None) == "fail":
raise RegistryError(code=ErrorCode.OBJECT_EXISTS)
else:
return MagicMock(
res_data=[self.mockDataInfoContact],
code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
)
def setUp(self):
"""mock epp send function as this will fail locally"""

View file

@ -2586,6 +2586,7 @@ class TestAnalystDelete(MockEppLib):
def tearDown(self):
Host.objects.all().delete()
PublicContact.objects.all().delete()
Domain.objects.all().delete()
super().tearDown()
@ -2643,7 +2644,7 @@ class TestAnalystDelete(MockEppLib):
call(
commands.DeleteHost(name=common.HostObjSet(hosts=['ns1.sharedhost.com'])),
cleaned=True,
)
),
]
)
# Domain itself should not be deleted
@ -2651,6 +2652,49 @@ class TestAnalystDelete(MockEppLib):
# State should not have changed
self.assertEqual(domain.state, Domain.State.ON_HOLD)
def test_deletion_with_host_and_contacts(self):
"""
Scenario: Domain with related Host and Contacts is Deleted
When a contact and host exists that is tied to this domain
Then `commands.DeleteHost` is sent to the registry
Then `commands.DeleteContact` is sent to the registry
Then `commands.DeleteDomain` is sent to the registry
Then `commands.DeleteContact` is sent to the registry for the registrant contact
And `state` is set to `DELETED`
"""
# with less_console_noise():
# Desired domain
domain, _ = Domain.objects.get_or_create(name="freeman.gov", state=Domain.State.ON_HOLD)
# Put the domain in client hold
domain.place_client_hold()
# Delete it
domain.deletedInEpp()
domain.save()
# Check that the host and contacts are deleted, order doesn't matter
self.mockedSendFunction.assert_has_calls(
[
call(commands.DeleteHost(name=common.HostObjSet(hosts=['fake.host.com'])), cleaned=True),
call(commands.DeleteContact(id="securityContact"), cleaned=True),
call(commands.DeleteContact(id="technicalContact"), cleaned=True),
call(commands.DeleteContact(id="adminContact"),cleaned=True,)
],
any_order=True
)
# These calls need to be in order
self.mockedSendFunction.assert_has_calls(
[
call(commands.DeleteDomain(name="freeman.gov"), cleaned=True),
call(commands.InfoContact(id="regContact"), cleaned=True),
call(commands.DeleteContact(id="regContact"), cleaned=True),
],
)
# Domain itself should not be deleted
self.assertNotEqual(domain, None)
# State should have changed
self.assertEqual(domain.state, Domain.State.DELETED)
def test_deletion_ready_fsm_failure(self):
"""
Scenario: Domain deletion is unsuccessful due to FSM rules