mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-15 09:07:02 +02:00
Merge pull request #2025 from cisagov/rh/1979-fix-unknown-state
ISSUE 1979 PT 2: Unknown State Remdiation Pt 2
This commit is contained in:
commit
61e6206c51
6 changed files with 150 additions and 23 deletions
|
@ -1689,6 +1689,59 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
else:
|
||||
logger.error("Error _delete_hosts_if_not_used, code was %s error was %s" % (e.code, e))
|
||||
|
||||
def _fix_unknown_state(self, cleaned):
|
||||
"""
|
||||
_fix_unknown_state: Calls _add_missing_contacts_if_unknown
|
||||
to add contacts in as needed (or return an error). Otherwise
|
||||
if we are able to add contacts and the state is out of UNKNOWN
|
||||
and (and should be into DNS_NEEDED), we double check the
|
||||
current state and # of nameservers and update the state from there
|
||||
"""
|
||||
try:
|
||||
self._add_missing_contacts_if_unknown(cleaned)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"%s couldn't _add_missing_contacts_if_unknown, error was %s."
|
||||
"Domain will still be in UNKNOWN state." % (self.name, e)
|
||||
)
|
||||
if len(self.nameservers) >= 2 and (self.state != self.State.READY):
|
||||
self.ready()
|
||||
self.save()
|
||||
|
||||
@transition(field="state", source=State.UNKNOWN, target=State.DNS_NEEDED)
|
||||
def _add_missing_contacts_if_unknown(self, cleaned):
|
||||
"""
|
||||
_add_missing_contacts_if_unknown: Add contacts (SECURITY, TECHNICAL, and/or ADMINISTRATIVE)
|
||||
if they are missing, AND switch the state to DNS_NEEDED from UNKNOWN (if it
|
||||
is in an UNKNOWN state, that is an error state)
|
||||
Note: The transition state change happens at the end of the function
|
||||
"""
|
||||
|
||||
missingAdmin = True
|
||||
missingSecurity = True
|
||||
missingTech = True
|
||||
|
||||
if len(cleaned.get("_contacts")) < 3:
|
||||
for contact in cleaned.get("_contacts"):
|
||||
if contact.type == PublicContact.ContactTypeChoices.ADMINISTRATIVE:
|
||||
missingAdmin = False
|
||||
if contact.type == PublicContact.ContactTypeChoices.SECURITY:
|
||||
missingSecurity = False
|
||||
if contact.type == PublicContact.ContactTypeChoices.TECHNICAL:
|
||||
missingTech = False
|
||||
|
||||
# We are only creating if it doesn't exist so we don't overwrite
|
||||
if missingAdmin:
|
||||
administrative_contact = self.get_default_administrative_contact()
|
||||
administrative_contact.save()
|
||||
if missingSecurity:
|
||||
security_contact = self.get_default_security_contact()
|
||||
security_contact.save()
|
||||
if missingTech:
|
||||
technical_contact = self.get_default_technical_contact()
|
||||
technical_contact.save()
|
||||
|
||||
def _fetch_cache(self, fetch_hosts=False, fetch_contacts=False):
|
||||
"""Contact registry for info about a domain."""
|
||||
try:
|
||||
|
@ -1696,6 +1749,9 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
cache = self._extract_data_from_response(data_response)
|
||||
cleaned = self._clean_cache(cache, data_response)
|
||||
self._update_hosts_and_contacts(cleaned, fetch_hosts, fetch_contacts)
|
||||
|
||||
if self.state == self.State.UNKNOWN:
|
||||
self._fix_unknown_state(cleaned)
|
||||
if fetch_hosts:
|
||||
self._update_hosts_and_ips_in_db(cleaned)
|
||||
if fetch_contacts:
|
||||
|
|
|
@ -545,7 +545,6 @@ class MockDb(TestCase):
|
|||
self.domain_2, _ = Domain.objects.get_or_create(name="adomain2.gov", state=Domain.State.DNS_NEEDED)
|
||||
self.domain_3, _ = Domain.objects.get_or_create(name="ddomain3.gov", state=Domain.State.ON_HOLD)
|
||||
self.domain_4, _ = Domain.objects.get_or_create(name="bdomain4.gov", state=Domain.State.UNKNOWN)
|
||||
self.domain_4, _ = Domain.objects.get_or_create(name="bdomain4.gov", state=Domain.State.UNKNOWN)
|
||||
self.domain_5, _ = Domain.objects.get_or_create(
|
||||
name="bdomain5.gov", state=Domain.State.DELETED, deleted=timezone.make_aware(datetime(2023, 11, 1))
|
||||
)
|
||||
|
@ -977,7 +976,20 @@ class MockEppLib(TestCase):
|
|||
mockDataInfoDomain = fakedEppObject(
|
||||
"fakePw",
|
||||
cr_date=make_aware(datetime(2023, 5, 25, 19, 45, 35)),
|
||||
contacts=[common.DomainContact(contact="123", type=PublicContact.ContactTypeChoices.SECURITY)],
|
||||
contacts=[
|
||||
common.DomainContact(
|
||||
contact="securityContact",
|
||||
type=PublicContact.ContactTypeChoices.SECURITY,
|
||||
),
|
||||
common.DomainContact(
|
||||
contact="technicalContact",
|
||||
type=PublicContact.ContactTypeChoices.TECHNICAL,
|
||||
),
|
||||
common.DomainContact(
|
||||
contact="adminContact",
|
||||
type=PublicContact.ContactTypeChoices.ADMINISTRATIVE,
|
||||
),
|
||||
],
|
||||
hosts=["fake.host.com"],
|
||||
statuses=[
|
||||
common.Status(state="serverTransferProhibited", description="", lang="en"),
|
||||
|
@ -1047,10 +1059,13 @@ class MockEppLib(TestCase):
|
|||
ex_date=date(2023, 11, 15),
|
||||
)
|
||||
mockDataInfoContact = mockDataInfoDomain.dummyInfoContactResultData(
|
||||
"123", "123@mail.gov", datetime(2023, 5, 25, 19, 45, 35), "lastPw"
|
||||
id="123", email="123@mail.gov", cr_date=datetime(2023, 5, 25, 19, 45, 35), pw="lastPw"
|
||||
)
|
||||
mockDataSecurityContact = mockDataInfoDomain.dummyInfoContactResultData(
|
||||
id="securityContact", email="security@mail.gov", cr_date=datetime(2023, 5, 25, 19, 45, 35), pw="lastPw"
|
||||
)
|
||||
InfoDomainWithContacts = fakedEppObject(
|
||||
"fakepw",
|
||||
"fakePw",
|
||||
cr_date=make_aware(datetime(2023, 5, 25, 19, 45, 35)),
|
||||
contacts=[
|
||||
common.DomainContact(
|
||||
|
@ -1072,6 +1087,7 @@ class MockEppLib(TestCase):
|
|||
common.Status(state="inactive", description="", lang="en"),
|
||||
],
|
||||
registrant="regContact",
|
||||
ex_date=date(2023, 11, 15),
|
||||
)
|
||||
|
||||
InfoDomainWithDefaultSecurityContact = fakedEppObject(
|
||||
|
@ -1498,6 +1514,8 @@ class MockEppLib(TestCase):
|
|||
"meow.gov": (self.mockDataInfoDomainSubdomainAndIPAddress, None),
|
||||
"fakemeow.gov": (self.mockDataInfoDomainNotSubdomainNoIP, None),
|
||||
"subdomainwoip.gov": (self.mockDataInfoDomainSubdomainNoIP, None),
|
||||
"ddomain3.gov": (self.InfoDomainWithContacts, None),
|
||||
"igorville.gov": (self.InfoDomainWithContacts, None),
|
||||
}
|
||||
|
||||
# Retrieve the corresponding values from the dictionary
|
||||
|
|
|
@ -25,11 +25,13 @@ from registrar.models import (
|
|||
Domain,
|
||||
DomainRequest,
|
||||
DomainInformation,
|
||||
DraftDomain,
|
||||
User,
|
||||
DomainInvitation,
|
||||
Contact,
|
||||
PublicContact,
|
||||
Host,
|
||||
Website,
|
||||
DraftDomain,
|
||||
)
|
||||
from registrar.models.user_domain_role import UserDomainRole
|
||||
from registrar.models.verified_by_staff import VerifiedByStaff
|
||||
|
@ -690,6 +692,8 @@ class TestDomainAdmin(MockEppLib, WebTest):
|
|||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
PublicContact.objects.all().delete()
|
||||
Host.objects.all().delete()
|
||||
Domain.objects.all().delete()
|
||||
DomainInformation.objects.all().delete()
|
||||
DomainRequest.objects.all().delete()
|
||||
|
|
|
@ -107,9 +107,9 @@ class TestDomainCache(MockEppLib):
|
|||
common.DomainContact(contact="123", type="security"),
|
||||
]
|
||||
expectedContactsDict = {
|
||||
PublicContact.ContactTypeChoices.ADMINISTRATIVE: None,
|
||||
PublicContact.ContactTypeChoices.SECURITY: "123",
|
||||
PublicContact.ContactTypeChoices.TECHNICAL: None,
|
||||
PublicContact.ContactTypeChoices.ADMINISTRATIVE: "adminContact",
|
||||
PublicContact.ContactTypeChoices.SECURITY: "securityContact",
|
||||
PublicContact.ContactTypeChoices.TECHNICAL: "technicalContact",
|
||||
}
|
||||
expectedHostsDict = {
|
||||
"name": self.mockDataInfoDomain.hosts[0],
|
||||
|
@ -129,6 +129,7 @@ class TestDomainCache(MockEppLib):
|
|||
# The contact list should not contain what is sent by the registry by default,
|
||||
# as _fetch_cache will transform the type to PublicContact
|
||||
self.assertNotEqual(domain._cache["contacts"], expectedUnfurledContactsList)
|
||||
|
||||
self.assertEqual(domain._cache["contacts"], expectedContactsDict)
|
||||
|
||||
# get and check hosts is set correctly
|
||||
|
@ -203,19 +204,20 @@ class TestDomainCache(MockEppLib):
|
|||
def test_map_epp_contact_to_public_contact(self):
|
||||
# Tests that the mapper is working how we expect
|
||||
with less_console_noise():
|
||||
domain, _ = Domain.objects.get_or_create(name="registry.gov")
|
||||
domain, _ = Domain.objects.get_or_create(name="registry.gov", state=Domain.State.DNS_NEEDED)
|
||||
security = PublicContact.ContactTypeChoices.SECURITY
|
||||
mapped = domain.map_epp_contact_to_public_contact(
|
||||
self.mockDataInfoContact,
|
||||
self.mockDataInfoContact.id,
|
||||
self.mockDataSecurityContact,
|
||||
self.mockDataSecurityContact.id,
|
||||
security,
|
||||
)
|
||||
|
||||
# id, registry_id, and contact are the same thing
|
||||
expected_contact = PublicContact(
|
||||
domain=domain,
|
||||
contact_type=security,
|
||||
registry_id="123",
|
||||
email="123@mail.gov",
|
||||
registry_id="securityContact",
|
||||
email="security@mail.gov",
|
||||
voice="+1.8882820870",
|
||||
fax="+1-212-9876543",
|
||||
pw="lastPw",
|
||||
|
@ -232,7 +234,6 @@ class TestDomainCache(MockEppLib):
|
|||
# two duplicate objects. We would expect
|
||||
# these not to have the same state.
|
||||
expected_contact._state = mapped._state
|
||||
|
||||
# Mapped object is what we expect
|
||||
self.assertEqual(mapped.__dict__, expected_contact.__dict__)
|
||||
|
||||
|
@ -243,9 +244,9 @@ class TestDomainCache(MockEppLib):
|
|||
registry_id=domain.security_contact.registry_id,
|
||||
contact_type=security,
|
||||
).get()
|
||||
|
||||
# DB Object is the same as the mapped object
|
||||
self.assertEqual(db_object, in_db)
|
||||
|
||||
domain.security_contact = in_db
|
||||
# Trigger the getter
|
||||
_ = domain.security_contact
|
||||
|
@ -309,6 +310,40 @@ class TestDomainCache(MockEppLib):
|
|||
)
|
||||
self.assertEqual(context.exception.code, desired_error)
|
||||
|
||||
def test_fix_unknown_to_ready_state(self):
|
||||
"""
|
||||
Scenario: A error occurred and the domain's state is in UNKONWN
|
||||
which shouldn't happen. The biz logic and test is to make sure
|
||||
we resolve that UNKNOWN state to READY because it has 2 nameservers.
|
||||
Note:
|
||||
* Default state when you do get_or_create is UNKNOWN
|
||||
* justnameserver.com has 2 nameservers which is why we are using it
|
||||
* justnameserver.com also has all 3 contacts hence 0 count
|
||||
"""
|
||||
with less_console_noise():
|
||||
domain, _ = Domain.objects.get_or_create(name="justnameserver.com")
|
||||
# trigger the getter
|
||||
_ = domain.nameservers
|
||||
self.assertEqual(domain.state, Domain.State.READY)
|
||||
self.assertEqual(PublicContact.objects.filter(domain=domain.id).count(), 0)
|
||||
|
||||
def test_fix_unknown_to_dns_needed_state(self):
|
||||
"""
|
||||
Scenario: A error occurred and the domain's state is in UNKONWN
|
||||
which shouldn't happen. The biz logic and test is to make sure
|
||||
we resolve that UNKNOWN state to DNS_NEEDED because it has 1 nameserver.
|
||||
Note:
|
||||
* Default state when you do get_or_create is UNKNOWN
|
||||
* defaulttechnical.gov has 1 nameservers which is why we are using it
|
||||
* defaulttechnical.gov already has a security contact (1) hence 2 count
|
||||
"""
|
||||
with less_console_noise():
|
||||
domain, _ = Domain.objects.get_or_create(name="defaulttechnical.gov")
|
||||
# trigger the getter
|
||||
_ = domain.nameservers
|
||||
self.assertEqual(domain.state, Domain.State.DNS_NEEDED)
|
||||
self.assertEqual(PublicContact.objects.filter(domain=domain.id).count(), 2)
|
||||
|
||||
|
||||
class TestDomainCreation(MockEppLib):
|
||||
"""Rule: An approved domain request must result in a domain"""
|
||||
|
@ -346,7 +381,7 @@ class TestDomainCreation(MockEppLib):
|
|||
Given that no domain object exists in the registry
|
||||
When a property is accessed
|
||||
Then Domain sends `commands.CreateDomain` to the registry
|
||||
And `domain.state` is set to `UNKNOWN`
|
||||
And `domain.state` is set to `DNS_NEEDED`
|
||||
And `domain.is_active()` returns False
|
||||
"""
|
||||
with less_console_noise():
|
||||
|
@ -375,7 +410,7 @@ class TestDomainCreation(MockEppLib):
|
|||
any_order=False, # Ensure calls are in the specified order
|
||||
)
|
||||
|
||||
self.assertEqual(domain.state, Domain.State.UNKNOWN)
|
||||
self.assertEqual(domain.state, Domain.State.DNS_NEEDED)
|
||||
self.assertEqual(domain.is_active(), False)
|
||||
|
||||
@skip("assertion broken with mock addition")
|
||||
|
@ -400,6 +435,7 @@ class TestDomainCreation(MockEppLib):
|
|||
DomainInformation.objects.all().delete()
|
||||
DomainRequest.objects.all().delete()
|
||||
PublicContact.objects.all().delete()
|
||||
Host.objects.all().delete()
|
||||
Domain.objects.all().delete()
|
||||
User.objects.all().delete()
|
||||
DraftDomain.objects.all().delete()
|
||||
|
@ -485,6 +521,7 @@ class TestDomainStatuses(MockEppLib):
|
|||
|
||||
def tearDown(self) -> None:
|
||||
PublicContact.objects.all().delete()
|
||||
Host.objects.all().delete()
|
||||
Domain.objects.all().delete()
|
||||
super().tearDown()
|
||||
|
||||
|
@ -624,6 +661,7 @@ class TestRegistrantContacts(MockEppLib):
|
|||
self.domain._invalidate_cache()
|
||||
self.domain_contact._invalidate_cache()
|
||||
PublicContact.objects.all().delete()
|
||||
Host.objects.all().delete()
|
||||
Domain.objects.all().delete()
|
||||
|
||||
def test_no_security_email(self):
|
||||
|
@ -998,10 +1036,10 @@ class TestRegistrantContacts(MockEppLib):
|
|||
And the field `disclose` is set to true for DF.EMAIL
|
||||
"""
|
||||
with less_console_noise():
|
||||
domain, _ = Domain.objects.get_or_create(name="igorville.gov")
|
||||
domain, _ = Domain.objects.get_or_create(name="igorville.gov", state=Domain.State.DNS_NEEDED)
|
||||
expectedSecContact = PublicContact.get_default_security()
|
||||
expectedSecContact.domain = domain
|
||||
expectedSecContact.email = "123@mail.gov"
|
||||
expectedSecContact.email = "security@mail.gov"
|
||||
domain.security_contact = expectedSecContact
|
||||
expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=True)
|
||||
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
||||
|
@ -1847,6 +1885,8 @@ class TestRegistrantDNSSEC(MockEppLib):
|
|||
self.domain, _ = Domain.objects.get_or_create(name="fake.gov")
|
||||
|
||||
def tearDown(self):
|
||||
PublicContact.objects.all().delete()
|
||||
Host.objects.all().delete()
|
||||
Domain.objects.all().delete()
|
||||
super().tearDown()
|
||||
|
||||
|
@ -1904,6 +1944,7 @@ class TestRegistrantDNSSEC(MockEppLib):
|
|||
),
|
||||
cleaned=True,
|
||||
),
|
||||
call(commands.InfoHost(name="fake.host.com"), cleaned=True),
|
||||
call(
|
||||
commands.UpdateDomain(
|
||||
name="dnssec-dsdata.gov",
|
||||
|
@ -1976,6 +2017,13 @@ class TestRegistrantDNSSEC(MockEppLib):
|
|||
),
|
||||
cleaned=True,
|
||||
),
|
||||
call(
|
||||
commands.InfoDomain(
|
||||
name="dnssec-dsdata.gov",
|
||||
),
|
||||
cleaned=True,
|
||||
),
|
||||
call(commands.InfoHost(name="fake.host.com"), cleaned=True),
|
||||
call(
|
||||
commands.UpdateDomain(
|
||||
name="dnssec-dsdata.gov",
|
||||
|
@ -2129,6 +2177,7 @@ class TestRegistrantDNSSEC(MockEppLib):
|
|||
),
|
||||
cleaned=True,
|
||||
),
|
||||
call(commands.InfoHost(name="fake.host.com"), cleaned=True),
|
||||
call(
|
||||
commands.UpdateDomain(
|
||||
name="dnssec-dsdata.gov",
|
||||
|
|
|
@ -263,7 +263,7 @@ class ExportDataTest(MockDb, MockEppLib):
|
|||
"adomain10.gov,Federal,Armed Forces Retirement Home,Ready\n"
|
||||
"adomain2.gov,Interstate,(blank),Dns needed\n"
|
||||
"cdomain11.govFederal-ExecutiveWorldWarICentennialCommissionReady\n"
|
||||
"ddomain3.gov,Federal,Armed Forces Retirement Home,123@mail.gov,On hold,2023-05-25\n"
|
||||
"ddomain3.gov,Federal,Armed Forces Retirement Home,security@mail.gov,On hold,2023-11-15\n"
|
||||
"defaultsecurity.gov,Federal - Executive,World War I Centennial Commission,(blank),Ready\n"
|
||||
"zdomain12.govInterstateReady\n"
|
||||
)
|
||||
|
|
|
@ -243,7 +243,7 @@ class TestDomainDetail(TestDomainOverview):
|
|||
self.assertContains(home_page, "DNS needed")
|
||||
|
||||
def test_unknown_domain_does_not_show_as_expired_on_detail_page(self):
|
||||
"""An UNKNOWN domain does not show as expired on the detail page.
|
||||
"""An UNKNOWN domain should not exist on the detail_page anymore.
|
||||
It shows as 'DNS needed'"""
|
||||
# At the time of this test's writing, there are 6 UNKNOWN domains inherited
|
||||
# from constructors. Let's reset.
|
||||
|
@ -262,9 +262,9 @@ class TestDomainDetail(TestDomainOverview):
|
|||
igorville = Domain.objects.get(name="igorville.gov")
|
||||
self.assertEquals(igorville.state, Domain.State.UNKNOWN)
|
||||
detail_page = home_page.click("Manage", index=0)
|
||||
self.assertNotContains(detail_page, "Expired")
|
||||
self.assertContains(detail_page, "Expired")
|
||||
|
||||
self.assertContains(detail_page, "DNS needed")
|
||||
self.assertNotContains(detail_page, "DNS needed")
|
||||
|
||||
def test_domain_detail_blocked_for_ineligible_user(self):
|
||||
"""We could easily duplicate this test for all domain management
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue