mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-21 02:06:03 +02:00
Merge pull request #3185 from cisagov/ms/2823-update-delete-domain-process
#2823: update delete domain process - [MS]
This commit is contained in:
commit
c02b036301
7 changed files with 335 additions and 75 deletions
|
@ -20,7 +20,7 @@ applications:
|
|||
# Tell Django where it is being hosted
|
||||
DJANGO_BASE_URL: https://getgov-ms.app.cloud.gov
|
||||
# Tell Django how much stuff to log
|
||||
DJANGO_LOG_LEVEL: INFO
|
||||
DJANGO_LOG_LEVEL: DEBUG
|
||||
# default public site location
|
||||
GETGOV_PUBLIC_SITE_URL: https://get.gov
|
||||
# Flag to disable/enable features in prod environments
|
||||
|
|
|
@ -62,9 +62,11 @@ class RegistryError(Exception):
|
|||
- 2501 - 2502 Something malicious or abusive may have occurred
|
||||
"""
|
||||
|
||||
def __init__(self, *args, code=None, **kwargs):
|
||||
def __init__(self, *args, code=None, note="", **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.code = code
|
||||
# note is a string that can be used to provide additional context
|
||||
self.note = note
|
||||
|
||||
def should_retry(self):
|
||||
return self.code == ErrorCode.COMMAND_FAILED
|
||||
|
|
|
@ -3334,7 +3334,7 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
|||
except RegistryError as err:
|
||||
# Using variables to get past the linter
|
||||
message1 = f"Cannot delete Domain when in state {obj.state}"
|
||||
message2 = "This subdomain is being used as a hostname on another domain"
|
||||
message2 = f"This subdomain is being used as a hostname on another domain: {err.note}"
|
||||
# Human-readable mappings of ErrorCodes. Can be expanded.
|
||||
error_messages = {
|
||||
# noqa on these items as black wants to reformat to an invalid length
|
||||
|
|
|
@ -230,6 +230,12 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
"""Called during delete. Example: `del domain.registrant`."""
|
||||
super().__delete__(obj)
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
# If the domain is deleted we don't want the expiration date to be set
|
||||
if self.state == self.State.DELETED and self.expiration_date:
|
||||
self.expiration_date = None
|
||||
super().save(force_insert, force_update, using, update_fields)
|
||||
|
||||
@classmethod
|
||||
def available(cls, domain: str) -> bool:
|
||||
"""Check if a domain is available.
|
||||
|
@ -253,7 +259,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.
|
||||
|
||||
|
@ -706,7 +712,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
raise e
|
||||
|
||||
@nameservers.setter # type: ignore
|
||||
def nameservers(self, hosts: list[tuple[str, list]]):
|
||||
def nameservers(self, hosts: list[tuple[str, list]]): # noqa
|
||||
"""Host should be a tuple of type str, str,... where the elements are
|
||||
Fully qualified host name, addresses associated with the host
|
||||
example: [(ns1.okay.gov, [127.0.0.1, others ips])]"""
|
||||
|
@ -743,7 +749,12 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
|
||||
successTotalNameservers = len(oldNameservers) - deleteCount + addToDomainCount
|
||||
|
||||
self._delete_hosts_if_not_used(hostsToDelete=deleted_values)
|
||||
try:
|
||||
self._delete_hosts_if_not_used(hostsToDelete=deleted_values)
|
||||
except Exception as e:
|
||||
# we don't need this part to succeed in order to continue.
|
||||
logger.error("Failed to delete nameserver hosts: %s", e)
|
||||
|
||||
if successTotalNameservers < 2:
|
||||
try:
|
||||
self.dns_needed()
|
||||
|
@ -1029,6 +1040,47 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
def _delete_domain(self):
|
||||
"""This domain should be deleted from the registry
|
||||
may raises RegistryError, should be caught or handled correctly by caller"""
|
||||
|
||||
logger.info("Deleting subdomains for %s", self.name)
|
||||
# check if any subdomains are in use by another domain
|
||||
hosts = Host.objects.filter(name__regex=r".+{}".format(self.name))
|
||||
for host in hosts:
|
||||
if host.domain != self:
|
||||
logger.error("Unable to delete host: %s is in use by another domain: %s", host.name, host.domain)
|
||||
raise RegistryError(
|
||||
code=ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION,
|
||||
note=f"Host {host.name} is in use by {host.domain}",
|
||||
)
|
||||
|
||||
(
|
||||
deleted_values,
|
||||
updated_values,
|
||||
new_values,
|
||||
oldNameservers,
|
||||
) = self.getNameserverChanges(hosts=[])
|
||||
|
||||
_ = self._update_host_values(updated_values, oldNameservers) # returns nothing, just need to be run and errors
|
||||
addToDomainList, _ = self.createNewHostList(new_values)
|
||||
deleteHostList, _ = self.createDeleteHostList(deleted_values)
|
||||
responseCode = self.addAndRemoveHostsFromDomain(hostsToAdd=addToDomainList, hostsToDelete=deleteHostList)
|
||||
|
||||
# if unable to update domain raise error and stop
|
||||
if responseCode != ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY:
|
||||
raise NameserverError(code=nsErrorCodes.BAD_DATA)
|
||||
|
||||
# addAndRemoveHostsFromDomain removes the hosts from the domain object,
|
||||
# but we still need to delete the object themselves
|
||||
self._delete_hosts_if_not_used(hostsToDelete=deleted_values)
|
||||
|
||||
logger.debug("Deleting non-registrant contacts for %s", self.name)
|
||||
contacts = PublicContact.objects.filter(domain=self)
|
||||
for contact in contacts:
|
||||
if contact.contact_type != PublicContact.ContactTypeChoices.REGISTRANT:
|
||||
self._update_domain_with_contact(contact, rem=True)
|
||||
request = commands.DeleteContact(contact.registry_id)
|
||||
registry.send(request, cleaned=True)
|
||||
|
||||
logger.info("Deleting domain %s", self.name)
|
||||
request = commands.DeleteDomain(name=self.name)
|
||||
registry.send(request, cleaned=True)
|
||||
|
||||
|
@ -1096,7 +1148,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
Returns True if expired, False otherwise.
|
||||
"""
|
||||
if self.expiration_date is None:
|
||||
return True
|
||||
return self.state != self.State.DELETED
|
||||
now = timezone.now().date()
|
||||
return self.expiration_date < now
|
||||
|
||||
|
@ -1430,6 +1482,8 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
@transition(field="state", source=[State.ON_HOLD, State.DNS_NEEDED], target=State.DELETED)
|
||||
def deletedInEpp(self):
|
||||
"""Domain is deleted in epp but is saved in our database.
|
||||
Subdomains will be deleted first if not in use by another domain.
|
||||
Contacts for this domain will also be deleted.
|
||||
Error handling should be provided by the caller."""
|
||||
# While we want to log errors, we want to preserve
|
||||
# that information when this function is called.
|
||||
|
@ -1439,8 +1493,9 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
logger.info("deletedInEpp()-> inside _delete_domain")
|
||||
self._delete_domain()
|
||||
self.deleted = timezone.now()
|
||||
self.expiration_date = None
|
||||
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}. {err.note}")
|
||||
raise err
|
||||
except TransitionNotAllowed as err:
|
||||
logger.error("Could not delete domain. FSM failure: {err}")
|
||||
|
@ -1745,7 +1800,6 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
"""delete the host object in registry,
|
||||
will only delete the host object, if it's not being used by another domain
|
||||
Performs just the DeleteHost epp call
|
||||
Supresses regstry error, as registry can disallow delete for various reasons
|
||||
Args:
|
||||
hostsToDelete (list[str])- list of nameserver/host names to remove
|
||||
Returns:
|
||||
|
@ -1764,6 +1818,8 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
else:
|
||||
logger.error("Error _delete_hosts_if_not_used, code was %s error was %s" % (e.code, e))
|
||||
|
||||
raise e
|
||||
|
||||
def _fix_unknown_state(self, cleaned):
|
||||
"""
|
||||
_fix_unknown_state: Calls _add_missing_contacts_if_unknown
|
||||
|
|
|
@ -1232,6 +1232,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),
|
||||
)
|
||||
|
||||
|
@ -1394,6 +1395,15 @@ class MockEppLib(TestCase):
|
|||
hosts=["fake.host.com"],
|
||||
)
|
||||
|
||||
infoDomainSharedHost = fakedEppObject(
|
||||
"sharedHost.gov",
|
||||
cr_date=make_aware(datetime(2023, 5, 25, 19, 45, 35)),
|
||||
contacts=[],
|
||||
hosts=[
|
||||
"ns1.sharedhost.com",
|
||||
],
|
||||
)
|
||||
|
||||
infoDomainThreeHosts = fakedEppObject(
|
||||
"my-nameserver.gov",
|
||||
cr_date=make_aware(datetime(2023, 5, 25, 19, 45, 35)),
|
||||
|
@ -1604,6 +1614,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:
|
||||
|
@ -1611,10 +1623,7 @@ class MockEppLib(TestCase):
|
|||
case commands.UpdateHost:
|
||||
return self.mockUpdateHostCommands(_request, cleaned)
|
||||
case commands.DeleteHost:
|
||||
return MagicMock(
|
||||
res_data=[self.mockDataHostChange],
|
||||
code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
|
||||
)
|
||||
return self.mockDeleteHostCommands(_request, cleaned)
|
||||
case commands.CheckDomain:
|
||||
return self.mockCheckDomainCommand(_request, cleaned)
|
||||
case commands.DeleteDomain:
|
||||
|
@ -1667,6 +1676,15 @@ class MockEppLib(TestCase):
|
|||
code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
|
||||
)
|
||||
|
||||
def mockDeleteHostCommands(self, _request, cleaned):
|
||||
host = getattr(_request, "name", None)
|
||||
if "sharedhost.com" in host:
|
||||
raise RegistryError(code=ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION, note="ns1.sharedhost.com")
|
||||
return MagicMock(
|
||||
res_data=[self.mockDataHostChange],
|
||||
code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
|
||||
)
|
||||
|
||||
def mockUpdateDomainCommands(self, _request, cleaned):
|
||||
if getattr(_request, "name", None) == "dnssec-invalid.gov":
|
||||
raise RegistryError(code=ErrorCode.PARAMETER_VALUE_RANGE_ERROR)
|
||||
|
@ -1678,10 +1696,7 @@ class MockEppLib(TestCase):
|
|||
|
||||
def mockDeleteDomainCommands(self, _request, cleaned):
|
||||
if getattr(_request, "name", None) == "failDelete.gov":
|
||||
name = getattr(_request, "name", None)
|
||||
fake_nameserver = "ns1.failDelete.gov"
|
||||
if name in fake_nameserver:
|
||||
raise RegistryError(code=ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION)
|
||||
raise RegistryError(code=ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION)
|
||||
return None
|
||||
|
||||
def mockRenewDomainCommand(self, _request, cleaned):
|
||||
|
@ -1721,6 +1736,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,
|
||||
|
@ -1751,6 +1767,7 @@ class MockEppLib(TestCase):
|
|||
"subdomainwoip.gov": (self.mockDataInfoDomainSubdomainNoIP, None),
|
||||
"ddomain3.gov": (self.InfoDomainWithContacts, None),
|
||||
"igorville.gov": (self.InfoDomainWithContacts, None),
|
||||
"sharingiscaring.gov": (self.infoDomainSharedHost, None),
|
||||
}
|
||||
|
||||
# Retrieve the corresponding values from the dictionary
|
||||
|
@ -1801,6 +1818,15 @@ class MockEppLib(TestCase):
|
|||
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"""
|
||||
self.mockSendPatch = patch("registrar.models.domain.registry.send")
|
||||
|
|
|
@ -16,6 +16,7 @@ from registrar.models import (
|
|||
Host,
|
||||
Portfolio,
|
||||
)
|
||||
from registrar.models.public_contact import PublicContact
|
||||
from registrar.models.user_domain_role import UserDomainRole
|
||||
from .common import (
|
||||
MockSESClient,
|
||||
|
@ -59,6 +60,7 @@ class TestDomainAdminAsStaff(MockEppLib):
|
|||
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()
|
||||
|
@ -170,7 +172,7 @@ class TestDomainAdminAsStaff(MockEppLib):
|
|||
@less_console_noise_decorator
|
||||
def test_deletion_is_successful(self):
|
||||
"""
|
||||
Scenario: Domain deletion is unsuccessful
|
||||
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`
|
||||
|
@ -221,6 +223,55 @@ class TestDomainAdminAsStaff(MockEppLib):
|
|||
|
||||
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: This subdomain is being used as a hostname on another domain: ns1.sharedhost.com", # 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):
|
||||
"""
|
||||
|
|
|
@ -9,6 +9,7 @@ from django.db.utils import IntegrityError
|
|||
from unittest.mock import MagicMock, patch, call
|
||||
import datetime
|
||||
from django.utils.timezone import make_aware
|
||||
from api.tests.common import less_console_noise_decorator
|
||||
from registrar.models import Domain, Host, HostIP
|
||||
|
||||
from unittest import skip
|
||||
|
@ -1454,6 +1455,7 @@ class TestRegistrantNameservers(MockEppLib):
|
|||
),
|
||||
call(commands.DeleteHost(name="ns1.cats-are-superior3.com"), cleaned=True),
|
||||
]
|
||||
|
||||
self.mockedSendFunction.assert_has_calls(expectedCalls, any_order=True)
|
||||
self.assertFalse(self.domainWithThreeNS.is_active())
|
||||
|
||||
|
@ -2582,12 +2584,32 @@ class TestAnalystDelete(MockEppLib):
|
|||
"""
|
||||
super().setUp()
|
||||
self.domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
|
||||
self.domain_with_contacts, _ = Domain.objects.get_or_create(name="freeman.gov", state=Domain.State.READY)
|
||||
self.domain_on_hold, _ = Domain.objects.get_or_create(name="fake-on-hold.gov", state=Domain.State.ON_HOLD)
|
||||
Host.objects.create(name="ns1.sharingiscaring.gov", domain=self.domain_on_hold)
|
||||
PublicContact.objects.create(
|
||||
registry_id="regContact",
|
||||
contact_type=PublicContact.ContactTypeChoices.REGISTRANT,
|
||||
domain=self.domain_with_contacts,
|
||||
)
|
||||
PublicContact.objects.create(
|
||||
registry_id="adminContact",
|
||||
contact_type=PublicContact.ContactTypeChoices.ADMINISTRATIVE,
|
||||
domain=self.domain_with_contacts,
|
||||
)
|
||||
PublicContact.objects.create(
|
||||
registry_id="techContact",
|
||||
contact_type=PublicContact.ContactTypeChoices.TECHNICAL,
|
||||
domain=self.domain_with_contacts,
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
Host.objects.all().delete()
|
||||
PublicContact.objects.all().delete()
|
||||
Domain.objects.all().delete()
|
||||
super().tearDown()
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_analyst_deletes_domain(self):
|
||||
"""
|
||||
Scenario: Analyst permanently deletes a domain
|
||||
|
@ -2597,59 +2619,163 @@ class TestAnalystDelete(MockEppLib):
|
|||
|
||||
The deleted date is set.
|
||||
"""
|
||||
with less_console_noise():
|
||||
# Put the domain in client hold
|
||||
self.domain.place_client_hold()
|
||||
# Delete it...
|
||||
self.domain.deletedInEpp()
|
||||
self.domain.save()
|
||||
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)
|
||||
# Domain should have a deleted
|
||||
self.assertNotEqual(self.domain.deleted, None)
|
||||
# Cache should be invalidated
|
||||
self.assertEqual(self.domain._cache, {})
|
||||
# Put the domain in client hold
|
||||
self.domain.place_client_hold()
|
||||
# Delete it...
|
||||
self.domain.deletedInEpp()
|
||||
self.domain.save()
|
||||
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)
|
||||
# Domain should have a deleted
|
||||
self.assertNotEqual(self.domain.deleted, None)
|
||||
# Cache should be invalidated
|
||||
self.assertEqual(self.domain._cache, {})
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_deletion_is_unsuccessful(self):
|
||||
"""
|
||||
Scenario: Domain deletion is unsuccessful
|
||||
When a subdomain exists
|
||||
When a subdomain exists that is in use by another domain
|
||||
Then a client error is returned of code 2305
|
||||
And `state` is not set to `DELETED`
|
||||
"""
|
||||
with less_console_noise():
|
||||
# Desired domain
|
||||
domain, _ = Domain.objects.get_or_create(name="failDelete.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()
|
||||
domain.save()
|
||||
self.assertTrue(err.is_client_error() and err.code == ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION)
|
||||
self.mockedSendFunction.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
commands.DeleteDomain(name="failDelete.gov"),
|
||||
cleaned=True,
|
||||
)
|
||||
]
|
||||
)
|
||||
# Domain itself should not be deleted
|
||||
self.assertNotEqual(domain, None)
|
||||
# State should not have changed
|
||||
self.assertEqual(domain.state, Domain.State.ON_HOLD)
|
||||
# Desired domain
|
||||
domain, _ = Domain.objects.get_or_create(name="sharingiscaring.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()
|
||||
domain.save()
|
||||
|
||||
self.assertTrue(err.code == ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION)
|
||||
self.assertEqual(err.msg, "Host ns1.sharingiscaring.gov is in use by: fake-on-hold.gov")
|
||||
# Domain itself should not be deleted
|
||||
self.assertNotEqual(domain, None)
|
||||
# State should not have changed
|
||||
self.assertEqual(domain.state, Domain.State.ON_HOLD)
|
||||
|
||||
@less_console_noise_decorator
|
||||
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 all the needed commands are sent to the registry
|
||||
And `state` is set to `DELETED`
|
||||
"""
|
||||
# Put the domain in client hold
|
||||
self.domain_with_contacts.place_client_hold()
|
||||
# Delete it
|
||||
self.domain_with_contacts.deletedInEpp()
|
||||
self.domain_with_contacts.save()
|
||||
|
||||
# Check that the host and contacts are deleted
|
||||
self.mockedSendFunction.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
commands.UpdateDomain(
|
||||
name="freeman.gov",
|
||||
add=[common.Status(state=Domain.Status.CLIENT_HOLD, description="", lang="en")],
|
||||
rem=[],
|
||||
nsset=None,
|
||||
keyset=None,
|
||||
registrant=None,
|
||||
auth_info=None,
|
||||
),
|
||||
cleaned=True,
|
||||
),
|
||||
]
|
||||
)
|
||||
self.mockedSendFunction.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
commands.InfoDomain(name="freeman.gov", auth_info=None),
|
||||
cleaned=True,
|
||||
),
|
||||
call(
|
||||
commands.InfoHost(name="fake.host.com"),
|
||||
cleaned=True,
|
||||
),
|
||||
call(
|
||||
commands.UpdateDomain(
|
||||
name="freeman.gov",
|
||||
add=[],
|
||||
rem=[common.HostObjSet(hosts=["fake.host.com"])],
|
||||
nsset=None,
|
||||
keyset=None,
|
||||
registrant=None,
|
||||
auth_info=None,
|
||||
),
|
||||
cleaned=True,
|
||||
),
|
||||
]
|
||||
)
|
||||
self.mockedSendFunction.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
commands.DeleteHost(name="fake.host.com"),
|
||||
cleaned=True,
|
||||
),
|
||||
call(
|
||||
commands.UpdateDomain(
|
||||
name="freeman.gov",
|
||||
add=[],
|
||||
rem=[common.DomainContact(contact="adminContact", type="admin")],
|
||||
nsset=None,
|
||||
keyset=None,
|
||||
registrant=None,
|
||||
auth_info=None,
|
||||
),
|
||||
cleaned=True,
|
||||
),
|
||||
call(
|
||||
commands.DeleteContact(id="adminContact"),
|
||||
cleaned=True,
|
||||
),
|
||||
call(
|
||||
commands.UpdateDomain(
|
||||
name="freeman.gov",
|
||||
add=[],
|
||||
rem=[common.DomainContact(contact="techContact", type="tech")],
|
||||
nsset=None,
|
||||
keyset=None,
|
||||
registrant=None,
|
||||
auth_info=None,
|
||||
),
|
||||
cleaned=True,
|
||||
),
|
||||
call(
|
||||
commands.DeleteContact(id="techContact"),
|
||||
cleaned=True,
|
||||
),
|
||||
],
|
||||
any_order=True,
|
||||
)
|
||||
self.mockedSendFunction.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
commands.DeleteDomain(name="freeman.gov"),
|
||||
cleaned=True,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
# Domain itself should not be deleted
|
||||
self.assertNotEqual(self.domain_with_contacts, None)
|
||||
# State should have changed
|
||||
self.assertEqual(self.domain_with_contacts.state, Domain.State.DELETED)
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_deletion_ready_fsm_failure(self):
|
||||
"""
|
||||
Scenario: Domain deletion is unsuccessful due to FSM rules
|
||||
|
@ -2661,15 +2787,14 @@ class TestAnalystDelete(MockEppLib):
|
|||
|
||||
The deleted date is still null.
|
||||
"""
|
||||
with less_console_noise():
|
||||
self.assertEqual(self.domain.state, Domain.State.READY)
|
||||
with self.assertRaises(TransitionNotAllowed) as err:
|
||||
self.domain.deletedInEpp()
|
||||
self.domain.save()
|
||||
self.assertTrue(err.is_client_error() and err.code == ErrorCode.OBJECT_STATUS_PROHIBITS_OPERATION)
|
||||
# Domain should not be deleted
|
||||
self.assertNotEqual(self.domain, None)
|
||||
# Domain should have the right state
|
||||
self.assertEqual(self.domain.state, Domain.State.READY)
|
||||
# deleted should be null
|
||||
self.assertEqual(self.domain.deleted, None)
|
||||
self.assertEqual(self.domain.state, Domain.State.READY)
|
||||
with self.assertRaises(TransitionNotAllowed) as err:
|
||||
self.domain.deletedInEpp()
|
||||
self.domain.save()
|
||||
self.assertTrue(err.is_client_error() and err.code == ErrorCode.OBJECT_STATUS_PROHIBITS_OPERATION)
|
||||
# Domain should not be deleted
|
||||
self.assertNotEqual(self.domain, None)
|
||||
# Domain should have the right state
|
||||
self.assertEqual(self.domain.state, Domain.State.READY)
|
||||
# deleted should be null
|
||||
self.assertEqual(self.domain.deleted, None)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue