From 273400c9089bb733245b5b68637cab56078bf6b5 Mon Sep 17 00:00:00 2001 From: Rebecca Hsieh Date: Mon, 24 Mar 2025 15:39:23 -0700 Subject: [PATCH] Adding in unit tests --- src/registrar/admin.py | 25 +++----- src/registrar/models/domain.py | 29 ++-------- src/registrar/models/domain_request.py | 19 ------ src/registrar/tests/common.py | 2 +- src/registrar/tests/test_admin_request.py | 70 ++++++++++++++++++++++- src/registrar/tests/test_models_domain.py | 50 ++++++++++++++++ 6 files changed, 131 insertions(+), 64 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 9e218161b..9eb3b2e2d 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -2888,26 +2888,11 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): Returns a tuple: (obj: DomainRequest, should_proceed: bool) """ - + # print("$$$$ IN THE _HANDLE_STATUS_CHANGE FUNCTION") should_proceed = True error_message = None - - print("##### original_obj.status is", original_obj.status) - print("##### obj.status is", obj.status) domain_name = original_obj.requested_domain.name - # if ( - # original_obj.status != models.DomainRequest.DomainRequestStatus.APPROVED - # and obj.status == models.DomainRequest.DomainRequestStatus.APPROVED - # and original_obj.requested_domain is not None - # and Domain.objects.filter(name=original_obj.requested_domain.name).exists() - # and Domain.is_pending_delete(domain_name) - # ): - # print(f"##### in the if statement - {domain_name} is not available, yay") - # # raise FSMDomainRequestError(code=FSMErrorCodes.DOMAIN_IS_PENDING_DELETE) - # error_message = FSMDomainRequestError.get_error_message(FSMErrorCodes.DOMAIN_IS_PENDING_DELETE) - # print(f"##### error_message is - {error_message}") - # Get the method that should be run given the status selected_method = self.get_status_method_mapping(obj) if selected_method is None: @@ -2935,8 +2920,12 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): and Domain.objects.filter(name=original_obj.requested_domain.name).exists() and Domain.is_pending_delete(domain_name) ): - # TODO: Write blurb here - print(f"##### in the elif statement - {domain_name} is not available, yay") + # 1. If the domain request is not approved in previous state (original status) + # 2. If the new status that's supposed to be triggered IS approved + # 3. That it's a valid domain + # 4. That the domain name already exists + # 5. AND that the domain is currently in pendingDelete state + print("$$$$ in _handle_status_change") error_message = FSMDomainRequestError.get_error_message(FSMErrorCodes.DOMAIN_IS_PENDING_DELETE) elif ( original_obj.status != models.DomainRequest.DomainRequestStatus.APPROVED diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index a82d55ec9..715f9a49b 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -264,38 +264,17 @@ class Domain(TimeStampedModel, DomainHelper): @classmethod def is_pending_delete(cls, domain: str) -> bool: - # TODO: Write blurb here still + """Check if domain is pendingDelete state via response from registry.""" domain_name = domain.lower() - print("***** in is_pending_delete") - # If not avail, check registry using InfoDomain - info_req = commands.InfoDomain(domain_name) info_response = registry.send(info_req, cleaned=True) - print("***** MODELS/DOMAIN.PY IN TRY info_response is:", info_response) - """ - InfoDomainResult(code=1000, msg='Command completed successfully', - res_data=[InfoDomainResultData(roid='DF1364752-GOV', - statuses=[Status(state='serverTransferProhibited', description=None, lang='en')], - cl_id='gov2023-ote', cr_id='gov2023-ote', cr_date=datetime.datetime(2023, 10, 23, 17, 8, 9, tzinfo=tzlocal()), - up_id='gov2023-ote', up_date=datetime.datetime(2023, 10, 28, 17, 8, 9, tzinfo=tzlocal()), - tr_date=None, name='meoward.gov', registrant='sh8013', admins=[], nsset=None, - keyset=None, ex_date=datetime.date(2024, 10, 23), auth_info=DomainAuthInfo(pw='feedabee'))], - cl_tr_id='wup7ad#2025-03-17T22:21:39.298149', sv_tr_id='aOQBDg4fQoSMGemppS5AdQ==-73ca', - extensions=[], msg_q=None) - """ - # Ensure res_data exists and is not empty if info_response and info_response.res_data: - res_data = info_response.res_data[0] - print("***** MODELS/DOMAIN.PY IN IF Response info_response.res_data[0]:", info_response.res_data[0]) - - domain_status_state = [status.state for status in res_data.statuses] - print(f"***** Domain statuses for {domain_name} is: {domain_status_state}") - - # Check for pendingDelete status - return "pendingDelete" not in domain_status_state + domain_status_state = [status.state for status in info_response.res_data[0].statuses] + # Return True if in pendingDelete status, else False + return "pendingDelete" in domain_status_state return False diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index 52f1d4141..6fd51555d 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -1185,28 +1185,9 @@ class DomainRequest(TimeStampedModel): # create the domain Domain = apps.get_model("registrar.Domain") - print("##### BEFORE the Domain.available check") - print( - "##### Domain.is_pending_delete(self.requested_domain.name)", - Domain.is_pending_delete(self.requested_domain.name), - ) - print("##### self.requested_domain.name is", self.requested_domain.name) - - # == Check that the domain_request is valid == # - # if Domain.objects.filter(name=self.requested_domain.name).exists(): - # print("##### If it already exists, we do another check") - # if Domain.is_pending_delete(self.requested_domain.name): - # print(f"***** in the if statement - {self.requested_domain.name} is not available, yay") - # raise FSMDomainRequestError(code=FSMErrorCodes.DOMAIN_IS_PENDING_DELETE) - # else: - # print("##### in the else statement") - # raise FSMDomainRequestError(code=FSMErrorCodes.APPROVE_DOMAIN_IN_USE) - if Domain.objects.filter(name=self.requested_domain.name).exists(): raise FSMDomainRequestError(code=FSMErrorCodes.APPROVE_DOMAIN_IN_USE) - print("##### AFTER the Domain.available check") - # == Create the domain and related components == # created_domain = Domain.objects.create(name=self.requested_domain.name) self.approved_domain = created_domain diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index 8fbf052a4..e2b2b1faa 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -1071,7 +1071,7 @@ def completed_domain_request( # noqa email="testy@town.com", phone="(555) 555 5555", ) - domain, _ = DraftDomain.objects.get_or_create(name=name) + domain = DraftDomain.objects.create(name=name) other, _ = Contact.objects.get_or_create( first_name="Testy", last_name="Tester", diff --git a/src/registrar/tests/test_admin_request.py b/src/registrar/tests/test_admin_request.py index 9320dd3d3..d730211ec 100644 --- a/src/registrar/tests/test_admin_request.py +++ b/src/registrar/tests/test_admin_request.py @@ -2155,7 +2155,6 @@ class TestDomainRequestAdmin(MockEppLib): Used to test errors when saving a change with an active domain, also used to test side effects when saving a change goes through.""" - with less_console_noise(): # Create an instance of the model domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.APPROVED) @@ -2268,6 +2267,75 @@ class TestDomainRequestAdmin(MockEppLib): "Cannot approve. Requested domain is already in use.", ) + # @less_console_noise + def test_error_when_approving_domain_in_pending_delete_state(self): + """Prevent approving a domain when another request with the same name is in pendingDelete.""" + + # 1. Create two domain requests with the same name + to_be_pending_delete_state_domain_request = completed_domain_request( + status=DomainRequest.DomainRequestStatus.SUBMITTED, name="meoward1.gov" + ) + print("#1 to_be_pending_delete_state_domain_request is", to_be_pending_delete_state_domain_request) + + to_be_in_review_to_try_to_approve_domain_request = completed_domain_request( + status=DomainRequest.DomainRequestStatus.SUBMITTED, name="meoward1.gov" + ) + print( + "#1 to_be_in_review_to_try_to_approve_domain_request is", to_be_in_review_to_try_to_approve_domain_request + ) + + # 2. Approve to_be_pending_delete_state_domain_request + to_be_pending_delete_state_domain_request.status = DomainRequest.DomainRequestStatus.APPROVED + to_be_pending_delete_state_domain_request.save() + + # # 2.5 And put it into pendingDelete state + # with patch("registrar.models.domain.Domain.is_pending_delete", return_value=True): + # to_be_pending_delete_state_domain_request.save() + + + # 3. Put to_be_in_review_to_try_to_approve_domain_request into in-review state + to_be_in_review_to_try_to_approve_domain_request.status = DomainRequest.DomainRequestStatus.IN_REVIEW + to_be_in_review_to_try_to_approve_domain_request.save() + + # 4. Update request as a superuser + request = self.factory.post( + f"/admin/registrar/domainrequest/{to_be_in_review_to_try_to_approve_domain_request.pk}/change/" + ) + request.user = self.superuser + request.session = {} + + print("#4 to_be_pending_delete_state_domain_request is", to_be_pending_delete_state_domain_request) + print( + "#4 to_be_in_review_to_try_to_approve_domain_request is", to_be_in_review_to_try_to_approve_domain_request + ) + + # 5. Use ExitStack for combine patching like above + with ExitStack() as stack: + print("$$$$$ Do we get into the with ExitStack statement") + # Patch django.contrib.messages.error + stack.enter_context(patch.object(messages, "error")) + + with patch("registrar.models.domain.Domain.is_pending_delete", return_value=True) as pending_patch: + # to_be_in_review_to_try_to_approve_domain_request.save() + print("$$$$$ inside the second with statement") + + # Attempt to approve to_be_in_review_to_try_to_approve_domain_request + # (which should fail due to to_be_pending_delete_state_domain_request being in pendingDelete) + to_be_in_review_to_try_to_approve_domain_request.status = DomainRequest.DomainRequestStatus.APPROVED + print("to_be_in_review_to_try_to_approve_domain_request is ", to_be_in_review_to_try_to_approve_domain_request) + + # Save the model with the patched request + self.admin.save_model(request, to_be_in_review_to_try_to_approve_domain_request, None, True) + + print("$$$$ pending_patch.call_args_list is", pending_patch.call_args_list) + print("$$$$ pending_patch.called is", pending_patch.called) + + # Assert that the correct error message is displayed + messages.error.assert_called_once_with( + request, + "Domain of same name is currently in pending delete state.", + ) + @less_console_noise def test_no_error_when_saving_to_approved_and_domain_exists(self): """The negative of the redundant admin check on model transition not allowed.""" diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 93072f93b..af3e17f43 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -666,6 +666,56 @@ class TestDomainAvailable(MockEppLib): self.assertFalse(available) patcher.stop() + def test_is_pending_delete(self): + """ + Scenario: Testing if a domain is in pendingDelete status from the registry + Should return True + + * Mock EPP response with pendingDelete status + * Validate InfoDomain command is called + * Validate response given mock + """ + + def side_effect(_request, cleaned): + mock_response = MagicMock() + mock_response.res_data = [MagicMock(statuses=[MagicMock(state="pendingDelete")])] + return mock_response + + with patch("registrar.models.domain.registry.send") as mocked_send: + mocked_send.side_effect = side_effect + + result = Domain.is_pending_delete("is-pending-delete.gov") + + mocked_send.assert_called_once_with(commands.InfoDomain("is-pending-delete.gov"), cleaned=True) + + # Assert: The result is False because of pendingDelete status + self.assertTrue(result) + + def test_is_not_pending_delete(self): + """ + Scenario: Testing if a domain is NOT in pendingDelete status. + Should return False. + + * Mock EPP response without pendingDelete status (isserverTransferProhibited) + * Validate response given mock + * Validate response given mock + """ + + def side_effect(_request, cleaned): + mock_response = MagicMock() + mock_response.res_data = [ + MagicMock(statuses=[MagicMock(state="serverTransferProhibited"), MagicMock(state="ok")]) + ] + return mock_response + + with patch("registrar.models.domain.registry.send") as mocked_send: + mocked_send.side_effect = side_effect + + result = Domain.is_pending_delete("is-not-pending.gov") + + mocked_send.assert_called_once_with(commands.InfoDomain("is-not-pending.gov"), cleaned=True) + self.assertFalse(result) + def test_domain_available_with_invalid_error(self): """ Scenario: Testing whether an invalid domain is available