from django.forms import ValidationError from django.test import TestCase from django.db.utils import IntegrityError from django.db import transaction from unittest.mock import patch from django.test import RequestFactory from registrar.models import ( Contact, DomainRequest, DomainInformation, User, Website, Domain, DraftDomain, DomainInvitation, UserDomainRole, FederalAgency, ) import boto3_mocking from registrar.models.portfolio import Portfolio from registrar.models.portfolio_invitation import PortfolioInvitation from registrar.models.transition_domain import TransitionDomain from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices from registrar.models.verified_by_staff import VerifiedByStaff # type: ignore from registrar.utility.constants import BranchChoices from .common import ( MockSESClient, less_console_noise, completed_domain_request, set_domain_request_investigators, create_test_user, ) from django_fsm import TransitionNotAllowed from waffle.testutils import override_flag from api.tests.common import less_console_noise_decorator @boto3_mocking.patching class TestDomainRequest(TestCase): @less_console_noise_decorator def setUp(self): self.dummy_user, _ = Contact.objects.get_or_create( email="mayor@igorville.com", first_name="Hello", last_name="World" ) self.dummy_user_2, _ = User.objects.get_or_create( username="intern@igorville.com", email="intern@igorville.com", first_name="Lava", last_name="World" ) self.started_domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.STARTED, name="started.gov", ) self.submitted_domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.SUBMITTED, name="submitted.gov", ) self.in_review_domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.IN_REVIEW, name="in-review.gov", ) self.action_needed_domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.ACTION_NEEDED, name="action-needed.gov", ) self.approved_domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.APPROVED, name="approved.gov", ) self.withdrawn_domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.WITHDRAWN, name="withdrawn.gov", ) self.rejected_domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.REJECTED, name="rejected.gov", ) self.ineligible_domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.INELIGIBLE, name="ineligible.gov", ) # Store all domain request statuses in a variable for ease of use self.all_domain_requests = [ self.started_domain_request, self.submitted_domain_request, self.in_review_domain_request, self.action_needed_domain_request, self.approved_domain_request, self.withdrawn_domain_request, self.rejected_domain_request, self.ineligible_domain_request, ] self.mock_client = MockSESClient() def tearDown(self): super().tearDown() DomainInformation.objects.all().delete() DomainRequest.objects.all().delete() DraftDomain.objects.all().delete() Domain.objects.all().delete() User.objects.all().delete() self.mock_client.EMAILS_SENT.clear() def assertNotRaises(self, exception_type): """Helper method for testing allowed transitions.""" with less_console_noise(): return self.assertRaises(Exception, None, exception_type) @less_console_noise_decorator def test_federal_agency_set_to_non_federal_on_approve(self): """Ensures that when the federal_agency field is 'none' when .approve() is called, the field is set to the 'Non-Federal Agency' record""" domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.IN_REVIEW, name="city2.gov", federal_agency=None, ) # Ensure that the federal agency is None self.assertEqual(domain_request.federal_agency, None) # Approve the request domain_request.approve() self.assertEqual(domain_request.status, DomainRequest.DomainRequestStatus.APPROVED) # After approval, it should be "Non-Federal agency" expected_federal_agency = FederalAgency.objects.filter(agency="Non-Federal Agency").first() self.assertEqual(domain_request.federal_agency, expected_federal_agency) def test_empty_create_fails(self): """Can't create a completely empty domain request.""" with less_console_noise(): with transaction.atomic(): with self.assertRaisesRegex(IntegrityError, "creator"): DomainRequest.objects.create() @less_console_noise_decorator def test_minimal_create(self): """Can create with just a creator.""" user, _ = User.objects.get_or_create(username="testy") domain_request = DomainRequest.objects.create(creator=user) self.assertEqual(domain_request.status, DomainRequest.DomainRequestStatus.STARTED) @less_console_noise_decorator def test_full_create(self): """Can create with all fields.""" user, _ = User.objects.get_or_create(username="testy") contact = Contact.objects.create() com_website, _ = Website.objects.get_or_create(website="igorville.com") gov_website, _ = Website.objects.get_or_create(website="igorville.gov") domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov") domain_request = DomainRequest.objects.create( creator=user, investigator=user, generic_org_type=DomainRequest.OrganizationChoices.FEDERAL, federal_type=BranchChoices.EXECUTIVE, is_election_board=False, organization_name="Test", address_line1="100 Main St.", address_line2="APT 1A", state_territory="CA", zipcode="12345-6789", senior_official=contact, requested_domain=domain, submitter=contact, purpose="Igorville rules!", anything_else="All of Igorville loves the dotgov program.", is_policy_acknowledged=True, ) domain_request.current_websites.add(com_website) domain_request.alternative_domains.add(gov_website) domain_request.other_contacts.add(contact) domain_request.save() @less_console_noise_decorator def test_domain_info(self): """Can create domain info with all fields.""" user, _ = User.objects.get_or_create(username="testy") contact = Contact.objects.create() domain, _ = Domain.objects.get_or_create(name="igorville.gov") information = DomainInformation.objects.create( creator=user, generic_org_type=DomainInformation.OrganizationChoices.FEDERAL, federal_type=BranchChoices.EXECUTIVE, is_election_board=False, organization_name="Test", address_line1="100 Main St.", address_line2="APT 1A", state_territory="CA", zipcode="12345-6789", senior_official=contact, submitter=contact, purpose="Igorville rules!", anything_else="All of Igorville loves the dotgov program.", is_policy_acknowledged=True, domain=domain, ) information.other_contacts.add(contact) information.save() self.assertEqual(information.domain.id, domain.id) self.assertEqual(information.id, domain.domain_info.id) @less_console_noise_decorator def test_status_fsm_submit_fail(self): user, _ = User.objects.get_or_create(username="testy") domain_request = DomainRequest.objects.create(creator=user) with boto3_mocking.clients.handler_for("sesv2", self.mock_client): with less_console_noise(): with self.assertRaises(ValueError): # can't submit a domain request with a null domain name domain_request.submit() @less_console_noise_decorator def test_status_fsm_submit_succeed(self): user, _ = User.objects.get_or_create(username="testy") site = DraftDomain.objects.create(name="igorville.gov") domain_request = DomainRequest.objects.create(creator=user, requested_domain=site) # no submitter email so this emits a log warning with boto3_mocking.clients.handler_for("sesv2", self.mock_client): with less_console_noise(): domain_request.submit() self.assertEqual(domain_request.status, domain_request.DomainRequestStatus.SUBMITTED) @less_console_noise_decorator def check_email_sent( self, domain_request, msg, action, expected_count, expected_content=None, expected_email="mayor@igorville.com" ): """Check if an email was sent after performing an action.""" with self.subTest(msg=msg, action=action): with boto3_mocking.clients.handler_for("sesv2", self.mock_client): # Perform the specified action action_method = getattr(domain_request, action) action_method() # Check if an email was sent sent_emails = [ email for email in MockSESClient.EMAILS_SENT if expected_email in email["kwargs"]["Destination"]["ToAddresses"] ] self.assertEqual(len(sent_emails), expected_count) if expected_content: email_content = sent_emails[0]["kwargs"]["Content"]["Simple"]["Body"]["Text"]["Data"] self.assertIn(expected_content, email_content) @override_flag("profile_feature", active=False) @less_console_noise_decorator def test_submit_from_started_sends_email(self): msg = "Create a domain request and submit it and see if email was sent." domain_request = completed_domain_request(submitter=self.dummy_user, user=self.dummy_user_2) self.check_email_sent(domain_request, msg, "submit", 1, expected_content="Hello") @override_flag("profile_feature", active=True) @less_console_noise_decorator def test_submit_from_started_sends_email_to_creator(self): """Tests if, when the profile feature flag is on, we send an email to the creator""" msg = "Create a domain request and submit it and see if email was sent when the feature flag is on." domain_request = completed_domain_request(submitter=self.dummy_user, user=self.dummy_user_2) self.check_email_sent( domain_request, msg, "submit", 1, expected_content="Lava", expected_email="intern@igorville.com" ) @less_console_noise_decorator def test_submit_from_withdrawn_sends_email(self): msg = "Create a withdrawn domain request and submit it and see if email was sent." domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.WITHDRAWN, submitter=self.dummy_user ) self.check_email_sent(domain_request, msg, "submit", 1, expected_content="Hello") @less_console_noise_decorator def test_submit_from_action_needed_does_not_send_email(self): msg = "Create a domain request with ACTION_NEEDED status and submit it, check if email was not sent." domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.ACTION_NEEDED) self.check_email_sent(domain_request, msg, "submit", 0) @less_console_noise_decorator def test_submit_from_in_review_does_not_send_email(self): msg = "Create a withdrawn domain request and submit it and see if email was sent." domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW) self.check_email_sent(domain_request, msg, "submit", 0) @less_console_noise_decorator def test_approve_sends_email(self): msg = "Create a domain request and approve it and see if email was sent." domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.IN_REVIEW, submitter=self.dummy_user ) self.check_email_sent(domain_request, msg, "approve", 1, expected_content="Hello") @less_console_noise_decorator def test_withdraw_sends_email(self): msg = "Create a domain request and withdraw it and see if email was sent." domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.IN_REVIEW, submitter=self.dummy_user ) self.check_email_sent(domain_request, msg, "withdraw", 1, expected_content="Hello") @less_console_noise_decorator def test_reject_sends_email(self): msg = "Create a domain request and reject it and see if email was sent." domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.APPROVED, submitter=self.dummy_user ) self.check_email_sent(domain_request, msg, "reject", 1, expected_content="Hello") @less_console_noise_decorator def test_reject_with_prejudice_does_not_send_email(self): msg = "Create a domain request and reject it with prejudice and see if email was sent." domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.APPROVED) self.check_email_sent(domain_request, msg, "reject_with_prejudice", 0) @less_console_noise_decorator def assert_fsm_transition_raises_error(self, test_cases, method_to_run): """Given a list of test cases, check if each transition throws the intended error""" with boto3_mocking.clients.handler_for("sesv2", self.mock_client), less_console_noise(): for domain_request, exception_type in test_cases: with self.subTest(domain_request=domain_request, exception_type=exception_type): with self.assertRaises(exception_type): # Retrieve the method by name from the domain_request object and call it method = getattr(domain_request, method_to_run) # Call the method method() @less_console_noise_decorator def assert_fsm_transition_does_not_raise_error(self, test_cases, method_to_run): """Given a list of test cases, ensure that none of them throw transition errors""" with boto3_mocking.clients.handler_for("sesv2", self.mock_client), less_console_noise(): for domain_request, exception_type in test_cases: with self.subTest(domain_request=domain_request, exception_type=exception_type): try: # Retrieve the method by name from the DomainRequest object and call it method = getattr(domain_request, method_to_run) # Call the method method() except exception_type: self.fail(f"{exception_type} was raised, but it was not expected.") @less_console_noise_decorator def test_submit_transition_allowed_with_no_investigator(self): """ Tests for attempting to transition without an investigator. For submit, this should be valid in all cases. """ test_cases = [ (self.started_domain_request, TransitionNotAllowed), (self.in_review_domain_request, TransitionNotAllowed), (self.action_needed_domain_request, TransitionNotAllowed), (self.withdrawn_domain_request, TransitionNotAllowed), ] # Set all investigators to none set_domain_request_investigators(self.all_domain_requests, None) self.assert_fsm_transition_does_not_raise_error(test_cases, "submit") @less_console_noise_decorator def test_submit_transition_allowed_with_investigator_not_staff(self): """ Tests for attempting to transition with an investigator user that is not staff. For submit, this should be valid in all cases. """ test_cases = [ (self.in_review_domain_request, TransitionNotAllowed), (self.action_needed_domain_request, TransitionNotAllowed), ] # Set all investigators to a user with no staff privs user, _ = User.objects.get_or_create(username="pancakesyrup", is_staff=False) set_domain_request_investigators(self.all_domain_requests, user) self.assert_fsm_transition_does_not_raise_error(test_cases, "submit") @less_console_noise_decorator def test_submit_transition_allowed(self): """ Test that calling submit from allowable statuses does raises TransitionNotAllowed. """ test_cases = [ (self.started_domain_request, TransitionNotAllowed), (self.in_review_domain_request, TransitionNotAllowed), (self.action_needed_domain_request, TransitionNotAllowed), (self.withdrawn_domain_request, TransitionNotAllowed), ] self.assert_fsm_transition_does_not_raise_error(test_cases, "submit") @less_console_noise_decorator def test_submit_transition_allowed_twice(self): """ Test that rotating between submit and in_review doesn't throw an error """ with boto3_mocking.clients.handler_for("sesv2", self.mock_client): try: # Make a submission self.in_review_domain_request.submit() # Rerun the old method to get back to the original state self.in_review_domain_request.in_review() # Make another submission self.in_review_domain_request.submit() except TransitionNotAllowed: self.fail("TransitionNotAllowed was raised, but it was not expected.") self.assertEqual(self.in_review_domain_request.status, DomainRequest.DomainRequestStatus.SUBMITTED) @less_console_noise_decorator def test_submit_transition_not_allowed(self): """ Test that calling submit against transition rules raises TransitionNotAllowed. """ test_cases = [ (self.submitted_domain_request, TransitionNotAllowed), (self.approved_domain_request, TransitionNotAllowed), (self.rejected_domain_request, TransitionNotAllowed), (self.ineligible_domain_request, TransitionNotAllowed), ] self.assert_fsm_transition_raises_error(test_cases, "submit") @less_console_noise_decorator def test_in_review_transition_allowed(self): """ Test that calling in_review from allowable statuses does raises TransitionNotAllowed. """ test_cases = [ (self.submitted_domain_request, TransitionNotAllowed), (self.action_needed_domain_request, TransitionNotAllowed), (self.approved_domain_request, TransitionNotAllowed), (self.rejected_domain_request, TransitionNotAllowed), (self.ineligible_domain_request, TransitionNotAllowed), ] self.assert_fsm_transition_does_not_raise_error(test_cases, "in_review") @less_console_noise_decorator def test_in_review_transition_not_allowed_with_no_investigator(self): """ Tests for attempting to transition without an investigator """ test_cases = [ (self.action_needed_domain_request, TransitionNotAllowed), (self.approved_domain_request, TransitionNotAllowed), (self.rejected_domain_request, TransitionNotAllowed), (self.ineligible_domain_request, TransitionNotAllowed), ] # Set all investigators to none set_domain_request_investigators(self.all_domain_requests, None) self.assert_fsm_transition_raises_error(test_cases, "in_review") @less_console_noise_decorator def test_in_review_transition_not_allowed_with_investigator_not_staff(self): """ Tests for attempting to transition with an investigator that is not staff. This should throw an exception. """ test_cases = [ (self.action_needed_domain_request, TransitionNotAllowed), (self.approved_domain_request, TransitionNotAllowed), (self.rejected_domain_request, TransitionNotAllowed), (self.ineligible_domain_request, TransitionNotAllowed), ] # Set all investigators to a user with no staff privs user, _ = User.objects.get_or_create(username="pancakesyrup", is_staff=False) set_domain_request_investigators(self.all_domain_requests, user) self.assert_fsm_transition_raises_error(test_cases, "in_review") @less_console_noise_decorator def test_in_review_transition_not_allowed(self): """ Test that calling in_review against transition rules raises TransitionNotAllowed. """ test_cases = [ (self.started_domain_request, TransitionNotAllowed), (self.in_review_domain_request, TransitionNotAllowed), (self.withdrawn_domain_request, TransitionNotAllowed), ] self.assert_fsm_transition_raises_error(test_cases, "in_review") @less_console_noise_decorator def test_action_needed_transition_allowed(self): """ Test that calling action_needed from allowable statuses does raises TransitionNotAllowed. """ test_cases = [ (self.in_review_domain_request, TransitionNotAllowed), (self.approved_domain_request, TransitionNotAllowed), (self.rejected_domain_request, TransitionNotAllowed), (self.ineligible_domain_request, TransitionNotAllowed), ] self.assert_fsm_transition_does_not_raise_error(test_cases, "action_needed") @less_console_noise_decorator def test_action_needed_transition_not_allowed_with_no_investigator(self): """ Tests for attempting to transition without an investigator """ test_cases = [ (self.in_review_domain_request, TransitionNotAllowed), (self.approved_domain_request, TransitionNotAllowed), (self.rejected_domain_request, TransitionNotAllowed), (self.ineligible_domain_request, TransitionNotAllowed), ] # Set all investigators to none set_domain_request_investigators(self.all_domain_requests, None) self.assert_fsm_transition_raises_error(test_cases, "action_needed") @less_console_noise_decorator def test_action_needed_transition_not_allowed_with_investigator_not_staff(self): """ Tests for attempting to transition with an investigator that is not staff """ test_cases = [ (self.in_review_domain_request, TransitionNotAllowed), (self.approved_domain_request, TransitionNotAllowed), (self.rejected_domain_request, TransitionNotAllowed), (self.ineligible_domain_request, TransitionNotAllowed), ] # Set all investigators to a user with no staff privs user, _ = User.objects.get_or_create(username="pancakesyrup", is_staff=False) set_domain_request_investigators(self.all_domain_requests, user) self.assert_fsm_transition_raises_error(test_cases, "action_needed") @less_console_noise_decorator def test_action_needed_transition_not_allowed(self): """ Test that calling action_needed against transition rules raises TransitionNotAllowed. """ test_cases = [ (self.started_domain_request, TransitionNotAllowed), (self.submitted_domain_request, TransitionNotAllowed), (self.action_needed_domain_request, TransitionNotAllowed), (self.withdrawn_domain_request, TransitionNotAllowed), ] self.assert_fsm_transition_raises_error(test_cases, "action_needed") @less_console_noise_decorator def test_approved_transition_allowed(self): """ Test that calling action_needed from allowable statuses does raises TransitionNotAllowed. """ test_cases = [ (self.submitted_domain_request, TransitionNotAllowed), (self.in_review_domain_request, TransitionNotAllowed), (self.action_needed_domain_request, TransitionNotAllowed), (self.rejected_domain_request, TransitionNotAllowed), ] self.assert_fsm_transition_does_not_raise_error(test_cases, "approve") @less_console_noise_decorator def test_approved_transition_not_allowed_with_no_investigator(self): """ Tests for attempting to transition without an investigator """ test_cases = [ (self.in_review_domain_request, TransitionNotAllowed), (self.action_needed_domain_request, TransitionNotAllowed), (self.rejected_domain_request, TransitionNotAllowed), ] # Set all investigators to none set_domain_request_investigators(self.all_domain_requests, None) self.assert_fsm_transition_raises_error(test_cases, "approve") @less_console_noise_decorator def test_approved_transition_not_allowed_with_investigator_not_staff(self): """ Tests for attempting to transition with an investigator that is not staff """ test_cases = [ (self.in_review_domain_request, TransitionNotAllowed), (self.action_needed_domain_request, TransitionNotAllowed), (self.rejected_domain_request, TransitionNotAllowed), ] # Set all investigators to a user with no staff privs user, _ = User.objects.get_or_create(username="pancakesyrup", is_staff=False) set_domain_request_investigators(self.all_domain_requests, user) self.assert_fsm_transition_raises_error(test_cases, "approve") @less_console_noise_decorator def test_approved_skips_sending_email(self): """ Test that calling .approve with send_email=False doesn't actually send an email """ with boto3_mocking.clients.handler_for("sesv2", self.mock_client): self.submitted_domain_request.approve(send_email=False) # Assert that no emails were sent self.assertEqual(len(self.mock_client.EMAILS_SENT), 0) @less_console_noise_decorator def test_approved_transition_not_allowed(self): """ Test that calling action_needed against transition rules raises TransitionNotAllowed. """ test_cases = [ (self.started_domain_request, TransitionNotAllowed), (self.approved_domain_request, TransitionNotAllowed), (self.withdrawn_domain_request, TransitionNotAllowed), (self.ineligible_domain_request, TransitionNotAllowed), ] self.assert_fsm_transition_raises_error(test_cases, "approve") @less_console_noise_decorator def test_withdraw_transition_allowed(self): """ Test that calling action_needed from allowable statuses does raises TransitionNotAllowed. """ test_cases = [ (self.submitted_domain_request, TransitionNotAllowed), (self.in_review_domain_request, TransitionNotAllowed), (self.action_needed_domain_request, TransitionNotAllowed), ] self.assert_fsm_transition_does_not_raise_error(test_cases, "withdraw") @less_console_noise_decorator def test_withdraw_transition_allowed_with_no_investigator(self): """ Tests for attempting to transition without an investigator. For withdraw, this should be valid in all cases. """ test_cases = [ (self.submitted_domain_request, TransitionNotAllowed), (self.in_review_domain_request, TransitionNotAllowed), (self.action_needed_domain_request, TransitionNotAllowed), ] # Set all investigators to none set_domain_request_investigators(self.all_domain_requests, None) self.assert_fsm_transition_does_not_raise_error(test_cases, "withdraw") @less_console_noise_decorator def test_withdraw_transition_allowed_with_investigator_not_staff(self): """ Tests for attempting to transition when investigator is not staff. For withdraw, this should be valid in all cases. """ test_cases = [ (self.submitted_domain_request, TransitionNotAllowed), (self.in_review_domain_request, TransitionNotAllowed), (self.action_needed_domain_request, TransitionNotAllowed), ] # Set all investigators to a user with no staff privs user, _ = User.objects.get_or_create(username="pancakesyrup", is_staff=False) set_domain_request_investigators(self.all_domain_requests, user) self.assert_fsm_transition_does_not_raise_error(test_cases, "withdraw") @less_console_noise_decorator def test_withdraw_transition_not_allowed(self): """ Test that calling action_needed against transition rules raises TransitionNotAllowed. """ test_cases = [ (self.started_domain_request, TransitionNotAllowed), (self.approved_domain_request, TransitionNotAllowed), (self.withdrawn_domain_request, TransitionNotAllowed), (self.rejected_domain_request, TransitionNotAllowed), (self.ineligible_domain_request, TransitionNotAllowed), ] self.assert_fsm_transition_raises_error(test_cases, "withdraw") @less_console_noise_decorator def test_reject_transition_allowed(self): """ Test that calling action_needed from allowable statuses does raises TransitionNotAllowed. """ test_cases = [ (self.in_review_domain_request, TransitionNotAllowed), (self.action_needed_domain_request, TransitionNotAllowed), (self.approved_domain_request, TransitionNotAllowed), ] self.assert_fsm_transition_does_not_raise_error(test_cases, "reject") @less_console_noise_decorator def test_reject_transition_not_allowed_with_no_investigator(self): """ Tests for attempting to transition without an investigator """ test_cases = [ (self.in_review_domain_request, TransitionNotAllowed), (self.action_needed_domain_request, TransitionNotAllowed), (self.approved_domain_request, TransitionNotAllowed), ] # Set all investigators to none set_domain_request_investigators(self.all_domain_requests, None) self.assert_fsm_transition_raises_error(test_cases, "reject") @less_console_noise_decorator def test_reject_transition_not_allowed_with_investigator_not_staff(self): """ Tests for attempting to transition when investigator is not staff """ test_cases = [ (self.in_review_domain_request, TransitionNotAllowed), (self.action_needed_domain_request, TransitionNotAllowed), (self.approved_domain_request, TransitionNotAllowed), ] # Set all investigators to a user with no staff privs user, _ = User.objects.get_or_create(username="pancakesyrup", is_staff=False) set_domain_request_investigators(self.all_domain_requests, user) self.assert_fsm_transition_raises_error(test_cases, "reject") @less_console_noise_decorator def test_reject_transition_not_allowed(self): """ Test that calling action_needed against transition rules raises TransitionNotAllowed. """ test_cases = [ (self.started_domain_request, TransitionNotAllowed), (self.submitted_domain_request, TransitionNotAllowed), (self.withdrawn_domain_request, TransitionNotAllowed), (self.rejected_domain_request, TransitionNotAllowed), (self.ineligible_domain_request, TransitionNotAllowed), ] self.assert_fsm_transition_raises_error(test_cases, "reject") @less_console_noise_decorator def test_reject_with_prejudice_transition_allowed(self): """ Test that calling action_needed from allowable statuses does raises TransitionNotAllowed. """ test_cases = [ (self.in_review_domain_request, TransitionNotAllowed), (self.action_needed_domain_request, TransitionNotAllowed), (self.approved_domain_request, TransitionNotAllowed), (self.rejected_domain_request, TransitionNotAllowed), ] self.assert_fsm_transition_does_not_raise_error(test_cases, "reject_with_prejudice") @less_console_noise_decorator def test_reject_with_prejudice_transition_not_allowed_with_no_investigator(self): """ Tests for attempting to transition without an investigator """ test_cases = [ (self.in_review_domain_request, TransitionNotAllowed), (self.action_needed_domain_request, TransitionNotAllowed), (self.approved_domain_request, TransitionNotAllowed), (self.rejected_domain_request, TransitionNotAllowed), ] # Set all investigators to none set_domain_request_investigators(self.all_domain_requests, None) self.assert_fsm_transition_raises_error(test_cases, "reject_with_prejudice") @less_console_noise_decorator def test_reject_with_prejudice_not_allowed_with_investigator_not_staff(self): """ Tests for attempting to transition when investigator is not staff """ test_cases = [ (self.in_review_domain_request, TransitionNotAllowed), (self.action_needed_domain_request, TransitionNotAllowed), (self.approved_domain_request, TransitionNotAllowed), (self.rejected_domain_request, TransitionNotAllowed), ] # Set all investigators to a user with no staff privs user, _ = User.objects.get_or_create(username="pancakesyrup", is_staff=False) set_domain_request_investigators(self.all_domain_requests, user) self.assert_fsm_transition_raises_error(test_cases, "reject_with_prejudice") @less_console_noise_decorator def test_reject_with_prejudice_transition_not_allowed(self): """ Test that calling action_needed against transition rules raises TransitionNotAllowed. """ test_cases = [ (self.started_domain_request, TransitionNotAllowed), (self.submitted_domain_request, TransitionNotAllowed), (self.withdrawn_domain_request, TransitionNotAllowed), (self.ineligible_domain_request, TransitionNotAllowed), ] self.assert_fsm_transition_raises_error(test_cases, "reject_with_prejudice") @less_console_noise_decorator def test_transition_not_allowed_approved_in_review_when_domain_is_active(self): """Create a domain request with status approved, create a matching domain that is active, and call in_review against transition rules""" domain = Domain.objects.create(name=self.approved_domain_request.requested_domain.name) self.approved_domain_request.approved_domain = domain self.approved_domain_request.save() # Define a custom implementation for is_active def custom_is_active(self): return True # Override to return True with boto3_mocking.clients.handler_for("sesv2", self.mock_client): # 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): self.approved_domain_request.in_review() @less_console_noise_decorator def test_transition_not_allowed_approved_action_needed_when_domain_is_active(self): """Create a domain request with status approved, create a matching domain that is active, and call action_needed against transition rules""" domain = Domain.objects.create(name=self.approved_domain_request.requested_domain.name) self.approved_domain_request.approved_domain = domain self.approved_domain_request.save() # Define a custom implementation for is_active def custom_is_active(self): return True # Override to return True with boto3_mocking.clients.handler_for("sesv2", self.mock_client): # 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): self.approved_domain_request.action_needed() @less_console_noise_decorator def test_transition_not_allowed_approved_rejected_when_domain_is_active(self): """Create a domain request with status approved, create a matching domain that is active, and call reject against transition rules""" domain = Domain.objects.create(name=self.approved_domain_request.requested_domain.name) self.approved_domain_request.approved_domain = domain self.approved_domain_request.save() # Define a custom implementation for is_active def custom_is_active(self): return True # Override to return True with boto3_mocking.clients.handler_for("sesv2", self.mock_client): # 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): self.approved_domain_request.reject() @less_console_noise_decorator def test_transition_not_allowed_approved_ineligible_when_domain_is_active(self): """Create a domain request with status approved, create a matching domain that is active, and call reject_with_prejudice against transition rules""" domain = Domain.objects.create(name=self.approved_domain_request.requested_domain.name) self.approved_domain_request.approved_domain = domain self.approved_domain_request.save() # Define a custom implementation for is_active def custom_is_active(self): return True # Override to return True with boto3_mocking.clients.handler_for("sesv2", self.mock_client): # 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): self.approved_domain_request.reject_with_prejudice() @less_console_noise_decorator def test_approve_from_rejected_clears_rejection_reason(self): """When transitioning from rejected to approved on a domain request, the rejection_reason is cleared.""" # Create a sample domain request domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.REJECTED) domain_request.rejection_reason = DomainRequest.RejectionReasons.DOMAIN_PURPOSE # Approve with boto3_mocking.clients.handler_for("sesv2", self.mock_client): domain_request.approve() self.assertEqual(domain_request.status, DomainRequest.DomainRequestStatus.APPROVED) self.assertEqual(domain_request.rejection_reason, None) @less_console_noise_decorator def test_in_review_from_rejected_clears_rejection_reason(self): """When transitioning from rejected to in_review on a domain request, the rejection_reason is cleared.""" # Create a sample domain request domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.REJECTED) domain_request.domain_is_not_active = True domain_request.rejection_reason = DomainRequest.RejectionReasons.DOMAIN_PURPOSE # Approve with boto3_mocking.clients.handler_for("sesv2", self.mock_client): domain_request.in_review() self.assertEqual(domain_request.status, DomainRequest.DomainRequestStatus.IN_REVIEW) self.assertEqual(domain_request.rejection_reason, None) @less_console_noise_decorator def test_action_needed_from_rejected_clears_rejection_reason(self): """When transitioning from rejected to action_needed on a domain request, the rejection_reason is cleared.""" # Create a sample domain request domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.REJECTED) domain_request.domain_is_not_active = True domain_request.rejection_reason = DomainRequest.RejectionReasons.DOMAIN_PURPOSE # Approve with boto3_mocking.clients.handler_for("sesv2", self.mock_client): domain_request.action_needed() self.assertEqual(domain_request.status, DomainRequest.DomainRequestStatus.ACTION_NEEDED) self.assertEqual(domain_request.rejection_reason, None) @less_console_noise_decorator def test_has_rationale_returns_true(self): """has_rationale() returns true when a domain request has no_other_contacts_rationale""" self.started_domain_request.no_other_contacts_rationale = "You talkin' to me?" self.started_domain_request.save() self.assertEquals(self.started_domain_request.has_rationale(), True) @less_console_noise_decorator def test_has_rationale_returns_false(self): """has_rationale() returns false when a domain request has no no_other_contacts_rationale""" self.assertEquals(self.started_domain_request.has_rationale(), False) @less_console_noise_decorator def test_has_other_contacts_returns_true(self): """has_other_contacts() returns true when a domain request has other_contacts""" # completed_domain_request has other contacts by default self.assertEquals(self.started_domain_request.has_other_contacts(), True) @less_console_noise_decorator def test_has_other_contacts_returns_false(self): """has_other_contacts() returns false when a domain request has no other_contacts""" domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.STARTED, name="no-others.gov", has_other_contacts=False ) self.assertEquals(domain_request.has_other_contacts(), False) class TestPermissions(TestCase): """Test the User-Domain-Role connection.""" def setUp(self): super().setUp() self.mock_client = MockSESClient() def tearDown(self): super().tearDown() self.mock_client.EMAILS_SENT.clear() @boto3_mocking.patching @less_console_noise_decorator def test_approval_creates_role(self): draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov") user, _ = User.objects.get_or_create() investigator, _ = User.objects.get_or_create(username="frenchtoast", is_staff=True) domain_request = DomainRequest.objects.create( creator=user, requested_domain=draft_domain, investigator=investigator ) with boto3_mocking.clients.handler_for("sesv2", self.mock_client): # skip using the submit method domain_request.status = DomainRequest.DomainRequestStatus.SUBMITTED domain_request.approve() # should be a role for this user domain = Domain.objects.get(name="igorville.gov") self.assertTrue(UserDomainRole.objects.get(user=user, domain=domain)) class TestDomainInformation(TestCase): """Test the DomainInformation model, when approved or otherwise""" def setUp(self): super().setUp() self.mock_client = MockSESClient() def tearDown(self): super().tearDown() self.mock_client.EMAILS_SENT.clear() Domain.objects.all().delete() DomainInformation.objects.all().delete() DomainRequest.objects.all().delete() User.objects.all().delete() DraftDomain.objects.all().delete() @boto3_mocking.patching @less_console_noise_decorator def test_approval_creates_info(self): draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov") user, _ = User.objects.get_or_create() investigator, _ = User.objects.get_or_create(username="frenchtoast", is_staff=True) domain_request = DomainRequest.objects.create( creator=user, requested_domain=draft_domain, notes="test notes", investigator=investigator ) with boto3_mocking.clients.handler_for("sesv2", self.mock_client): # skip using the submit method domain_request.status = DomainRequest.DomainRequestStatus.SUBMITTED domain_request.approve() # should be an information present for this domain domain = Domain.objects.get(name="igorville.gov") domain_information = DomainInformation.objects.filter(domain=domain) self.assertTrue(domain_information.exists()) # Test that both objects are what we expect current_domain_information = domain_information.get().__dict__ expected_domain_information = DomainInformation( creator=user, domain=domain, notes="test notes", domain_request=domain_request, federal_agency=FederalAgency.objects.get(agency="Non-Federal Agency"), ).__dict__ # Test the two records for consistency self.assertEqual(self.clean_dict(current_domain_information), self.clean_dict(expected_domain_information)) def clean_dict(self, dict_obj): """Cleans dynamic fields in a dictionary""" bad_fields = ["_state", "created_at", "id", "updated_at"] return {k: v for k, v in dict_obj.items() if k not in bad_fields} class TestDomainInvitations(TestCase): """Test the retrieval of domain invitations.""" @less_console_noise_decorator def setUp(self): self.domain, _ = Domain.objects.get_or_create(name="igorville.gov") self.email = "mayor@igorville.gov" self.invitation, _ = DomainInvitation.objects.get_or_create(email=self.email, domain=self.domain) self.user, _ = User.objects.get_or_create(email=self.email) def tearDown(self): super().tearDown() # clean out the roles each time UserDomainRole.objects.all().delete() self.domain.delete() self.invitation.delete() User.objects.all().delete() @less_console_noise_decorator def test_retrieval_creates_role(self): self.invitation.retrieve() self.assertTrue(UserDomainRole.objects.get(user=self.user, domain=self.domain)) @less_console_noise_decorator def test_retrieve_missing_user_error(self): # get rid of matching users User.objects.filter(email=self.email).delete() with self.assertRaises(RuntimeError): self.invitation.retrieve() @less_console_noise_decorator def test_retrieve_existing_role_no_error(self): # make the overlapping role UserDomainRole.objects.get_or_create(user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER) # this is not an error but does produce a console warning self.invitation.retrieve() self.assertEqual(self.invitation.status, DomainInvitation.DomainInvitationStatus.RETRIEVED) @less_console_noise_decorator def test_retrieve_on_each_login(self): """A user's authenticate on_each_login callback retrieves their invitations.""" self.user.on_each_login() self.assertTrue(UserDomainRole.objects.get(user=self.user, domain=self.domain)) class TestPortfolioInvitations(TestCase): """Test the retrieval of portfolio invitations.""" @less_console_noise_decorator def setUp(self): self.email = "mayor@igorville.gov" self.email2 = "creator@igorville.gov" self.user, _ = User.objects.get_or_create(email=self.email) self.user2, _ = User.objects.get_or_create(email=self.email2, username="creator") self.portfolio, _ = Portfolio.objects.get_or_create(creator=self.user2, organization_name="Hotel California") self.portfolio_role_base = UserPortfolioRoleChoices.ORGANIZATION_MEMBER self.portfolio_role_admin = UserPortfolioRoleChoices.ORGANIZATION_ADMIN self.portfolio_permission_1 = UserPortfolioPermissionChoices.VIEW_CREATED_REQUESTS self.portfolio_permission_2 = UserPortfolioPermissionChoices.EDIT_REQUESTS self.invitation, _ = PortfolioInvitation.objects.get_or_create( email=self.email, portfolio=self.portfolio, portfolio_roles=[self.portfolio_role_base, self.portfolio_role_admin], portfolio_additional_permissions=[self.portfolio_permission_1, self.portfolio_permission_2], ) def tearDown(self): super().tearDown() Portfolio.objects.all().delete() PortfolioInvitation.objects.all().delete() User.objects.all().delete() @less_console_noise_decorator def test_retrieval(self): self.assertFalse(self.user.portfolio) self.invitation.retrieve() self.user.refresh_from_db() self.assertEqual(self.user.portfolio.organization_name, "Hotel California") self.assertEqual(self.user.portfolio_roles, [self.portfolio_role_base, self.portfolio_role_admin]) self.assertEqual( self.user.portfolio_additional_permissions, [self.portfolio_permission_1, self.portfolio_permission_2] ) self.assertEqual(self.invitation.status, PortfolioInvitation.PortfolioInvitationStatus.RETRIEVED) @less_console_noise_decorator def test_retrieve_missing_user_error(self): # get rid of matching users User.objects.filter(email=self.email).delete() with self.assertRaises(RuntimeError): self.invitation.retrieve() @less_console_noise_decorator def test_retrieve_user_already_member_error(self): self.assertFalse(self.user.portfolio) portfolio2, _ = Portfolio.objects.get_or_create(creator=self.user2, organization_name="Tokyo Hotel") self.user.portfolio = portfolio2 self.assertEqual(self.user.portfolio.organization_name, "Tokyo Hotel") self.user.save() self.user.check_portfolio_invitations_on_login() self.user.refresh_from_db() self.assertEqual(self.user.portfolio.organization_name, "Tokyo Hotel") self.assertEqual(self.invitation.status, PortfolioInvitation.PortfolioInvitationStatus.INVITED) class TestUser(TestCase): """Test actions that occur on user login, test class method that controls how users get validated.""" @less_console_noise_decorator def setUp(self): self.email = "mayor@igorville.gov" self.domain_name = "igorvilleInTransition.gov" self.domain, _ = Domain.objects.get_or_create(name="igorville.gov") self.user, _ = User.objects.get_or_create(email=self.email) def tearDown(self): super().tearDown() Domain.objects.all().delete() DomainInvitation.objects.all().delete() DomainInformation.objects.all().delete() DomainRequest.objects.all().delete() DraftDomain.objects.all().delete() TransitionDomain.objects.all().delete() Portfolio.objects.all().delete() User.objects.all().delete() UserDomainRole.objects.all().delete() @patch.object(User, "has_edit_suborganization", return_value=True) def test_portfolio_role_summary_admin(self, mock_edit_suborganization): # Test if the user is recognized as an Admin self.assertEqual(self.user.portfolio_role_summary, ["Admin"]) @patch.multiple( User, has_view_all_domains_permission=lambda self: True, has_domain_requests_portfolio_permission=lambda self: True, has_edit_requests=lambda self: True, ) def test_portfolio_role_summary_view_only_admin_and_domain_requestor(self): # Test if the user has both 'View-only admin' and 'Domain requestor' roles self.assertEqual(self.user.portfolio_role_summary, ["View-only admin", "Domain requestor"]) @patch.multiple( User, has_view_all_domains_permission=lambda self: True, has_domain_requests_portfolio_permission=lambda self: True, ) def test_portfolio_role_summary_view_only_admin(self): # Test if the user is recognized as a View-only admin self.assertEqual(self.user.portfolio_role_summary, ["View-only admin"]) @patch.multiple( User, has_base_portfolio_permission=lambda self: True, has_edit_requests=lambda self: True, has_domains_portfolio_permission=lambda self: True, ) def test_portfolio_role_summary_member_domain_requestor_domain_manager(self): # Test if the user has 'Member', 'Domain requestor', and 'Domain manager' roles self.assertEqual(self.user.portfolio_role_summary, ["Domain requestor", "Domain manager"]) @patch.multiple(User, has_base_portfolio_permission=lambda self: True, has_edit_requests=lambda self: True) def test_portfolio_role_summary_member_domain_requestor(self): # Test if the user has 'Member' and 'Domain requestor' roles self.assertEqual(self.user.portfolio_role_summary, ["Domain requestor"]) @patch.multiple( User, has_base_portfolio_permission=lambda self: True, has_domains_portfolio_permission=lambda self: True ) def test_portfolio_role_summary_member_domain_manager(self): # Test if the user has 'Member' and 'Domain manager' roles self.assertEqual(self.user.portfolio_role_summary, ["Domain manager"]) @patch.multiple(User, has_base_portfolio_permission=lambda self: True) def test_portfolio_role_summary_member(self): # Test if the user is recognized as a Member self.assertEqual(self.user.portfolio_role_summary, ["Member"]) def test_portfolio_role_summary_empty(self): # Test if the user has no roles self.assertEqual(self.user.portfolio_role_summary, []) @less_console_noise_decorator def test_check_transition_domains_without_domains_on_login(self): """A user's on_each_login callback does not check transition domains. This test makes sure that in the event a domain does not exist for a given transition domain, both a domain and domain invitation are created.""" self.user.on_each_login() self.assertFalse(Domain.objects.filter(name=self.domain_name).exists()) @less_console_noise_decorator def test_identity_verification_with_domain_manager(self): """A domain manager should return False when tested with class method needs_identity_verification""" UserDomainRole.objects.get_or_create(user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER) self.assertFalse(User.needs_identity_verification(self.user.email, self.user.username)) @less_console_noise_decorator def test_identity_verification_with_transition_user(self): """A user from the Verisign transition should return False when tested with class method needs_identity_verification""" TransitionDomain.objects.get_or_create(username=self.user.email, domain_name=self.domain_name) self.assertFalse(User.needs_identity_verification(self.user.email, self.user.username)) @less_console_noise_decorator def test_identity_verification_with_very_important_person(self): """A Very Important Person should return False when tested with class method needs_identity_verification""" VerifiedByStaff.objects.get_or_create(email=self.user.email) self.assertFalse(User.needs_identity_verification(self.user.email, self.user.username)) @less_console_noise_decorator def test_identity_verification_with_invited_user(self): """An invited user should return False when tested with class method needs_identity_verification""" DomainInvitation.objects.get_or_create(email=self.user.email, domain=self.domain) self.assertFalse(User.needs_identity_verification(self.user.email, self.user.username)) @less_console_noise_decorator def test_identity_verification_with_new_user(self): """A new user who's neither transitioned nor invited should return True when tested with class method needs_identity_verification""" self.assertTrue(User.needs_identity_verification(self.user.email, self.user.username)) @less_console_noise_decorator def test_check_domain_invitations_on_login_caps_email(self): """A DomainInvitation with an email address with capital letters should match a User record whose email address is not in caps""" # create DomainInvitation with CAPS email that matches User email # on a case-insensitive match caps_email = "MAYOR@igorville.gov" # mock the domain invitation save routine with patch("registrar.models.DomainInvitation.save") as save_mock: DomainInvitation.objects.get_or_create(email=caps_email, domain=self.domain) self.user.check_domain_invitations_on_login() # if check_domain_invitations_on_login properly matches exactly one # Domain Invitation, then save routine should be called exactly once save_mock.assert_called_once() @less_console_noise_decorator def test_approved_domains_count(self): """Test that the correct approved domain count is returned for a user""" # with no associated approved domains, expect this to return 0 self.assertEquals(self.user.get_approved_domains_count(), 0) # with one approved domain, expect this to return 1 UserDomainRole.objects.get_or_create(user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER) self.assertEquals(self.user.get_approved_domains_count(), 1) # with one approved domain, expect this to return 1 (domain2 is deleted, so not considered approved) domain2, _ = Domain.objects.get_or_create(name="igorville2.gov", state=Domain.State.DELETED) UserDomainRole.objects.get_or_create(user=self.user, domain=domain2, role=UserDomainRole.Roles.MANAGER) self.assertEquals(self.user.get_approved_domains_count(), 1) # with two approved domains, expect this to return 2 domain3, _ = Domain.objects.get_or_create(name="igorville3.gov", state=Domain.State.DNS_NEEDED) UserDomainRole.objects.get_or_create(user=self.user, domain=domain3, role=UserDomainRole.Roles.MANAGER) self.assertEquals(self.user.get_approved_domains_count(), 2) # with three approved domains, expect this to return 3 domain4, _ = Domain.objects.get_or_create(name="igorville4.gov", state=Domain.State.ON_HOLD) UserDomainRole.objects.get_or_create(user=self.user, domain=domain4, role=UserDomainRole.Roles.MANAGER) self.assertEquals(self.user.get_approved_domains_count(), 3) # with four approved domains, expect this to return 4 domain5, _ = Domain.objects.get_or_create(name="igorville5.gov", state=Domain.State.READY) UserDomainRole.objects.get_or_create(user=self.user, domain=domain5, role=UserDomainRole.Roles.MANAGER) self.assertEquals(self.user.get_approved_domains_count(), 4) @less_console_noise_decorator def test_active_requests_count(self): """Test that the correct active domain requests count is returned for a user""" # with no associated active requests, expect this to return 0 self.assertEquals(self.user.get_active_requests_count(), 0) # with one active request, expect this to return 1 draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville1.gov") DomainRequest.objects.create( creator=self.user, requested_domain=draft_domain, status=DomainRequest.DomainRequestStatus.SUBMITTED ) self.assertEquals(self.user.get_active_requests_count(), 1) # with two active requests, expect this to return 2 draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville2.gov") DomainRequest.objects.create( creator=self.user, requested_domain=draft_domain, status=DomainRequest.DomainRequestStatus.IN_REVIEW ) self.assertEquals(self.user.get_active_requests_count(), 2) # with three active requests, expect this to return 3 draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville3.gov") DomainRequest.objects.create( creator=self.user, requested_domain=draft_domain, status=DomainRequest.DomainRequestStatus.ACTION_NEEDED ) self.assertEquals(self.user.get_active_requests_count(), 3) # with three active requests, expect this to return 3 (STARTED is not considered active) draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville4.gov") DomainRequest.objects.create( creator=self.user, requested_domain=draft_domain, status=DomainRequest.DomainRequestStatus.STARTED ) self.assertEquals(self.user.get_active_requests_count(), 3) @less_console_noise_decorator def test_rejected_requests_count(self): """Test that the correct rejected domain requests count is returned for a user""" # with no associated rejected requests, expect this to return 0 self.assertEquals(self.user.get_rejected_requests_count(), 0) # with one rejected request, expect this to return 1 draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville1.gov") DomainRequest.objects.create( creator=self.user, requested_domain=draft_domain, status=DomainRequest.DomainRequestStatus.REJECTED ) self.assertEquals(self.user.get_rejected_requests_count(), 1) @less_console_noise_decorator def test_ineligible_requests_count(self): """Test that the correct ineligible domain requests count is returned for a user""" # with no associated ineligible requests, expect this to return 0 self.assertEquals(self.user.get_ineligible_requests_count(), 0) # with one ineligible request, expect this to return 1 draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville1.gov") DomainRequest.objects.create( creator=self.user, requested_domain=draft_domain, status=DomainRequest.DomainRequestStatus.INELIGIBLE ) self.assertEquals(self.user.get_ineligible_requests_count(), 1) @less_console_noise_decorator def test_has_contact_info(self): """Test that has_contact_info properly returns""" # test with a user with contact info defined self.assertTrue(self.user.has_contact_info()) # test with a user without contact info defined self.user.title = None self.user.email = None self.user.phone = None self.assertFalse(self.user.has_contact_info()) @less_console_noise_decorator def test_has_portfolio_permission(self): """ 0. Returns False when user does not have a permission 1. Returns False when a user does not have a portfolio 2. Returns True when user has direct permission 3. Returns True when user has permission through a role Note: This tests _get_portfolio_permissions as a side effect """ portfolio, _ = Portfolio.objects.get_or_create(creator=self.user, organization_name="Hotel California") self.user.portfolio_additional_permissions = [UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS] self.user.save() self.user.refresh_from_db() user_can_view_all_domains = self.user.has_domains_portfolio_permission() user_can_view_all_requests = self.user.has_domain_requests_portfolio_permission() self.assertFalse(user_can_view_all_domains) self.assertFalse(user_can_view_all_requests) self.user.portfolio = portfolio self.user.save() self.user.refresh_from_db() user_can_view_all_domains = self.user.has_domains_portfolio_permission() user_can_view_all_requests = self.user.has_domain_requests_portfolio_permission() self.assertTrue(user_can_view_all_domains) self.assertFalse(user_can_view_all_requests) self.user.portfolio_roles = [UserPortfolioRoleChoices.ORGANIZATION_ADMIN] self.user.save() self.user.refresh_from_db() user_can_view_all_domains = self.user.has_domains_portfolio_permission() user_can_view_all_requests = self.user.has_domain_requests_portfolio_permission() self.assertTrue(user_can_view_all_domains) self.assertTrue(user_can_view_all_requests) UserDomainRole.objects.all().get_or_create( user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER ) user_can_view_all_domains = self.user.has_domains_portfolio_permission() user_can_view_all_requests = self.user.has_domain_requests_portfolio_permission() self.assertTrue(user_can_view_all_domains) self.assertTrue(user_can_view_all_requests) Portfolio.objects.all().delete() @less_console_noise_decorator def test_user_with_portfolio_but_no_roles(self): # Create an instance of User with a portfolio but no roles or additional permissions portfolio, _ = Portfolio.objects.get_or_create(creator=self.user, organization_name="Hotel California") self.user.portfolio = portfolio self.user.portfolio_roles = [] # Test if the ValidationError is raised with the correct message with self.assertRaises(ValidationError) as cm: self.user.clean() self.assertEqual( cm.exception.message, "When portfolio is assigned, portfolio roles or additional permissions are required." ) Portfolio.objects.all().delete() @less_console_noise_decorator def test_user_with_portfolio_roles_but_no_portfolio(self): # Create an instance of User with a portfolio role but no portfolio self.user.portfolio = None self.user.portfolio_roles = [UserPortfolioRoleChoices.ORGANIZATION_ADMIN] # Test if the ValidationError is raised with the correct message with self.assertRaises(ValidationError) as cm: self.user.clean() self.assertEqual( cm.exception.message, "When portfolio roles or additional permissions are assigned, portfolio is required." ) class TestContact(TestCase): @less_console_noise_decorator def setUp(self): self.email = "mayor@igorville.gov" self.user, _ = User.objects.get_or_create( email=self.email, first_name="Jeff", last_name="Lebowski", phone="123456789" ) self.contact, _ = Contact.objects.get_or_create( first_name="Jeff", last_name="Lebowski", ) self.contact_as_so, _ = Contact.objects.get_or_create(email="newguy@igorville.gov") self.domain_request = DomainRequest.objects.create(creator=self.user, senior_official=self.contact_as_so) def tearDown(self): super().tearDown() DomainRequest.objects.all().delete() Contact.objects.all().delete() User.objects.all().delete() def test_has_more_than_one_join(self): """Test the Contact model method, has_more_than_one_join""" # test for a contact which is assigned as a senior official on a domain request self.assertFalse(self.contact_as_so.has_more_than_one_join("senior_official")) self.assertTrue(self.contact_as_so.has_more_than_one_join("submitted_domain_requests")) @less_console_noise_decorator def test_has_contact_info(self): """Test that has_contact_info properly returns""" self.contact.title = "Title" # test with a contact with contact info defined self.assertTrue(self.contact.has_contact_info()) # test with a contact without contact info defined self.contact.title = None self.contact.email = None self.contact.phone = None self.assertFalse(self.contact.has_contact_info()) class TestDomainRequestCustomSave(TestCase): """Tests custom save behaviour on the DomainRequest object""" def tearDown(self): DomainRequest.objects.all().delete() super().tearDown() @less_console_noise_decorator def test_create_or_update_organization_type_new_instance(self): """Test create_or_update_organization_type when creating a new instance""" domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.STARTED, name="started.gov", generic_org_type=DomainRequest.OrganizationChoices.CITY, is_election_board=True, ) self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION) @less_console_noise_decorator def test_create_or_update_organization_type_new_instance_federal_does_nothing(self): """Test if create_or_update_organization_type does nothing when creating a new instance for federal""" domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.STARTED, name="started.gov", generic_org_type=DomainRequest.OrganizationChoices.FEDERAL, is_election_board=True, ) self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.FEDERAL) self.assertEqual(domain_request.is_election_board, None) @less_console_noise_decorator def test_create_or_update_organization_type_existing_instance_updates_election_board(self): """Test create_or_update_organization_type for an existing instance.""" domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.STARTED, name="started.gov", generic_org_type=DomainRequest.OrganizationChoices.CITY, is_election_board=False, ) domain_request.is_election_board = True domain_request.save() self.assertEqual(domain_request.is_election_board, True) self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION) # Try reverting the election board value domain_request.is_election_board = False domain_request.save() self.assertEqual(domain_request.is_election_board, False) self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY) @less_console_noise_decorator def test_existing_instance_updates_election_board_to_none(self): """Test create_or_update_organization_type for an existing instance, first to True and then to None. Start our with is_election_board as none to simulate a situation where the request was started, but only completed to the point of filling out the generic_org_type.""" domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.STARTED, name="started.gov", generic_org_type=DomainRequest.OrganizationChoices.CITY, is_election_board=None, ) domain_request.is_election_board = True domain_request.save() self.assertEqual(domain_request.is_election_board, True) self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION) # Try reverting the election board value. domain_request.is_election_board = None domain_request.save() self.assertEqual(domain_request.is_election_board, None) self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY) @less_console_noise_decorator def test_create_or_update_organization_type_existing_instance_updates_generic_org_type(self): """Test create_or_update_organization_type when modifying generic_org_type on an existing instance.""" domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.STARTED, name="started.gov", generic_org_type=DomainRequest.OrganizationChoices.CITY, is_election_board=True, ) domain_request.generic_org_type = DomainRequest.OrganizationChoices.INTERSTATE domain_request.save() # Election board should be None because interstate cannot have an election board. self.assertEqual(domain_request.is_election_board, None) self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.INTERSTATE) # Try changing the org Type to something that CAN have an election board. domain_request_tribal = completed_domain_request( status=DomainRequest.DomainRequestStatus.STARTED, name="startedTribal.gov", generic_org_type=DomainRequest.OrganizationChoices.TRIBAL, is_election_board=True, ) self.assertEqual( domain_request_tribal.organization_type, DomainRequest.OrgChoicesElectionOffice.TRIBAL_ELECTION ) # Change the org type domain_request_tribal.generic_org_type = DomainRequest.OrganizationChoices.STATE_OR_TERRITORY domain_request_tribal.save() self.assertEqual(domain_request_tribal.is_election_board, True) self.assertEqual( domain_request_tribal.organization_type, DomainRequest.OrgChoicesElectionOffice.STATE_OR_TERRITORY_ELECTION ) @less_console_noise_decorator def test_create_or_update_organization_type_no_update(self): """Test create_or_update_organization_type when there are no values to update.""" # Test for when both generic_org_type and organization_type is declared, # and are both non-election board domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.STARTED, name="started.gov", generic_org_type=DomainRequest.OrganizationChoices.CITY, is_election_board=False, ) domain_request.save() self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY) self.assertEqual(domain_request.is_election_board, False) self.assertEqual(domain_request.generic_org_type, DomainRequest.OrganizationChoices.CITY) # Test for when both generic_org_type and organization_type is declared, # and are both election board domain_request_election = completed_domain_request( status=DomainRequest.DomainRequestStatus.STARTED, name="startedElection.gov", generic_org_type=DomainRequest.OrganizationChoices.CITY, is_election_board=True, organization_type=DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION, ) self.assertEqual( domain_request_election.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION ) self.assertEqual(domain_request_election.is_election_board, True) self.assertEqual(domain_request_election.generic_org_type, DomainRequest.OrganizationChoices.CITY) # Modify an unrelated existing value for both, and ensure that everything is still consistent domain_request.city = "Fudge" domain_request_election.city = "Caramel" domain_request.save() domain_request_election.save() self.assertEqual(domain_request.city, "Fudge") self.assertEqual(domain_request_election.city, "Caramel") # Test for non-election self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY) self.assertEqual(domain_request.is_election_board, False) self.assertEqual(domain_request.generic_org_type, DomainRequest.OrganizationChoices.CITY) # Test for election self.assertEqual( domain_request_election.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION ) self.assertEqual(domain_request_election.is_election_board, True) self.assertEqual(domain_request_election.generic_org_type, DomainRequest.OrganizationChoices.CITY) class TestDomainInformationCustomSave(TestCase): """Tests custom save behaviour on the DomainInformation object""" def tearDown(self): DomainInformation.objects.all().delete() DomainRequest.objects.all().delete() Domain.objects.all().delete() super().tearDown() @less_console_noise_decorator def test_create_or_update_organization_type_new_instance(self): """Test create_or_update_organization_type when creating a new instance""" domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.STARTED, name="started.gov", generic_org_type=DomainRequest.OrganizationChoices.CITY, is_election_board=True, ) domain_information = DomainInformation.create_from_da(domain_request) self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION) @less_console_noise_decorator def test_create_or_update_organization_type_new_instance_federal_does_nothing(self): """Test if create_or_update_organization_type does nothing when creating a new instance for federal""" domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.STARTED, name="started.gov", generic_org_type=DomainRequest.OrganizationChoices.FEDERAL, is_election_board=True, ) domain_information = DomainInformation.create_from_da(domain_request) self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.FEDERAL) self.assertEqual(domain_information.is_election_board, None) @less_console_noise_decorator def test_create_or_update_organization_type_existing_instance_updates_election_board(self): """Test create_or_update_organization_type for an existing instance.""" domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.STARTED, name="started.gov", generic_org_type=DomainRequest.OrganizationChoices.CITY, is_election_board=False, ) domain_information = DomainInformation.create_from_da(domain_request) domain_information.is_election_board = True domain_information.save() self.assertEqual(domain_information.is_election_board, True) self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION) # Try reverting the election board value domain_information.is_election_board = False domain_information.save() domain_information.refresh_from_db() self.assertEqual(domain_information.is_election_board, False) self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY) @less_console_noise_decorator def test_existing_instance_update_election_board_to_none(self): """Test create_or_update_organization_type for an existing instance, first to True and then to None. Start our with is_election_board as none to simulate a situation where the request was started, but only completed to the point of filling out the generic_org_type.""" domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.STARTED, name="started.gov", generic_org_type=DomainRequest.OrganizationChoices.CITY, is_election_board=None, ) domain_information = DomainInformation.create_from_da(domain_request) domain_information.is_election_board = True domain_information.save() self.assertEqual(domain_information.is_election_board, True) self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION) # Try reverting the election board value domain_information.is_election_board = None domain_information.save() domain_information.refresh_from_db() self.assertEqual(domain_information.is_election_board, None) self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY) @less_console_noise_decorator def test_create_or_update_organization_type_existing_instance_updates_generic_org_type(self): """Test create_or_update_organization_type when modifying generic_org_type on an existing instance.""" domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.STARTED, name="started.gov", generic_org_type=DomainRequest.OrganizationChoices.CITY, is_election_board=True, ) domain_information = DomainInformation.create_from_da(domain_request) domain_information.generic_org_type = DomainRequest.OrganizationChoices.INTERSTATE domain_information.save() # Election board should be None because interstate cannot have an election board. self.assertEqual(domain_information.is_election_board, None) self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.INTERSTATE) # Try changing the org Type to something that CAN have an election board. domain_request_tribal = completed_domain_request( status=DomainRequest.DomainRequestStatus.STARTED, name="startedTribal.gov", generic_org_type=DomainRequest.OrganizationChoices.TRIBAL, is_election_board=True, ) domain_information_tribal = DomainInformation.create_from_da(domain_request_tribal) self.assertEqual( domain_information_tribal.organization_type, DomainRequest.OrgChoicesElectionOffice.TRIBAL_ELECTION ) # Change the org type domain_information_tribal.generic_org_type = DomainRequest.OrganizationChoices.STATE_OR_TERRITORY domain_information_tribal.save() self.assertEqual(domain_information_tribal.is_election_board, True) self.assertEqual( domain_information_tribal.organization_type, DomainRequest.OrgChoicesElectionOffice.STATE_OR_TERRITORY_ELECTION, ) @less_console_noise_decorator def test_create_or_update_organization_type_no_update(self): """Test create_or_update_organization_type when there are no values to update.""" # Test for when both generic_org_type and organization_type is declared, # and are both non-election board domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.STARTED, name="started.gov", generic_org_type=DomainRequest.OrganizationChoices.CITY, is_election_board=False, ) domain_information = DomainInformation.create_from_da(domain_request) domain_information.save() self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY) self.assertEqual(domain_information.is_election_board, False) self.assertEqual(domain_information.generic_org_type, DomainRequest.OrganizationChoices.CITY) # Test for when both generic_org_type and organization_type is declared, # and are both election board domain_request_election = completed_domain_request( status=DomainRequest.DomainRequestStatus.STARTED, name="startedElection.gov", generic_org_type=DomainRequest.OrganizationChoices.CITY, is_election_board=True, organization_type=DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION, ) domain_information_election = DomainInformation.create_from_da(domain_request_election) self.assertEqual( domain_information_election.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION ) self.assertEqual(domain_information_election.is_election_board, True) self.assertEqual(domain_information_election.generic_org_type, DomainRequest.OrganizationChoices.CITY) # Modify an unrelated existing value for both, and ensure that everything is still consistent domain_information.city = "Fudge" domain_information_election.city = "Caramel" domain_information.save() domain_information_election.save() self.assertEqual(domain_information.city, "Fudge") self.assertEqual(domain_information_election.city, "Caramel") # Test for non-election self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY) self.assertEqual(domain_information.is_election_board, False) self.assertEqual(domain_information.generic_org_type, DomainRequest.OrganizationChoices.CITY) # Test for election self.assertEqual( domain_information_election.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION ) self.assertEqual(domain_information_election.is_election_board, True) self.assertEqual(domain_information_election.generic_org_type, DomainRequest.OrganizationChoices.CITY) class TestDomainRequestIncomplete(TestCase): @classmethod def setUpClass(cls): super().setUpClass() cls.factory = RequestFactory() cls.user = create_test_user() @less_console_noise_decorator def setUp(self): super().setUp() so, _ = Contact.objects.get_or_create( first_name="Meowy", last_name="Meoward", title="Chief Cat", email="meoward@chiefcat.com", phone="(206) 206 2060", ) draft_domain, _ = DraftDomain.objects.get_or_create(name="MeowardMeowardMeoward.gov") you, _ = Contact.objects.get_or_create( first_name="Testy you", last_name="Tester you", title="Admin Tester", email="testy-admin@town.com", phone="(555) 555 5556", ) other, _ = Contact.objects.get_or_create( first_name="Testy2", last_name="Tester2", title="Another Tester", email="testy2@town.com", phone="(555) 555 5557", ) alt, _ = Website.objects.get_or_create(website="MeowardMeowardMeoward1.gov") current, _ = Website.objects.get_or_create(website="MeowardMeowardMeoward.com") self.amtrak, _ = FederalAgency.objects.get_or_create(agency="AMTRAK") self.domain_request = DomainRequest.objects.create( generic_org_type=DomainRequest.OrganizationChoices.FEDERAL, federal_type="executive", federal_agency=FederalAgency.objects.get(agency="AMTRAK"), about_your_organization="Some description", is_election_board=True, tribe_name="Some tribe name", organization_name="Some organization", address_line1="address 1", state_territory="CA", zipcode="94044", senior_official=so, requested_domain=draft_domain, purpose="Some purpose", submitter=you, no_other_contacts_rationale=None, has_cisa_representative=True, cisa_representative_email="somerep@cisa.com", has_anything_else_text=True, anything_else="Anything else", is_policy_acknowledged=True, creator=self.user, ) self.domain_request.other_contacts.add(other) self.domain_request.current_websites.add(current) self.domain_request.alternative_domains.add(alt) def tearDown(self): super().tearDown() DomainRequest.objects.all().delete() Contact.objects.all().delete() self.amtrak.delete() @classmethod def tearDownClass(cls): super().tearDownClass() cls.user.delete() @less_console_noise_decorator def test_is_federal_complete(self): self.assertTrue(self.domain_request._is_federal_complete()) self.domain_request.federal_type = None self.domain_request.save() self.assertFalse(self.domain_request._is_federal_complete()) @less_console_noise_decorator def test_is_interstate_complete(self): self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.INTERSTATE self.domain_request.about_your_organization = "Something something about your organization" self.domain_request.save() self.assertTrue(self.domain_request._is_interstate_complete()) self.domain_request.about_your_organization = None self.domain_request.save() self.assertFalse(self.domain_request._is_interstate_complete()) @less_console_noise_decorator def test_is_state_or_territory_complete(self): self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.STATE_OR_TERRITORY self.domain_request.is_election_board = True self.domain_request.save() self.assertTrue(self.domain_request._is_state_or_territory_complete()) self.domain_request.is_election_board = None self.domain_request.save() self.assertFalse(self.domain_request._is_state_or_territory_complete()) @less_console_noise_decorator def test_is_tribal_complete(self): self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.TRIBAL self.domain_request.tribe_name = "Tribe Name" self.domain_request.is_election_board = False self.domain_request.save() self.assertTrue(self.domain_request._is_tribal_complete()) self.domain_request.is_election_board = None self.domain_request.save() self.assertFalse(self.domain_request._is_tribal_complete()) self.domain_request.tribe_name = None self.domain_request.save() self.assertFalse(self.domain_request._is_tribal_complete()) @less_console_noise_decorator def test_is_county_complete(self): self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.COUNTY self.domain_request.is_election_board = False self.domain_request.save() self.assertTrue(self.domain_request._is_county_complete()) self.domain_request.is_election_board = None self.domain_request.save() self.assertFalse(self.domain_request._is_county_complete()) @less_console_noise_decorator def test_is_city_complete(self): self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.CITY self.domain_request.is_election_board = False self.domain_request.save() self.assertTrue(self.domain_request._is_city_complete()) self.domain_request.is_election_board = None self.domain_request.save() self.assertFalse(self.domain_request._is_city_complete()) @less_console_noise_decorator def test_is_special_district_complete(self): self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.SPECIAL_DISTRICT self.domain_request.about_your_organization = "Something something about your organization" self.domain_request.is_election_board = False self.domain_request.save() self.assertTrue(self.domain_request._is_special_district_complete()) self.domain_request.is_election_board = None self.domain_request.save() self.assertFalse(self.domain_request._is_special_district_complete()) self.domain_request.about_your_organization = None self.domain_request.save() self.assertFalse(self.domain_request._is_special_district_complete()) @less_console_noise_decorator def test_is_organization_name_and_address_complete(self): self.assertTrue(self.domain_request._is_organization_name_and_address_complete()) self.domain_request.organization_name = None self.domain_request.address_line1 = None self.domain_request.save() self.assertTrue(self.domain_request._is_organization_name_and_address_complete()) @less_console_noise_decorator def test_is_senior_official_complete(self): self.assertTrue(self.domain_request._is_senior_official_complete()) self.domain_request.senior_official = None self.domain_request.save() self.assertFalse(self.domain_request._is_senior_official_complete()) @less_console_noise_decorator def test_is_requested_domain_complete(self): self.assertTrue(self.domain_request._is_requested_domain_complete()) self.domain_request.requested_domain = None self.domain_request.save() self.assertFalse(self.domain_request._is_requested_domain_complete()) @less_console_noise_decorator def test_is_purpose_complete(self): self.assertTrue(self.domain_request._is_purpose_complete()) self.domain_request.purpose = None self.domain_request.save() self.assertFalse(self.domain_request._is_purpose_complete()) @less_console_noise_decorator def test_is_submitter_complete(self): self.assertTrue(self.domain_request._is_submitter_complete()) self.domain_request.submitter = None self.domain_request.save() self.assertFalse(self.domain_request._is_submitter_complete()) @less_console_noise_decorator def test_is_other_contacts_complete_missing_one_field(self): self.assertTrue(self.domain_request._is_other_contacts_complete()) contact = self.domain_request.other_contacts.first() contact.first_name = None contact.save() self.assertFalse(self.domain_request._is_other_contacts_complete()) @less_console_noise_decorator def test_is_other_contacts_complete_all_none(self): self.domain_request.other_contacts.clear() self.assertFalse(self.domain_request._is_other_contacts_complete()) @less_console_noise_decorator def test_is_other_contacts_False_and_has_rationale(self): # Click radio button "No" for no other contacts and give rationale self.domain_request.other_contacts.clear() self.domain_request.other_contacts.exists = False self.domain_request.no_other_contacts_rationale = "Some rationale" self.assertTrue(self.domain_request._is_other_contacts_complete()) @less_console_noise_decorator def test_is_other_contacts_False_and_NO_rationale(self): # Click radio button "No" for no other contacts and DONT give rationale self.domain_request.other_contacts.clear() self.domain_request.other_contacts.exists = False self.domain_request.no_other_contacts_rationale = None self.assertFalse(self.domain_request._is_other_contacts_complete()) @less_console_noise_decorator def test_is_additional_details_complete(self): test_cases = [ # CISA Rep - Yes # Firstname - Yes # Lastname - Yes # Email - Yes # Anything Else Radio - Yes # Anything Else Text - Yes { "has_cisa_representative": True, "cisa_representative_first_name": "cisa-first-name", "cisa_representative_last_name": "cisa-last-name", "cisa_representative_email": "some@cisarepemail.com", "has_anything_else_text": True, "anything_else": "Some text", "expected": True, }, # CISA Rep - Yes # Firstname - Yes # Lastname - Yes # Email - Yes # Anything Else Radio - Yes # Anything Else Text - None { "has_cisa_representative": True, "cisa_representative_first_name": "cisa-first-name", "cisa_representative_last_name": "cisa-last-name", "cisa_representative_email": "some@cisarepemail.com", "has_anything_else_text": True, "anything_else": None, "expected": True, }, # CISA Rep - Yes # Firstname - Yes # Lastname - Yes # Email - None >> e-mail is optional so it should not change anything setting this to None # Anything Else Radio - No # Anything Else Text - No { "has_cisa_representative": True, "cisa_representative_first_name": "cisa-first-name", "cisa_representative_last_name": "cisa-last-name", "cisa_representative_email": None, "has_anything_else_text": False, "anything_else": None, "expected": True, }, # CISA Rep - Yes # Firstname - Yes # Lastname - Yes # Email - None # Anything Else Radio - None # Anything Else Text - None { "has_cisa_representative": True, "cisa_representative_first_name": "cisa-first-name", "cisa_representative_last_name": "cisa-last-name", "cisa_representative_email": None, "has_anything_else_text": None, "anything_else": None, "expected": False, }, # CISA Rep - Yes # Firstname - None # Lastname - None # Email - None # Anything Else Radio - None # Anything Else Text - None { "has_cisa_representative": True, "cisa_representative_first_name": None, "cisa_representative_last_name": None, "cisa_representative_email": None, "has_anything_else_text": None, "anything_else": None, "expected": False, }, # CISA Rep - Yes # Firstname - None # Lastname - None # Email - None # Anything Else Radio - No # Anything Else Text - No # sync_yes_no will override has_cisa_representative to be False if cisa_representative_first_name is None # therefore, our expected will be True { "has_cisa_representative": True, # Above will be overridden to False if cisa_representative_first_name is None "cisa_representative_first_name": None, "cisa_representative_last_name": None, "cisa_representative_email": None, "has_anything_else_text": False, "anything_else": None, "expected": True, }, # CISA Rep - Yes # Firstname - None # Lastname - None # Email - None # Anything Else Radio - Yes # Anything Else Text - None # NOTE: We should never have an instance where only firstname or only lastname are populated # (they are both required) { "has_cisa_representative": True, # Above will be overridden to False if cisa_representative_first_name is None or # cisa_representative_last_name is None bc of sync_yes_no_form_fields "cisa_representative_first_name": None, "cisa_representative_last_name": None, "cisa_representative_email": None, "has_anything_else_text": True, "anything_else": None, "expected": True, }, # CISA Rep - Yes # Firstname - None # Lastname - None # Email - None # Anything Else Radio - Yes # Anything Else Text - Yes { "has_cisa_representative": True, # Above will be overridden to False if cisa_representative_first_name is None or # cisa_representative_last_name is None bc of sync_yes_no_form_fields "cisa_representative_first_name": None, "cisa_representative_last_name": None, "cisa_representative_email": None, "has_anything_else_text": True, "anything_else": "Some text", "expected": True, }, # CISA Rep - No # Anything Else Radio - Yes # Anything Else Text - Yes { "has_cisa_representative": False, "cisa_representative_first_name": None, "cisa_representative_last_name": None, "cisa_representative_email": None, "has_anything_else_text": True, "anything_else": "Some text", "expected": True, }, # CISA Rep - No # Anything Else Radio - Yes # Anything Else Text - None { "has_cisa_representative": False, "cisa_representative_first_name": None, "cisa_representative_last_name": None, "cisa_representative_email": None, "has_anything_else_text": True, "anything_else": None, "expected": True, }, # CISA Rep - No # Anything Else Radio - None # Anything Else Text - None { "has_cisa_representative": False, "cisa_representative_first_name": None, "cisa_representative_last_name": None, "cisa_representative_email": None, "has_anything_else_text": None, "anything_else": None, # Above is both None, so it does NOT get overwritten "expected": False, }, # CISA Rep - No # Anything Else Radio - No # Anything Else Text - No { "has_cisa_representative": False, "cisa_representative_first_name": None, "cisa_representative_last_name": None, "cisa_representative_email": None, "has_anything_else_text": False, "anything_else": None, "expected": True, }, # CISA Rep - None # Anything Else Radio - None { "has_cisa_representative": None, "cisa_representative_first_name": None, "cisa_representative_last_name": None, "cisa_representative_email": None, "has_anything_else_text": None, "anything_else": None, "expected": False, }, ] for case in test_cases: with self.subTest(case=case): self.domain_request.has_cisa_representative = case["has_cisa_representative"] self.domain_request.cisa_representative_email = case["cisa_representative_email"] self.domain_request.has_anything_else_text = case["has_anything_else_text"] self.domain_request.anything_else = case["anything_else"] self.domain_request.save() self.domain_request.refresh_from_db() self.assertEqual( self.domain_request._is_additional_details_complete(), case["expected"], msg=f"Failed for case: {case}", ) @less_console_noise_decorator def test_is_policy_acknowledgement_complete(self): self.assertTrue(self.domain_request._is_policy_acknowledgement_complete()) self.domain_request.is_policy_acknowledged = False self.assertTrue(self.domain_request._is_policy_acknowledgement_complete()) self.domain_request.is_policy_acknowledged = None self.assertFalse(self.domain_request._is_policy_acknowledgement_complete()) @less_console_noise_decorator def test_form_complete(self): request = self.factory.get("/") request.user = self.user self.assertTrue(self.domain_request._form_complete(request)) self.domain_request.generic_org_type = None self.domain_request.save() self.assertFalse(self.domain_request._form_complete(request)) class TestPortfolio(TestCase): def setUp(self): self.user, _ = User.objects.get_or_create( username="intern@igorville.com", email="intern@igorville.com", first_name="Lava", last_name="World" ) super().setUp() def tearDown(self): super().tearDown() Portfolio.objects.all().delete() User.objects.all().delete() def test_urbanization_field_resets_when_not_puetro_rico(self): """The urbanization field should only be populated when the state is puetro rico. Otherwise, this field should be empty.""" # Start out as PR, then change the field portfolio = Portfolio.objects.create( creator=self.user, organization_name="Test Portfolio", state_territory=DomainRequest.StateTerritoryChoices.PUERTO_RICO, urbanization="test", ) self.assertEqual(portfolio.urbanization, "test") self.assertEqual(portfolio.state_territory, DomainRequest.StateTerritoryChoices.PUERTO_RICO) portfolio.state_territory = DomainRequest.StateTerritoryChoices.ALABAMA portfolio.save() self.assertEqual(portfolio.urbanization, None) self.assertEqual(portfolio.state_territory, DomainRequest.StateTerritoryChoices.ALABAMA) def test_can_add_urbanization_field(self): """Ensures that you can populate the urbanization field when conditions are right""" # Create a portfolio that cannot have this field portfolio = Portfolio.objects.create( creator=self.user, organization_name="Test Portfolio", state_territory=DomainRequest.StateTerritoryChoices.ALABAMA, urbanization="test", ) # Implicitly check if this gets cleared on create. It should. self.assertEqual(portfolio.urbanization, None) self.assertEqual(portfolio.state_territory, DomainRequest.StateTerritoryChoices.ALABAMA) portfolio.state_territory = DomainRequest.StateTerritoryChoices.PUERTO_RICO portfolio.urbanization = "test123" portfolio.save() self.assertEqual(portfolio.urbanization, "test123") self.assertEqual(portfolio.state_territory, DomainRequest.StateTerritoryChoices.PUERTO_RICO)