From 5fd84b534d9898ae1a93c711311e97959f481351 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Wed, 6 Sep 2023 15:19:32 -0400 Subject: [PATCH] Unit test the unallowed transitions, error message on admin, and side effects of successful transitions --- src/registrar/tests/test_admin.py | 155 +++++++++++++++++++++++++++++ src/registrar/tests/test_models.py | 41 +++++++- 2 files changed, 195 insertions(+), 1 deletion(-) diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index fc5478dd9..9cde6d5cf 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -1,5 +1,8 @@ from django.test import TestCase, RequestFactory, Client from django.contrib.admin.sites import AdminSite +from unittest.mock import patch +from contextlib import ExitStack +from django.contrib import messages from registrar.admin import ( DomainApplicationAdmin, @@ -10,6 +13,7 @@ from registrar.admin import ( from registrar.models import ( DomainApplication, DomainInformation, + Domain, User, DomainInvitation, ) @@ -432,8 +436,159 @@ class TestDomainApplicationAdmin(TestCase): request, "Cannot edit an application with a restricted creator.", ) + + def test_error_when_saving_approved_to_rejected_and_domain_is_active(self): + # Create an instance of the model + application = completed_application(status=DomainApplication.APPROVED) + domain = Domain.objects.create(name=application.requested_domain.name) + application.approved_domain = domain + application.save() + # Create a request object with a superuser + request = self.factory.post( + "/admin/registrar/domainapplication/{}/change/".format(application.pk) + ) + request.user = self.superuser + + # Define a custom implementation for is_active + def custom_is_active(self): + return True # Override to return True + + # Use ExitStack to combine patch contexts + with ExitStack() as stack: + # Patch Domain.is_active and django.contrib.messages.error simultaneously + stack.enter_context(patch.object(Domain, 'is_active', custom_is_active)) + stack.enter_context(patch.object(messages, 'error')) + + # Simulate saving the model + application.status = DomainApplication.REJECTED + self.admin.save_model(request, application, None, True) + + # Assert that the error message was called with the correct argument + messages.error.assert_called_once_with( + request, + "This action is not permitted, the domain " + + "is already active.", + ) + + def test_side_effects_when_saving_approved_to_rejected(self): + # Create an instance of the model + application = completed_application(status=DomainApplication.APPROVED) + domain = Domain.objects.create(name=application.requested_domain.name) + domain_information = DomainInformation.objects.create(creator=self.superuser, domain=domain) + application.approved_domain = domain + application.save() + + # Create a request object with a superuser + request = self.factory.post( + "/admin/registrar/domainapplication/{}/change/".format(application.pk) + ) + request.user = self.superuser + + # Define a custom implementation for is_active + def custom_is_active(self): + return False # Override to return False + + # Use ExitStack to combine patch contexts + with ExitStack() as stack: + # Patch Domain.is_active and django.contrib.messages.error simultaneously + stack.enter_context(patch.object(Domain, 'is_active', custom_is_active)) + stack.enter_context(patch.object(messages, 'error')) + + # Simulate saving the model + application.status = DomainApplication.REJECTED + self.admin.save_model(request, application, None, True) + + # Assert that the error message was never called + messages.error.assert_not_called() + + self.assertEqual(application.approved_domain, None) + + # Assert that Domain got Deleted + with self.assertRaises(Domain.DoesNotExist): + domain.refresh_from_db() + + # Assert that DomainInformation got Deleted + with self.assertRaises(DomainInformation.DoesNotExist): + domain_information.refresh_from_db() + + def test_error_when_saving_approved_to_ineligible_and_domain_is_active(self): + # Create an instance of the model + application = completed_application(status=DomainApplication.APPROVED) + domain = Domain.objects.create(name=application.requested_domain.name) + application.approved_domain = domain + application.save() + + # Create a request object with a superuser + request = self.factory.post( + "/admin/registrar/domainapplication/{}/change/".format(application.pk) + ) + request.user = self.superuser + + # Define a custom implementation for is_active + def custom_is_active(self): + return True # Override to return True + + # Use ExitStack to combine patch contexts + with ExitStack() as stack: + # Patch Domain.is_active and django.contrib.messages.error simultaneously + stack.enter_context(patch.object(Domain, 'is_active', custom_is_active)) + stack.enter_context(patch.object(messages, 'error')) + + # Simulate saving the model + application.status = DomainApplication.INELIGIBLE + self.admin.save_model(request, application, None, True) + + # Assert that the error message was called with the correct argument + messages.error.assert_called_once_with( + request, + "This action is not permitted, the domain " + + "is already active.", + ) + + def test_side_effects_when_saving_approved_to_ineligible(self): + # Create an instance of the model + application = completed_application(status=DomainApplication.APPROVED) + domain = Domain.objects.create(name=application.requested_domain.name) + domain_information = DomainInformation.objects.create(creator=self.superuser, domain=domain) + application.approved_domain = domain + application.save() + + # Create a request object with a superuser + request = self.factory.post( + "/admin/registrar/domainapplication/{}/change/".format(application.pk) + ) + request.user = self.superuser + + # Define a custom implementation for is_active + def custom_is_active(self): + return False # Override to return False + + # Use ExitStack to combine patch contexts + with ExitStack() as stack: + # Patch Domain.is_active and django.contrib.messages.error simultaneously + stack.enter_context(patch.object(Domain, 'is_active', custom_is_active)) + stack.enter_context(patch.object(messages, 'error')) + + # Simulate saving the model + application.status = DomainApplication.INELIGIBLE + self.admin.save_model(request, application, None, True) + + # Assert that the error message was never called + messages.error.assert_not_called() + + self.assertEqual(application.approved_domain, None) + + # Assert that Domain got Deleted + with self.assertRaises(Domain.DoesNotExist): + domain.refresh_from_db() + + # Assert that DomainInformation got Deleted + with self.assertRaises(DomainInformation.DoesNotExist): + domain_information.refresh_from_db() + def tearDown(self): + Domain.objects.all().delete() DomainInformation.objects.all().delete() DomainApplication.objects.all().delete() User.objects.all().delete() diff --git a/src/registrar/tests/test_models.py b/src/registrar/tests/test_models.py index ca1191061..a6a6fb3c9 100644 --- a/src/registrar/tests/test_models.py +++ b/src/registrar/tests/test_models.py @@ -1,5 +1,6 @@ from django.test import TestCase from django.db.utils import IntegrityError +from unittest.mock import patch from registrar.models import ( Contact, @@ -439,7 +440,26 @@ class TestDomainApplication(TestCase): application = completed_application(status=DomainApplication.INELIGIBLE) with self.assertRaises(TransitionNotAllowed): - application.reject_with_prejudice() + application.reject() + + def test_transition_not_allowed_approved_rejected_when_domain_is_active(self): + """Create an application with status approved, create a matching domain that + is active, and call reject against transition rules""" + + application = completed_application(status=DomainApplication.APPROVED) + domain = Domain.objects.create(name=application.requested_domain.name) + application.approved_domain = domain + application.save() + + # Define a custom implementation for is_active + def custom_is_active(self): + return True # Override to return True + + # Use patch to temporarily replace is_active with the custom implementation + with patch.object(Domain, 'is_active', custom_is_active): + # Now, when you call is_active on Domain, it will return True + with self.assertRaises(TransitionNotAllowed): + application.reject() def test_transition_not_allowed_started_ineligible(self): """Create an application with status started and call reject @@ -494,6 +514,25 @@ class TestDomainApplication(TestCase): with self.assertRaises(TransitionNotAllowed): application.reject_with_prejudice() + + def test_transition_not_allowed_approved_ineligible_when_domain_is_active(self): + """Create an application with status approved, create a matching domain that + is active, and call reject_with_prejudice against transition rules""" + + application = completed_application(status=DomainApplication.APPROVED) + domain = Domain.objects.create(name=application.requested_domain.name) + application.approved_domain = domain + application.save() + + # Define a custom implementation for is_active + def custom_is_active(self): + return True # Override to return True + + # Use patch to temporarily replace is_active with the custom implementation + with patch.object(Domain, 'is_active', custom_is_active): + # Now, when you call is_active on Domain, it will return True + with self.assertRaises(TransitionNotAllowed): + application.reject_with_prejudice() class TestPermissions(TestCase):