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

View file

@ -1229,6 +1229,7 @@ class MockEppLib(TestCase):
common.Status(state="serverTransferProhibited", description="", lang="en"), common.Status(state="serverTransferProhibited", description="", lang="en"),
common.Status(state="inactive", description="", lang="en"), common.Status(state="inactive", description="", lang="en"),
], ],
registrant="regContact",
ex_date=date(2023, 5, 25), ex_date=date(2023, 5, 25),
) )
@ -1610,6 +1611,8 @@ class MockEppLib(TestCase):
return self.mockInfoContactCommands(_request, cleaned) return self.mockInfoContactCommands(_request, cleaned)
case commands.CreateContact: case commands.CreateContact:
return self.mockCreateContactCommands(_request, cleaned) return self.mockCreateContactCommands(_request, cleaned)
case commands.DeleteContact:
return self.mockDeleteContactCommands(_request, cleaned)
case commands.UpdateDomain: case commands.UpdateDomain:
return self.mockUpdateDomainCommands(_request, cleaned) return self.mockUpdateDomainCommands(_request, cleaned)
case commands.CreateHost: case commands.CreateHost:
@ -1731,6 +1734,7 @@ class MockEppLib(TestCase):
# Define a dictionary to map request names to data and extension values # Define a dictionary to map request names to data and extension values
request_mappings = { request_mappings = {
"fake.gov": (self.mockDataInfoDomain, None),
"security.gov": (self.infoDomainNoContact, None), "security.gov": (self.infoDomainNoContact, None),
"dnssec-dsdata.gov": ( "dnssec-dsdata.gov": (
self.mockDataInfoDomain, self.mockDataInfoDomain,
@ -1812,6 +1816,15 @@ class MockEppLib(TestCase):
raise ContactError(code=ContactErrorCodes.CONTACT_TYPE_NONE) raise ContactError(code=ContactErrorCodes.CONTACT_TYPE_NONE)
return MagicMock(res_data=[self.mockDataInfoHosts]) 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): def setUp(self):
"""mock epp send function as this will fail locally""" """mock epp send function as this will fail locally"""
self.mockSendPatch = patch("registrar.models.domain.registry.send") self.mockSendPatch = patch("registrar.models.domain.registry.send")

View file

@ -2586,6 +2586,7 @@ class TestAnalystDelete(MockEppLib):
def tearDown(self): def tearDown(self):
Host.objects.all().delete() Host.objects.all().delete()
PublicContact.objects.all().delete()
Domain.objects.all().delete() Domain.objects.all().delete()
super().tearDown() super().tearDown()
@ -2643,7 +2644,7 @@ class TestAnalystDelete(MockEppLib):
call( call(
commands.DeleteHost(name=common.HostObjSet(hosts=['ns1.sharedhost.com'])), commands.DeleteHost(name=common.HostObjSet(hosts=['ns1.sharedhost.com'])),
cleaned=True, cleaned=True,
) ),
] ]
) )
# Domain itself should not be deleted # Domain itself should not be deleted
@ -2651,6 +2652,49 @@ class TestAnalystDelete(MockEppLib):
# State should not have changed # State should not have changed
self.assertEqual(domain.state, Domain.State.ON_HOLD) 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): def test_deletion_ready_fsm_failure(self):
""" """
Scenario: Domain deletion is unsuccessful due to FSM rules Scenario: Domain deletion is unsuccessful due to FSM rules