diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 079fce3bc..0f8a9ada9 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -1685,6 +1685,61 @@ 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): + # print("!! GOT INTO _fix_unknown_state") + + try: + self._add_missing_contacts(cleaned) + + except Exception as e: + logger.error( + "%s couldn't _add_missing_contacts, error was %s." + "Domain will still be in UNKNOWN state." % (self.name, e) + ) + if len(self.nameservers) >= 2: + # print("!! GOT INTO _fix_unknown_state -> have 2 or more nameserver so ready state") + self.ready() + self.save() + + @transition(field="state", source=State.UNKNOWN, target=State.DNS_NEEDED) + def _add_missing_contacts(self, cleaned): + """ + _add_missing_contacts: 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 + """ + # print("!! GOT INTO _add_missing_contacts ") + + missingAdmin = True + missingSecurity = True + missingTech = True + # print("cleaned ", cleaned) + + if len(cleaned.get("_contacts")) < 3: + # print("!! GOT INTO _add_missing_contacts -> in if statement") + for contact in cleaned.get("_contacts"): + # this means we see it + if contact.type == "admin": + missingAdmin = False + if contact.type == "security": + missingSecurity = False + if contact.type == "tech": + 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() + + # print("!! GOT INTO _add_missing_contacts -> if statement finished ") + def _fetch_cache(self, fetch_hosts=False, fetch_contacts=False): """Contact registry for info about a domain.""" try: @@ -1692,6 +1747,10 @@ 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: + # print("!! GOT INTO 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: diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index bf54efe60..786e33659 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -28,8 +28,9 @@ from registrar.models import ( User, DomainInvitation, Contact, + PublicContact, + Host, Website, - DraftDomain, ) from registrar.models.user_domain_role import UserDomainRole from registrar.models.verified_by_staff import VerifiedByStaff @@ -618,6 +619,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() diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 8887aae1f..93407db26 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -309,6 +309,38 @@ 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") + _ = 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") + _ = 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 +378,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 +407,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") @@ -485,6 +517,7 @@ class TestDomainStatuses(MockEppLib): def tearDown(self) -> None: PublicContact.objects.all().delete() + Host.objects.all().delete() Domain.objects.all().delete() super().tearDown() @@ -624,6 +657,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): @@ -1847,6 +1881,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()