diff --git a/src/api/tests/test_available.py b/src/api/tests/test_available.py index 66f7de13f..fa9dadcd4 100644 --- a/src/api/tests/test_available.py +++ b/src/api/tests/test_available.py @@ -69,8 +69,8 @@ class AvailableViewTest(MockEppLib): self.assertTrue(check_domain_available("igorville.gov")) # input is lowercased so GSA.GOV should also not be available self.assertFalse(check_domain_available("GSA.gov")) - # input is lowercased so IGORVILLE.GOV should also not be available - self.assertFalse(check_domain_available("IGORVILLE.gov")) + # input is lowercased so IGORVILLE.GOV should also be available + self.assertTrue(check_domain_available("IGORVILLE.gov")) def test_domain_available_dotgov(self): """Domain searches work without trailing .gov""" diff --git a/src/api/views.py b/src/api/views.py index 700a8b1d5..068844919 100644 --- a/src/api/views.py +++ b/src/api/views.py @@ -32,7 +32,9 @@ DOMAIN_API_MESSAGES = { "Read more about choosing your .gov domain.".format(public_site_url("domains/choosing")) ), "invalid": "Enter a domain using only letters, numbers, or hyphens (though we don't recommend using hyphens).", - "success": "That domain is available!", + "success": "That domain is available! We’ll try to give you the domain you want, \ + but it's not guaranteed. After you complete this form, we’ll \ + evaluate whether your request meets our requirements.", "error": GenericError.get_error_message(GenericErrorCodes.CANNOT_CONTACT_REGISTRY), } diff --git a/src/djangooidc/oidc.py b/src/djangooidc/oidc.py index b2b1acd8e..91bfddc66 100644 --- a/src/djangooidc/oidc.py +++ b/src/djangooidc/oidc.py @@ -100,7 +100,9 @@ class Client(oic.Client): "state": session["state"], "nonce": session["nonce"], "redirect_uri": self.registration_response["redirect_uris"][0], - "acr_values": self.behaviour.get("acr_value"), + # acr_value may be passed in session if overriding, as in the case + # of step up auth, otherwise get from settings.py + "acr_values": session.get("acr_value") or self.behaviour.get("acr_value"), } if extra_args is not None: @@ -162,7 +164,6 @@ class Client(oic.Client): logger.error(err) logger.error("Unable to parse response for %s" % state) raise o_e.AuthenticationFailed(locator=state) - # ErrorResponse is not raised, it is passed back... if isinstance(authn_response, ErrorResponse): error = authn_response.get("error", "") @@ -207,7 +208,6 @@ class Client(oic.Client): logger.error(err) logger.error("Unable to request user info for %s" % state) raise o_e.AuthenticationFailed(locator=state) - # ErrorResponse is not raised, it is passed back... if isinstance(info_response, ErrorResponse): logger.error("Unable to get user info (%s) for %s" % (info_response.get("error", ""), state)) @@ -272,6 +272,11 @@ class Client(oic.Client): super(Client, self).store_response(resp, info) + def get_step_up_acr_value(self): + """returns the step_up_acr_value from settings + this helper function is called from djangooidc views""" + return self.behaviour.get("step_up_acr_value") + def __repr__(self): return "Client {} {} {}".format( self.client_id, diff --git a/src/djangooidc/tests/test_oidc.py b/src/djangooidc/tests/test_oidc.py new file mode 100644 index 000000000..21249aa90 --- /dev/null +++ b/src/djangooidc/tests/test_oidc.py @@ -0,0 +1,30 @@ +import logging + +from django.test import TestCase + +from django.conf import settings + +from djangooidc.oidc import Client + +logger = logging.getLogger(__name__) + + +class OidcTest(TestCase): + def test_oidc_create_authn_request_with_acr_value(self): + """Test that create_authn_request returns a redirect with an acr_value + when an acr_value is passed through session. + + This test is only valid locally. On local, client can be initialized. + Client initialization does not work in pipeline, so test is useless in + pipeline. However, it will not fail in pipeline.""" + try: + # Initialize provider using pyOICD + OP = getattr(settings, "OIDC_ACTIVE_PROVIDER") + CLIENT = Client(OP) + session = {"acr_value": "some_acr_value_maybe_ial2"} + response = CLIENT.create_authn_request(session) + self.assertEqual(response.status_code, 302) + self.assertIn("some_acr_value_maybe_ial2", response.url) + except Exception as err: + logger.warning(err) + logger.warning("Unable to configure OpenID Connect provider in pipeline. Cannot execute this test.") diff --git a/src/djangooidc/tests/test_views.py b/src/djangooidc/tests/test_views.py index 5ff36a77c..da12f4fd5 100644 --- a/src/djangooidc/tests/test_views.py +++ b/src/djangooidc/tests/test_views.py @@ -1,8 +1,9 @@ -from unittest.mock import patch +from unittest.mock import MagicMock, patch from django.http import HttpResponse -from django.test import Client, TestCase +from django.test import Client, TestCase, RequestFactory from django.urls import reverse +from ..views import login_callback from .common import less_console_noise @@ -11,6 +12,7 @@ from .common import less_console_noise class ViewsTest(TestCase): def setUp(self): self.client = Client() + self.factory = RequestFactory() def say_hi(*args): return HttpResponse("Hi") @@ -59,19 +61,83 @@ class ViewsTest(TestCase): # mock mock_client.callback.side_effect = self.user_info # test - with less_console_noise(): + with patch("djangooidc.views.requires_step_up_auth", return_value=False), less_console_noise(): response = self.client.get(reverse("openid_login_callback")) # assert self.assertEqual(response.status_code, 302) self.assertEqual(response.url, reverse("logout")) + def test_login_callback_no_step_up_auth(self, mock_client): + """Walk through login_callback when requires_step_up_auth returns False + and assert that we have a redirect to /""" + # setup + session = self.client.session + session.save() + # mock + mock_client.callback.side_effect = self.user_info + # test + with patch("djangooidc.views.requires_step_up_auth", return_value=False), less_console_noise(): + response = self.client.get(reverse("openid_login_callback")) + # assert + self.assertEqual(response.status_code, 302) + self.assertEqual(response.url, "/") + + def test_requires_step_up_auth(self, mock_client): + """Invoke login_callback passing it a request when requires_step_up_auth returns True + and assert that session is updated and create_authn_request (mock) is called.""" + # Configure the mock to return an expected value for get_step_up_acr_value + mock_client.return_value.get_step_up_acr_value.return_value = "step_up_acr_value" + + # Create a mock request + request = self.factory.get("/some-url") + request.session = {"acr_value": ""} + + # Ensure that the CLIENT instance used in login_callback is the mock + # patch requires_step_up_auth to return True + with patch("djangooidc.views.requires_step_up_auth", return_value=True), patch( + "djangooidc.views.CLIENT.create_authn_request", return_value=MagicMock() + ) as mock_create_authn_request: + login_callback(request) + + # create_authn_request only gets called when requires_step_up_auth is True + # and it changes this acr_value in request.session + + # Assert that acr_value is no longer empty string + self.assertNotEqual(request.session["acr_value"], "") + # And create_authn_request was called again + mock_create_authn_request.assert_called_once() + + def test_does_not_requires_step_up_auth(self, mock_client): + """Invoke login_callback passing it a request when requires_step_up_auth returns False + and assert that session is not updated and create_authn_request (mock) is not called. + + Possibly redundant with test_login_callback_requires_step_up_auth""" + # Create a mock request + request = self.factory.get("/some-url") + request.session = {"acr_value": ""} + + # Ensure that the CLIENT instance used in login_callback is the mock + # patch requires_step_up_auth to return False + with patch("djangooidc.views.requires_step_up_auth", return_value=False), patch( + "djangooidc.views.CLIENT.create_authn_request", return_value=MagicMock() + ) as mock_create_authn_request: + login_callback(request) + + # create_authn_request only gets called when requires_step_up_auth is True + # and it changes this acr_value in request.session + + # Assert that acr_value is NOT updated by testing that it is still an empty string + self.assertEqual(request.session["acr_value"], "") + # Assert create_authn_request was not called + mock_create_authn_request.assert_not_called() + @patch("djangooidc.views.authenticate") def test_login_callback_raises(self, mock_auth, mock_client): # mock mock_client.callback.side_effect = self.user_info mock_auth.return_value = None # test - with less_console_noise(): + with patch("djangooidc.views.requires_step_up_auth", return_value=False), less_console_noise(): response = self.client.get(reverse("openid_login_callback")) # assert self.assertEqual(response.status_code, 401) diff --git a/src/djangooidc/views.py b/src/djangooidc/views.py index ea893daf2..b5905df48 100644 --- a/src/djangooidc/views.py +++ b/src/djangooidc/views.py @@ -11,7 +11,7 @@ from urllib.parse import parse_qs, urlencode from djangooidc.oidc import Client from djangooidc import exceptions as o_e - +from registrar.models import User logger = logging.getLogger(__name__) @@ -68,6 +68,12 @@ def login_callback(request): try: query = parse_qs(request.GET.urlencode()) userinfo = CLIENT.callback(query, request.session) + # test for need for identity verification and if it is satisfied + # if not satisfied, redirect user to login with stepped up acr_value + if requires_step_up_auth(userinfo): + # add acr_value to request.session + request.session["acr_value"] = CLIENT.get_step_up_acr_value() + return CLIENT.create_authn_request(request.session) user = authenticate(request=request, **userinfo) if user: login(request, user) @@ -79,10 +85,27 @@ def login_callback(request): return error_page(request, err) +def requires_step_up_auth(userinfo): + """if User.needs_identity_verification and step_up_acr_value not in + ial returned from callback, return True""" + step_up_acr_value = CLIENT.get_step_up_acr_value() + acr_value = userinfo.get("ial", "") + uuid = userinfo.get("sub", "") + email = userinfo.get("email", "") + if acr_value != step_up_acr_value: + # The acr of this attempt is not at the highest level + # so check if the user needs the higher level + return User.needs_identity_verification(email, uuid) + else: + # This attempt already came back at the highest level + # so does not require step up + return False + + def logout(request, next_page=None): """Redirect the user to the authentication provider (OP) logout page.""" try: - username = request.user.username + user = request.user request_args = { "client_id": CLIENT.client_id, "state": request.session["state"], @@ -94,7 +117,6 @@ def logout(request, next_page=None): request_args.update( {"post_logout_redirect_uri": CLIENT.registration_response["post_logout_redirect_uris"][0]} ) - url = CLIENT.provider_info["end_session_endpoint"] url += "?" + urlencode(request_args) return HttpResponseRedirect(url) @@ -104,7 +126,7 @@ def logout(request, next_page=None): # Always remove Django session stuff - even if not logged out from OP. # Don't wait for the callback as it may never come. auth_logout(request) - logger.info("Successfully logged out user %s" % username) + logger.info("Successfully logged out user %s" % user) next_page = getattr(settings, "LOGOUT_REDIRECT_URL", None) if next_page: request.session["next"] = next_page diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index cfb41f4ea..9ed437aef 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -17,7 +17,7 @@ except ImportError: from django.conf import settings from .cert import Cert, Key -from .errors import LoginError, RegistryError +from .errors import ErrorCode, LoginError, RegistryError from .socket import Socket from .utility.pool import EPPConnectionPool @@ -115,7 +115,7 @@ class EPPLibWrapper: except TransportError as err: message = f"{cmd_type} failed to execute due to a connection error." logger.error(f"{message} Error: {err}", exc_info=True) - raise RegistryError(message) from err + raise RegistryError(message, code=ErrorCode.TRANSPORT_ERROR) from err except LoginError as err: # For linter due to it not liking this line length text = "failed to execute due to a registry login error." @@ -163,7 +163,8 @@ class EPPLibWrapper: try: return self._send(command) except RegistryError as err: - if err.should_retry() and counter < 3: + if counter < 3 and (err.should_retry() or err.is_transport_error()): + logger.info(f"Retrying transport error. Attempt #{counter+1} of 3.") counter += 1 sleep((counter * 50) / 1000) # sleep 50 ms to 150 ms else: # don't try again diff --git a/src/epplibwrapper/errors.py b/src/epplibwrapper/errors.py index d34ed5e91..2b7bdd255 100644 --- a/src/epplibwrapper/errors.py +++ b/src/epplibwrapper/errors.py @@ -4,13 +4,15 @@ from enum import IntEnum class ErrorCode(IntEnum): """ Overview of registry response codes from RFC 5730. See RFC 5730 for full text. - + - 0 System connection error - 1000 - 1500 Success - 2000 - 2308 Registrar did something silly - 2400 - 2500 Registry did something silly - 2501 - 2502 Something malicious or abusive may have occurred """ + TRANSPORT_ERROR = 0 + COMMAND_COMPLETED_SUCCESSFULLY = 1000 COMMAND_COMPLETED_SUCCESSFULLY_ACTION_PENDING = 1001 COMMAND_COMPLETED_SUCCESSFULLY_NO_MESSAGES = 1300 @@ -67,6 +69,9 @@ class RegistryError(Exception): def should_retry(self): return self.code == ErrorCode.COMMAND_FAILED + def is_transport_error(self): + return self.code == ErrorCode.TRANSPORT_ERROR + # connection errors have error code of None and [Errno 99] in the err message def is_connection_error(self): return self.code is None diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 429bd762f..518c67869 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -527,14 +527,14 @@ class DomainApplicationAdminForm(forms.ModelForm): current_state = application.status # first option in status transitions is current state - available_transitions = [(current_state, current_state)] + available_transitions = [(current_state, application.get_status_display())] transitions = get_available_FIELD_transitions( application, models.DomainApplication._meta.get_field("status") ) for transition in transitions: - available_transitions.append((transition.target, transition.target)) + available_transitions.append((transition.target, transition.target.label)) # only set the available transitions if the user is not restricted # from editing the domain application; otherwise, the form will be @@ -650,10 +650,10 @@ class DomainApplicationAdmin(ListHeaderAdmin): if ( obj - and original_obj.status == models.DomainApplication.APPROVED + and original_obj.status == models.DomainApplication.ApplicationStatus.APPROVED and ( - obj.status == models.DomainApplication.REJECTED - or obj.status == models.DomainApplication.INELIGIBLE + obj.status == models.DomainApplication.ApplicationStatus.REJECTED + or obj.status == models.DomainApplication.ApplicationStatus.INELIGIBLE ) and not obj.domain_is_not_active() ): @@ -675,14 +675,14 @@ class DomainApplicationAdmin(ListHeaderAdmin): else: if obj.status != original_obj.status: status_method_mapping = { - models.DomainApplication.STARTED: None, - models.DomainApplication.SUBMITTED: obj.submit, - models.DomainApplication.IN_REVIEW: obj.in_review, - models.DomainApplication.ACTION_NEEDED: obj.action_needed, - models.DomainApplication.APPROVED: obj.approve, - models.DomainApplication.WITHDRAWN: obj.withdraw, - models.DomainApplication.REJECTED: obj.reject, - models.DomainApplication.INELIGIBLE: (obj.reject_with_prejudice), + models.DomainApplication.ApplicationStatus.STARTED: None, + models.DomainApplication.ApplicationStatus.SUBMITTED: obj.submit, + models.DomainApplication.ApplicationStatus.IN_REVIEW: obj.in_review, + models.DomainApplication.ApplicationStatus.ACTION_NEEDED: obj.action_needed, + models.DomainApplication.ApplicationStatus.APPROVED: obj.approve, + models.DomainApplication.ApplicationStatus.WITHDRAWN: obj.withdraw, + models.DomainApplication.ApplicationStatus.REJECTED: obj.reject, + models.DomainApplication.ApplicationStatus.INELIGIBLE: (obj.reject_with_prejudice), } selected_method = status_method_mapping.get(obj.status) if selected_method is None: diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index 9bdcd4e98..c99daf72b 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -541,6 +541,7 @@ OIDC_PROVIDERS = { "scope": ["email", "profile:name", "phone"], "user_info_request": ["email", "first_name", "last_name", "phone"], "acr_value": "http://idmanagement.gov/ns/assurance/ial/1", + "step_up_acr_value": "http://idmanagement.gov/ns/assurance/ial/2", }, "client_registration": { "client_id": "cisa_dotgov_registrar", @@ -558,6 +559,7 @@ OIDC_PROVIDERS = { "scope": ["email", "profile:name", "phone"], "user_info_request": ["email", "first_name", "last_name", "phone"], "acr_value": "http://idmanagement.gov/ns/assurance/ial/1", + "step_up_acr_value": "http://idmanagement.gov/ns/assurance/ial/2", }, "client_registration": { "client_id": ("urn:gov:cisa:openidconnect.profiles:sp:sso:cisa:dotgov_registrar"), diff --git a/src/registrar/fixtures_applications.py b/src/registrar/fixtures_applications.py index aea153aef..ad3ae0820 100644 --- a/src/registrar/fixtures_applications.py +++ b/src/registrar/fixtures_applications.py @@ -49,28 +49,28 @@ class DomainApplicationFixture: # }, DA = [ { - "status": "started", - "organization_name": "Example - Finished but not Submitted", + "status": DomainApplication.ApplicationStatus.STARTED, + "organization_name": "Example - Finished but not submitted", }, { - "status": "submitted", - "organization_name": "Example - Submitted but pending Investigation", + "status": DomainApplication.ApplicationStatus.SUBMITTED, + "organization_name": "Example - Submitted but pending investigation", }, { - "status": "in review", - "organization_name": "Example - In Investigation", + "status": DomainApplication.ApplicationStatus.IN_REVIEW, + "organization_name": "Example - In investigation", }, { - "status": "in review", + "status": DomainApplication.ApplicationStatus.IN_REVIEW, "organization_name": "Example - Approved", }, { - "status": "withdrawn", + "status": DomainApplication.ApplicationStatus.WITHDRAWN, "organization_name": "Example - Withdrawn", }, { - "status": "action needed", - "organization_name": "Example - Action Needed", + "status": DomainApplication.ApplicationStatus.ACTION_NEEDED, + "organization_name": "Example - Action needed", }, { "status": "rejected", @@ -214,7 +214,9 @@ class DomainFixture(DomainApplicationFixture): for user in users: # approve one of each users in review status domains - application = DomainApplication.objects.filter(creator=user, status=DomainApplication.IN_REVIEW).last() + application = DomainApplication.objects.filter( + creator=user, status=DomainApplication.ApplicationStatus.IN_REVIEW + ).last() logger.debug(f"Approving {application} for {user}") application.approve() application.save() diff --git a/src/registrar/forms/application_wizard.py b/src/registrar/forms/application_wizard.py index 9a8899e2b..5310c4610 100644 --- a/src/registrar/forms/application_wizard.py +++ b/src/registrar/forms/application_wizard.py @@ -262,7 +262,7 @@ class OrganizationContactForm(RegistrarForm): validators=[ RegexValidator( "^[0-9]{5}(?:-[0-9]{4})?$|^$", - message="Enter a zip code in the form of 12345 or 12345-6789.", + message="Enter a zip code in the required format, like 12345 or 12345-6789.", ) ], ) diff --git a/src/registrar/forms/common.py b/src/registrar/forms/common.py index 3ab36cf6f..e2a234e71 100644 --- a/src/registrar/forms/common.py +++ b/src/registrar/forms/common.py @@ -1,6 +1,6 @@ # common.py # -# ALGORITHM_CHOICES are options for alg attribute in DS Data +# ALGORITHM_CHOICES are options for alg attribute in DS data # reference: # https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml ALGORITHM_CHOICES = [ @@ -18,7 +18,7 @@ ALGORITHM_CHOICES = [ (15, "(15) Ed25519"), (16, "(16) Ed448"), ] -# DIGEST_TYPE_CHOICES are options for digestType attribute in DS Data +# DIGEST_TYPE_CHOICES are options for digestType attribute in DS data # reference: https://datatracker.ietf.org/doc/html/rfc4034#appendix-A.2 DIGEST_TYPE_CHOICES = [ (1, "(1) SHA-1"), diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py index ff41b9268..8b55aa29d 100644 --- a/src/registrar/forms/domain.py +++ b/src/registrar/forms/domain.py @@ -239,7 +239,7 @@ class DomainOrgNameAddressForm(forms.ModelForm): validators=[ RegexValidator( "^[0-9]{5}(?:-[0-9]{4})?$|^$", - message="Enter a zip code in the form of 12345 or 12345-6789.", + message="Enter a zip code in the required format, like 12345 or 12345-6789.", ) ], ) @@ -302,7 +302,7 @@ class DomainDnssecForm(forms.Form): class DomainDsdataForm(forms.Form): - """Form for adding or editing DNSSEC DS Data to a domain.""" + """Form for adding or editing DNSSEC DS data to a domain.""" def validate_hexadecimal(value): """ diff --git a/src/registrar/management/commands/load_domain_invitations.py b/src/registrar/management/commands/load_domain_invitations.py index 28eb09def..32a63d860 100644 --- a/src/registrar/management/commands/load_domain_invitations.py +++ b/src/registrar/management/commands/load_domain_invitations.py @@ -62,7 +62,7 @@ class Command(BaseCommand): DomainInvitation( email=email_address.lower(), domain=domain, - status=DomainInvitation.INVITED, + status=DomainInvitation.DomainInvitationStatus.INVITED, ) ) logger.info("Creating %d invitations", len(to_create)) diff --git a/src/registrar/migrations/0055_alter_domain_state_alter_domainapplication_status_and_more.py b/src/registrar/migrations/0055_alter_domain_state_alter_domainapplication_status_and_more.py new file mode 100644 index 000000000..9b6bac48c --- /dev/null +++ b/src/registrar/migrations/0055_alter_domain_state_alter_domainapplication_status_and_more.py @@ -0,0 +1,70 @@ +# Generated by Django 4.2.7 on 2023-12-06 16:16 + +from django.db import migrations, models +import django_fsm + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0054_alter_domainapplication_federal_agency_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="domain", + name="state", + field=django_fsm.FSMField( + choices=[ + ("unknown", "Unknown"), + ("dns needed", "Dns needed"), + ("ready", "Ready"), + ("on hold", "On hold"), + ("deleted", "Deleted"), + ], + default="unknown", + help_text="Very basic info about the lifecycle of this domain object", + max_length=21, + protected=True, + ), + ), + migrations.AlterField( + model_name="domainapplication", + name="status", + field=django_fsm.FSMField( + choices=[ + ("started", "Started"), + ("submitted", "Submitted"), + ("in review", "In review"), + ("action needed", "Action needed"), + ("approved", "Approved"), + ("withdrawn", "Withdrawn"), + ("rejected", "Rejected"), + ("ineligible", "Ineligible"), + ], + default="started", + max_length=50, + ), + ), + migrations.AlterField( + model_name="domaininvitation", + name="status", + field=django_fsm.FSMField( + choices=[("invited", "Invited"), ("retrieved", "Retrieved")], + default="invited", + max_length=50, + protected=True, + ), + ), + migrations.AlterField( + model_name="transitiondomain", + name="status", + field=models.CharField( + blank=True, + choices=[("ready", "Ready"), ("on hold", "On hold"), ("unknown", "Unknown")], + default="ready", + help_text="domain status during the transfer", + max_length=255, + verbose_name="Status", + ), + ), + ] diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 94430fb36..c92f540f1 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -122,20 +122,20 @@ class Domain(TimeStampedModel, DomainHelper): """These capture (some of) the states a domain object can be in.""" # the state is indeterminate - UNKNOWN = "unknown" + UNKNOWN = "unknown", "Unknown" # The domain object exists in the registry # but nameservers don't exist for it yet - DNS_NEEDED = "dns needed" + DNS_NEEDED = "dns needed", "Dns needed" # Domain has had nameservers set, may or may not be active - READY = "ready" + READY = "ready", "Ready" # Registrar manually changed state to client hold - ON_HOLD = "on hold" + ON_HOLD = "on hold", "On hold" # previously existed but has been deleted from the registry - DELETED = "deleted" + DELETED = "deleted", "Deleted" class Cache(property): """ @@ -174,7 +174,8 @@ class Domain(TimeStampedModel, DomainHelper): """Check if a domain is available.""" if not cls.string_could_be_domain(domain): raise ValueError("Not a valid domain: %s" % str(domain)) - req = commands.CheckDomain([domain]) + domain_name = domain.lower() + req = commands.CheckDomain([domain_name]) return registry.send(req, cleaned=True).res_data[0].avail @classmethod diff --git a/src/registrar/models/domain_application.py b/src/registrar/models/domain_application.py index 2eb2075b7..12eda4caf 100644 --- a/src/registrar/models/domain_application.py +++ b/src/registrar/models/domain_application.py @@ -19,25 +19,16 @@ class DomainApplication(TimeStampedModel): """A registrant's application for a new domain.""" - # #### Constants for choice fields #### - STARTED = "started" - SUBMITTED = "submitted" - IN_REVIEW = "in review" - ACTION_NEEDED = "action needed" - APPROVED = "approved" - WITHDRAWN = "withdrawn" - REJECTED = "rejected" - INELIGIBLE = "ineligible" - STATUS_CHOICES = [ - (STARTED, STARTED), - (SUBMITTED, SUBMITTED), - (IN_REVIEW, IN_REVIEW), - (ACTION_NEEDED, ACTION_NEEDED), - (APPROVED, APPROVED), - (WITHDRAWN, WITHDRAWN), - (REJECTED, REJECTED), - (INELIGIBLE, INELIGIBLE), - ] + # Constants for choice fields + class ApplicationStatus(models.TextChoices): + STARTED = "started", "Started" + SUBMITTED = "submitted", "Submitted" + IN_REVIEW = "in review", "In review" + ACTION_NEEDED = "action needed", "Action needed" + APPROVED = "approved", "Approved" + WITHDRAWN = "withdrawn", "Withdrawn" + REJECTED = "rejected", "Rejected" + INELIGIBLE = "ineligible", "Ineligible" class StateTerritoryChoices(models.TextChoices): ALABAMA = "AL", "Alabama (AL)" @@ -363,8 +354,8 @@ class DomainApplication(TimeStampedModel): # #### Internal fields about the application ##### status = FSMField( - choices=STATUS_CHOICES, # possible states as an array of constants - default=STARTED, # sensible default + choices=ApplicationStatus.choices, # possible states as an array of constants + default=ApplicationStatus.STARTED, # sensible default protected=False, # can change state directly, particularly in Django admin ) # This is the application user who created this application. The contact @@ -592,7 +583,11 @@ class DomainApplication(TimeStampedModel): except EmailSendingError: logger.warning("Failed to send confirmation email", exc_info=True) - @transition(field="status", source=[STARTED, ACTION_NEEDED, WITHDRAWN], target=SUBMITTED) + @transition( + field="status", + source=[ApplicationStatus.STARTED, ApplicationStatus.ACTION_NEEDED, ApplicationStatus.WITHDRAWN], + target=ApplicationStatus.SUBMITTED, + ) def submit(self): """Submit an application that is started. @@ -618,7 +613,7 @@ class DomainApplication(TimeStampedModel): "emails/submission_confirmation_subject.txt", ) - @transition(field="status", source=SUBMITTED, target=IN_REVIEW) + @transition(field="status", source=ApplicationStatus.SUBMITTED, target=ApplicationStatus.IN_REVIEW) def in_review(self): """Investigate an application that has been submitted. @@ -630,7 +625,11 @@ class DomainApplication(TimeStampedModel): "emails/status_change_in_review_subject.txt", ) - @transition(field="status", source=[IN_REVIEW, REJECTED], target=ACTION_NEEDED) + @transition( + field="status", + source=[ApplicationStatus.IN_REVIEW, ApplicationStatus.REJECTED], + target=ApplicationStatus.ACTION_NEEDED, + ) def action_needed(self): """Send back an application that is under investigation or rejected. @@ -644,8 +643,13 @@ class DomainApplication(TimeStampedModel): @transition( field="status", - source=[SUBMITTED, IN_REVIEW, REJECTED, INELIGIBLE], - target=APPROVED, + source=[ + ApplicationStatus.SUBMITTED, + ApplicationStatus.IN_REVIEW, + ApplicationStatus.REJECTED, + ApplicationStatus.INELIGIBLE, + ], + target=ApplicationStatus.APPROVED, ) def approve(self): """Approve an application that has been submitted. @@ -678,7 +682,11 @@ class DomainApplication(TimeStampedModel): "emails/status_change_approved_subject.txt", ) - @transition(field="status", source=[SUBMITTED, IN_REVIEW], target=WITHDRAWN) + @transition( + field="status", + source=[ApplicationStatus.SUBMITTED, ApplicationStatus.IN_REVIEW], + target=ApplicationStatus.WITHDRAWN, + ) def withdraw(self): """Withdraw an application that has been submitted.""" self._send_status_update_email( @@ -689,8 +697,8 @@ class DomainApplication(TimeStampedModel): @transition( field="status", - source=[IN_REVIEW, APPROVED], - target=REJECTED, + source=[ApplicationStatus.IN_REVIEW, ApplicationStatus.APPROVED], + target=ApplicationStatus.REJECTED, conditions=[domain_is_not_active], ) def reject(self): @@ -698,7 +706,7 @@ class DomainApplication(TimeStampedModel): As side effects this will delete the domain and domain_information (will cascade), and send an email notification.""" - if self.status == self.APPROVED: + if self.status == self.ApplicationStatus.APPROVED: domain_state = self.approved_domain.state # Only reject if it exists on EPP if domain_state != Domain.State.UNKNOWN: @@ -714,8 +722,8 @@ class DomainApplication(TimeStampedModel): @transition( field="status", - source=[IN_REVIEW, APPROVED], - target=INELIGIBLE, + source=[ApplicationStatus.IN_REVIEW, ApplicationStatus.APPROVED], + target=ApplicationStatus.INELIGIBLE, conditions=[domain_is_not_active], ) def reject_with_prejudice(self): @@ -727,7 +735,7 @@ class DomainApplication(TimeStampedModel): permissions classes test against. This will also delete the domain and domain_information (will cascade) when they exist.""" - if self.status == self.APPROVED: + if self.status == self.ApplicationStatus.APPROVED: domain_state = self.approved_domain.state # Only reject if it exists on EPP if domain_state != Domain.State.UNKNOWN: diff --git a/src/registrar/models/domain_invitation.py b/src/registrar/models/domain_invitation.py index 1e0b7fec8..12082142d 100644 --- a/src/registrar/models/domain_invitation.py +++ b/src/registrar/models/domain_invitation.py @@ -15,8 +15,10 @@ logger = logging.getLogger(__name__) class DomainInvitation(TimeStampedModel): - INVITED = "invited" - RETRIEVED = "retrieved" + # Constants for status field + class DomainInvitationStatus(models.TextChoices): + INVITED = "invited", "Invited" + RETRIEVED = "retrieved", "Retrieved" email = models.EmailField( null=False, @@ -31,18 +33,15 @@ class DomainInvitation(TimeStampedModel): ) status = FSMField( - choices=[ - (INVITED, INVITED), - (RETRIEVED, RETRIEVED), - ], - default=INVITED, + choices=DomainInvitationStatus.choices, + default=DomainInvitationStatus.INVITED, protected=True, # can't alter state except through transition methods! ) def __str__(self): return f"Invitation for {self.email} on {self.domain} is {self.status}" - @transition(field="status", source=INVITED, target=RETRIEVED) + @transition(field="status", source=DomainInvitationStatus.INVITED, target=DomainInvitationStatus.RETRIEVED) def retrieve(self): """When an invitation is retrieved, create the corresponding permission. diff --git a/src/registrar/models/transition_domain.py b/src/registrar/models/transition_domain.py index 28bdc4fc7..89032d2be 100644 --- a/src/registrar/models/transition_domain.py +++ b/src/registrar/models/transition_domain.py @@ -4,7 +4,7 @@ from .utility.time_stamped_model import TimeStampedModel class StatusChoices(models.TextChoices): READY = "ready", "Ready" - ON_HOLD = "on hold", "On Hold" + ON_HOLD = "on hold", "On hold" UNKNOWN = "unknown", "Unknown" diff --git a/src/registrar/models/user.py b/src/registrar/models/user.py index 2daa3c253..0a153b5c8 100644 --- a/src/registrar/models/user.py +++ b/src/registrar/models/user.py @@ -3,6 +3,8 @@ import logging from django.contrib.auth.models import AbstractUser from django.db import models +from registrar.models.user_domain_role import UserDomainRole + from .domain_invitation import DomainInvitation from .transition_domain import TransitionDomain from .domain import Domain @@ -64,10 +66,42 @@ class User(AbstractUser): def is_restricted(self): return self.status == self.RESTRICTED + @classmethod + def needs_identity_verification(cls, email, uuid): + """A method used by our oidc classes to test whether a user needs email/uuid verification + or the full identity PII verification""" + + # An existing user who is a domain manager of a domain (that is, + # they have an entry in UserDomainRole for their User) + try: + existing_user = cls.objects.get(username=uuid) + if existing_user and UserDomainRole.objects.filter(user=existing_user).exists(): + return False + except cls.DoesNotExist: + # Do nothing when the user is not found, as we're checking for existence. + pass + except Exception as err: + raise err + + # A new incoming user who is a domain manager for one of the domains + # that we inputted from Verisign (that is, their email address appears + # in the username field of a TransitionDomain) + if TransitionDomain.objects.filter(username=email).exists(): + return False + + # A new incoming user who is being invited to be a domain manager (that is, + # their email address is in DomainInvitation for an invitation that is not yet "retrieved"). + if DomainInvitation.objects.filter(email=email, status=DomainInvitation.INVITED).exists(): + return False + + return True + def check_domain_invitations_on_login(self): """When a user first arrives on the site, we need to retrieve any domain invitations that match their email address.""" - for invitation in DomainInvitation.objects.filter(email=self.email, status=DomainInvitation.INVITED): + for invitation in DomainInvitation.objects.filter( + email=self.email, status=DomainInvitation.DomainInvitationStatus.INVITED + ): try: invitation.retrieve() invitation.save() diff --git a/src/registrar/templates/application_status.html b/src/registrar/templates/application_status.html index 3bf02f0e0..fbabf39a7 100644 --- a/src/registrar/templates/application_status.html +++ b/src/registrar/templates/application_status.html @@ -31,7 +31,7 @@ Status: {% if domainapplication.status == 'approved' %} Approved - {% elif domainapplication.status == 'in review' %} In Review + {% elif domainapplication.status == 'in review' %} In review {% elif domainapplication.status == 'rejected' %} Rejected {% elif domainapplication.status == 'submitted' %} Submitted {% elif domainapplication.status == 'ineligible' %} Ineligible diff --git a/src/registrar/templates/domain_detail.html b/src/registrar/templates/domain_detail.html index 81a350f82..470df7537 100644 --- a/src/registrar/templates/domain_detail.html +++ b/src/registrar/templates/domain_detail.html @@ -18,7 +18,7 @@ Status: {% if domain.state == domain.State.UNKNOWN or domain.state == domain.State.DNS_NEEDED%} - DNS Needed + DNS needed {% else %} {{ domain.state|title }} {% endif %} diff --git a/src/registrar/templates/domain_dsdata.html b/src/registrar/templates/domain_dsdata.html index d049675fc..15343413b 100644 --- a/src/registrar/templates/domain_dsdata.html +++ b/src/registrar/templates/domain_dsdata.html @@ -1,13 +1,13 @@ {% extends "domain_base.html" %} {% load static field_helpers url_helpers %} -{% block title %}DS Data | {{ domain.name }} | {% endblock %} +{% block title %}DS data | {{ domain.name }} | {% endblock %} {% block domain_content %} {% if domain.dnssecdata is None %}
- You have no DS Data added. Enable DNSSEC by adding DS Data. + You have no DS data added. Enable DNSSEC by adding DS data.
{% endif %} @@ -16,11 +16,11 @@ {% include "includes/form_errors.html" with form=form %} {% endfor %} -

DS Data

+

DS data

In order to enable DNSSEC, you must first configure it with your DNS hosting service.

-

Enter the values given by your DNS provider for DS Data.

+

Enter the values given by your DNS provider for DS data.

{% include "includes/required_fields.html" %} @@ -31,9 +31,9 @@ {% for form in formset %}
- DS Data record {{forloop.counter}} + DS data record {{forloop.counter}} -

DS Data record {{forloop.counter}}

+

DS data record {{forloop.counter}}

diff --git a/src/registrar/templates/domain_sidebar.html b/src/registrar/templates/domain_sidebar.html index c224d60c1..9e00fafa9 100644 --- a/src/registrar/templates/domain_sidebar.html +++ b/src/registrar/templates/domain_sidebar.html @@ -44,7 +44,7 @@ - DS Data + DS data diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 7cb7ce126..756ed4378 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -53,7 +53,7 @@ {{ domain.created_time|date }} {% if domain.state == "unknown" or domain.state == "dns needed"%} - DNS Needed + DNS needed {% else %} {{ domain.state|title }} {% endif %} @@ -111,7 +111,7 @@ {{ application.requested_domain.name|default:"New domain request" }} {{ application.created_at|date }} - {{ application.status|title }} + {{ application.get_status_display }} {% if application.status == "started" or application.status == "action needed" or application.status == "withdrawn" %} diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index f17e0f9fa..f031f4d76 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -294,7 +294,7 @@ class AuditedAdminMockData: self, domain_type, item_name, - status=DomainApplication.STARTED, + status=DomainApplication.ApplicationStatus.STARTED, org_type="federal", federal_type="executive", purpose="Purpose of the site", @@ -311,7 +311,7 @@ class AuditedAdminMockData: title, email, and username. status (str - optional): Defines the status for DomainApplication, - e.g. DomainApplication.STARTED + e.g. DomainApplication.ApplicationStatus.STARTED org_type (str - optional): Sets a domains org_type @@ -344,23 +344,23 @@ class AuditedAdminMockData: full_arg_dict = dict( email="test_mail@mail.com", domain=self.dummy_domain(item_name, True), - status=DomainInvitation.INVITED, + status=DomainInvitation.DomainInvitationStatus.INVITED, ) return full_arg_dict - def create_full_dummy_domain_application(self, item_name, status=DomainApplication.STARTED): + def create_full_dummy_domain_application(self, item_name, status=DomainApplication.ApplicationStatus.STARTED): """Creates a dummy domain application object""" domain_application_kwargs = self.dummy_kwarg_boilerplate(self.APPLICATION, item_name, status) application = DomainApplication.objects.get_or_create(**domain_application_kwargs)[0] return application - def create_full_dummy_domain_information(self, item_name, status=DomainApplication.STARTED): + def create_full_dummy_domain_information(self, item_name, status=DomainApplication.ApplicationStatus.STARTED): """Creates a dummy domain information object""" domain_application_kwargs = self.dummy_kwarg_boilerplate(self.INFORMATION, item_name, status) application = DomainInformation.objects.get_or_create(**domain_application_kwargs)[0] return application - def create_full_dummy_domain_invitation(self, item_name, status=DomainApplication.STARTED): + def create_full_dummy_domain_invitation(self, item_name, status=DomainApplication.ApplicationStatus.STARTED): """Creates a dummy domain invitation object""" domain_application_kwargs = self.dummy_kwarg_boilerplate(self.INVITATION, item_name, status) application = DomainInvitation.objects.get_or_create(**domain_application_kwargs)[0] @@ -374,7 +374,7 @@ class AuditedAdminMockData: has_other_contacts=True, has_current_website=True, has_alternative_gov_domain=True, - status=DomainApplication.STARTED, + status=DomainApplication.ApplicationStatus.STARTED, ): """A helper to create a dummy domain application object""" application = None @@ -455,7 +455,7 @@ def completed_application( has_alternative_gov_domain=True, has_about_your_organization=True, has_anything_else=True, - status=DomainApplication.STARTED, + status=DomainApplication.ApplicationStatus.STARTED, user=False, name="city.gov", ): @@ -829,10 +829,8 @@ class MockEppLib(TestCase): def mockCheckDomainCommand(self, _request, cleaned): if "gsa.gov" in getattr(_request, "names", None): return self._mockDomainName("gsa.gov", False) - elif "GSA.gov" in getattr(_request, "names", None): - return self._mockDomainName("GSA.gov", False) elif "igorville.gov" in getattr(_request, "names", None): - return self._mockDomainName("igorvilleremixed.gov", True) + return self._mockDomainName("igorville.gov", True) elif "top-level-agency.gov" in getattr(_request, "names", None): return self._mockDomainName("top-level-agency.gov", True) elif "city.gov" in getattr(_request, "names", None): diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 526a9ea2a..8a67fc191 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -61,7 +61,7 @@ class TestDomainAdmin(MockEppLib): Make sure the short name is displaying in admin on the list page """ self.client.force_login(self.superuser) - application = completed_application(status=DomainApplication.IN_REVIEW) + application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) application.approve() response = self.client.get("/admin/registrar/domain/") @@ -282,7 +282,7 @@ class TestDomainApplicationAdminForm(TestCase): form = DomainApplicationAdminForm(instance=self.application) # Verify that the form choices match the available transitions for started - expected_choices = [("started", "started"), ("submitted", "submitted")] + expected_choices = [("started", "Started"), ("submitted", "Submitted")] self.assertEqual(form.fields["status"].widget.choices, expected_choices) def test_form_choices_when_no_instance(self): @@ -355,7 +355,7 @@ class TestDomainApplicationAdmin(MockEppLib): request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk)) # Modify the application's property - application.status = DomainApplication.SUBMITTED + application.status = DomainApplication.ApplicationStatus.SUBMITTED # Use the model admin's save_model method self.admin.save_model(request, application, form=None, change=True) @@ -390,13 +390,13 @@ class TestDomainApplicationAdmin(MockEppLib): with boto3_mocking.clients.handler_for("sesv2", mock_client): # Create a sample application - application = completed_application(status=DomainApplication.SUBMITTED) + application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED) # Create a mock request request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk)) # Modify the application's property - application.status = DomainApplication.IN_REVIEW + application.status = DomainApplication.ApplicationStatus.IN_REVIEW # Use the model admin's save_model method self.admin.save_model(request, application, form=None, change=True) @@ -431,13 +431,13 @@ class TestDomainApplicationAdmin(MockEppLib): with boto3_mocking.clients.handler_for("sesv2", mock_client): # Create a sample application - application = completed_application(status=DomainApplication.IN_REVIEW) + application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) # Create a mock request request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk)) # Modify the application's property - application.status = DomainApplication.APPROVED + application.status = DomainApplication.ApplicationStatus.APPROVED # Use the model admin's save_model method self.admin.save_model(request, application, form=None, change=True) @@ -467,13 +467,13 @@ class TestDomainApplicationAdmin(MockEppLib): User.objects.filter(email=EMAIL).delete() # Create a sample application - application = completed_application(status=DomainApplication.IN_REVIEW) + application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) # Create a mock request request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk)) # Modify the application's property - application.status = DomainApplication.APPROVED + application.status = DomainApplication.ApplicationStatus.APPROVED # Use the model admin's save_model method self.admin.save_model(request, application, form=None, change=True) @@ -492,13 +492,13 @@ class TestDomainApplicationAdmin(MockEppLib): with boto3_mocking.clients.handler_for("sesv2", mock_client): # Create a sample application - application = completed_application(status=DomainApplication.IN_REVIEW) + application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) # Create a mock request request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk)) # Modify the application's property - application.status = DomainApplication.ACTION_NEEDED + application.status = DomainApplication.ApplicationStatus.ACTION_NEEDED # Use the model admin's save_model method self.admin.save_model(request, application, form=None, change=True) @@ -533,13 +533,13 @@ class TestDomainApplicationAdmin(MockEppLib): with boto3_mocking.clients.handler_for("sesv2", mock_client): # Create a sample application - application = completed_application(status=DomainApplication.IN_REVIEW) + application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) # Create a mock request request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk)) # Modify the application's property - application.status = DomainApplication.REJECTED + application.status = DomainApplication.ApplicationStatus.REJECTED # Use the model admin's save_model method self.admin.save_model(request, application, form=None, change=True) @@ -569,13 +569,13 @@ class TestDomainApplicationAdmin(MockEppLib): User.objects.filter(email=EMAIL).delete() # Create a sample application - application = completed_application(status=DomainApplication.IN_REVIEW) + application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) # Create a mock request request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk)) # Modify the application's property - application.status = DomainApplication.INELIGIBLE + application.status = DomainApplication.ApplicationStatus.INELIGIBLE # Use the model admin's save_model method self.admin.save_model(request, application, form=None, change=True) @@ -584,7 +584,7 @@ class TestDomainApplicationAdmin(MockEppLib): self.assertEqual(application.creator.status, "restricted") def test_readonly_when_restricted_creator(self): - application = completed_application(status=DomainApplication.IN_REVIEW) + application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) application.creator.status = User.RESTRICTED application.creator.save() @@ -662,7 +662,7 @@ class TestDomainApplicationAdmin(MockEppLib): def test_saving_when_restricted_creator(self): # Create an instance of the model - application = completed_application(status=DomainApplication.IN_REVIEW) + application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) application.creator.status = User.RESTRICTED application.creator.save() @@ -681,11 +681,11 @@ class TestDomainApplicationAdmin(MockEppLib): ) # Assert that the status has not changed - self.assertEqual(application.status, DomainApplication.IN_REVIEW) + self.assertEqual(application.status, DomainApplication.ApplicationStatus.IN_REVIEW) def test_change_view_with_restricted_creator(self): # Create an instance of the model - application = completed_application(status=DomainApplication.IN_REVIEW) + application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) application.creator.status = User.RESTRICTED application.creator.save() @@ -704,7 +704,7 @@ class TestDomainApplicationAdmin(MockEppLib): 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) + application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED) domain = Domain.objects.create(name=application.requested_domain.name) application.approved_domain = domain application.save() @@ -724,7 +724,7 @@ class TestDomainApplicationAdmin(MockEppLib): stack.enter_context(patch.object(messages, "error")) # Simulate saving the model - application.status = DomainApplication.REJECTED + application.status = DomainApplication.ApplicationStatus.REJECTED self.admin.save_model(request, application, None, True) # Assert that the error message was called with the correct argument @@ -735,7 +735,7 @@ class TestDomainApplicationAdmin(MockEppLib): def test_side_effects_when_saving_approved_to_rejected(self): # Create an instance of the model - application = completed_application(status=DomainApplication.APPROVED) + application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED) domain = Domain.objects.create(name=application.requested_domain.name) domain_information = DomainInformation.objects.create(creator=self.superuser, domain=domain) application.approved_domain = domain @@ -756,7 +756,7 @@ class TestDomainApplicationAdmin(MockEppLib): stack.enter_context(patch.object(messages, "error")) # Simulate saving the model - application.status = DomainApplication.REJECTED + application.status = DomainApplication.ApplicationStatus.REJECTED self.admin.save_model(request, application, None, True) # Assert that the error message was never called @@ -774,7 +774,7 @@ class TestDomainApplicationAdmin(MockEppLib): 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) + application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED) domain = Domain.objects.create(name=application.requested_domain.name) application.approved_domain = domain application.save() @@ -794,7 +794,7 @@ class TestDomainApplicationAdmin(MockEppLib): stack.enter_context(patch.object(messages, "error")) # Simulate saving the model - application.status = DomainApplication.INELIGIBLE + application.status = DomainApplication.ApplicationStatus.INELIGIBLE self.admin.save_model(request, application, None, True) # Assert that the error message was called with the correct argument @@ -805,7 +805,7 @@ class TestDomainApplicationAdmin(MockEppLib): def test_side_effects_when_saving_approved_to_ineligible(self): # Create an instance of the model - application = completed_application(status=DomainApplication.APPROVED) + application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED) domain = Domain.objects.create(name=application.requested_domain.name) domain_information = DomainInformation.objects.create(creator=self.superuser, domain=domain) application.approved_domain = domain @@ -826,7 +826,7 @@ class TestDomainApplicationAdmin(MockEppLib): stack.enter_context(patch.object(messages, "error")) # Simulate saving the model - application.status = DomainApplication.INELIGIBLE + application.status = DomainApplication.ApplicationStatus.INELIGIBLE self.admin.save_model(request, application, None, True) # Assert that the error message was never called @@ -877,12 +877,14 @@ class DomainInvitationAdminTest(TestCase): ) # Assert that the filters are added - self.assertContains(response, "invited", count=4) - self.assertContains(response, "retrieved", count=4) + self.assertContains(response, "invited", count=2) + self.assertContains(response, "Invited", count=2) + self.assertContains(response, "retrieved", count=2) + self.assertContains(response, "Retrieved", count=2) # Check for the HTML context specificially - invited_html = 'invited' - retrieved_html = 'retrieved' + invited_html = 'Invited' + retrieved_html = 'Retrieved' self.assertContains(response, invited_html, count=1) self.assertContains(response, retrieved_html, count=1) diff --git a/src/registrar/tests/test_forms.py b/src/registrar/tests/test_forms.py index 035e1c8c5..00bb7ce61 100644 --- a/src/registrar/tests/test_forms.py +++ b/src/registrar/tests/test_forms.py @@ -30,7 +30,7 @@ class TestFormValidation(MockEppLib): form = OrganizationContactForm(data={"zipcode": "nah"}) self.assertEqual( form.errors["zipcode"], - ["Enter a zip code in the form of 12345 or 12345-6789."], + ["Enter a zip code in the required format, like 12345 or 12345-6789."], ) def test_org_contact_zip_valid(self): diff --git a/src/registrar/tests/test_models.py b/src/registrar/tests/test_models.py index d397cb129..83126ab7c 100644 --- a/src/registrar/tests/test_models.py +++ b/src/registrar/tests/test_models.py @@ -35,7 +35,7 @@ class TestDomainApplication(TestCase): """Can create with just a creator.""" user, _ = User.objects.get_or_create() application = DomainApplication.objects.create(creator=user) - self.assertEqual(application.status, DomainApplication.STARTED) + self.assertEqual(application.status, DomainApplication.ApplicationStatus.STARTED) def test_full_create(self): """Can create with all fields.""" @@ -108,7 +108,7 @@ class TestDomainApplication(TestCase): # no submitter email so this emits a log warning with less_console_noise(): application.submit() - self.assertEqual(application.status, application.SUBMITTED) + self.assertEqual(application.status, application.ApplicationStatus.SUBMITTED) def test_submit_sends_email(self): """Create an application and submit it and see if email was sent.""" @@ -139,7 +139,7 @@ class TestDomainApplication(TestCase): """Create an application with status submitted and call submit against transition rules""" - application = completed_application(status=DomainApplication.SUBMITTED) + application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED) with self.assertRaises(TransitionNotAllowed): application.submit() @@ -148,7 +148,7 @@ class TestDomainApplication(TestCase): """Create an application with status in review and call submit against transition rules""" - application = completed_application(status=DomainApplication.IN_REVIEW) + application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) with self.assertRaises(TransitionNotAllowed): application.submit() @@ -157,7 +157,7 @@ class TestDomainApplication(TestCase): """Create an application with status approved and call submit against transition rules""" - application = completed_application(status=DomainApplication.APPROVED) + application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED) with self.assertRaises(TransitionNotAllowed): application.submit() @@ -166,7 +166,7 @@ class TestDomainApplication(TestCase): """Create an application with status rejected and call submit against transition rules""" - application = completed_application(status=DomainApplication.REJECTED) + application = completed_application(status=DomainApplication.ApplicationStatus.REJECTED) with self.assertRaises(TransitionNotAllowed): application.submit() @@ -175,7 +175,7 @@ class TestDomainApplication(TestCase): """Create an application with status ineligible and call submit against transition rules""" - application = completed_application(status=DomainApplication.INELIGIBLE) + application = completed_application(status=DomainApplication.ApplicationStatus.INELIGIBLE) with self.assertRaises(TransitionNotAllowed): application.submit() @@ -184,7 +184,7 @@ class TestDomainApplication(TestCase): """Create an application with status started and call in_review against transition rules""" - application = completed_application(status=DomainApplication.STARTED) + application = completed_application(status=DomainApplication.ApplicationStatus.STARTED) with self.assertRaises(TransitionNotAllowed): application.in_review() @@ -193,7 +193,7 @@ class TestDomainApplication(TestCase): """Create an application with status in review and call in_review against transition rules""" - application = completed_application(status=DomainApplication.IN_REVIEW) + application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) with self.assertRaises(TransitionNotAllowed): application.in_review() @@ -202,7 +202,7 @@ class TestDomainApplication(TestCase): """Create an application with status approved and call in_review against transition rules""" - application = completed_application(status=DomainApplication.APPROVED) + application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED) with self.assertRaises(TransitionNotAllowed): application.in_review() @@ -211,7 +211,7 @@ class TestDomainApplication(TestCase): """Create an application with status action needed and call in_review against transition rules""" - application = completed_application(status=DomainApplication.ACTION_NEEDED) + application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED) with self.assertRaises(TransitionNotAllowed): application.in_review() @@ -220,7 +220,7 @@ class TestDomainApplication(TestCase): """Create an application with status rejected and call in_review against transition rules""" - application = completed_application(status=DomainApplication.REJECTED) + application = completed_application(status=DomainApplication.ApplicationStatus.REJECTED) with self.assertRaises(TransitionNotAllowed): application.in_review() @@ -229,7 +229,7 @@ class TestDomainApplication(TestCase): """Create an application with status withdrawn and call in_review against transition rules""" - application = completed_application(status=DomainApplication.WITHDRAWN) + application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN) with self.assertRaises(TransitionNotAllowed): application.in_review() @@ -238,7 +238,7 @@ class TestDomainApplication(TestCase): """Create an application with status ineligible and call in_review against transition rules""" - application = completed_application(status=DomainApplication.INELIGIBLE) + application = completed_application(status=DomainApplication.ApplicationStatus.INELIGIBLE) with self.assertRaises(TransitionNotAllowed): application.in_review() @@ -247,7 +247,7 @@ class TestDomainApplication(TestCase): """Create an application with status started and call action_needed against transition rules""" - application = completed_application(status=DomainApplication.STARTED) + application = completed_application(status=DomainApplication.ApplicationStatus.STARTED) with self.assertRaises(TransitionNotAllowed): application.action_needed() @@ -256,7 +256,7 @@ class TestDomainApplication(TestCase): """Create an application with status submitted and call action_needed against transition rules""" - application = completed_application(status=DomainApplication.SUBMITTED) + application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED) with self.assertRaises(TransitionNotAllowed): application.action_needed() @@ -265,7 +265,7 @@ class TestDomainApplication(TestCase): """Create an application with status action needed and call action_needed against transition rules""" - application = completed_application(status=DomainApplication.ACTION_NEEDED) + application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED) with self.assertRaises(TransitionNotAllowed): application.action_needed() @@ -274,7 +274,7 @@ class TestDomainApplication(TestCase): """Create an application with status approved and call action_needed against transition rules""" - application = completed_application(status=DomainApplication.APPROVED) + application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED) with self.assertRaises(TransitionNotAllowed): application.action_needed() @@ -283,7 +283,7 @@ class TestDomainApplication(TestCase): """Create an application with status withdrawn and call action_needed against transition rules""" - application = completed_application(status=DomainApplication.WITHDRAWN) + application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN) with self.assertRaises(TransitionNotAllowed): application.action_needed() @@ -292,7 +292,7 @@ class TestDomainApplication(TestCase): """Create an application with status ineligible and call action_needed against transition rules""" - application = completed_application(status=DomainApplication.INELIGIBLE) + application = completed_application(status=DomainApplication.ApplicationStatus.INELIGIBLE) with self.assertRaises(TransitionNotAllowed): application.action_needed() @@ -301,7 +301,7 @@ class TestDomainApplication(TestCase): """Create an application with status started and call approve against transition rules""" - application = completed_application(status=DomainApplication.STARTED) + application = completed_application(status=DomainApplication.ApplicationStatus.STARTED) with self.assertRaises(TransitionNotAllowed): application.approve() @@ -310,7 +310,7 @@ class TestDomainApplication(TestCase): """Create an application with status approved and call approve against transition rules""" - application = completed_application(status=DomainApplication.APPROVED) + application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED) with self.assertRaises(TransitionNotAllowed): application.approve() @@ -319,7 +319,7 @@ class TestDomainApplication(TestCase): """Create an application with status action needed and call approve against transition rules""" - application = completed_application(status=DomainApplication.ACTION_NEEDED) + application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED) with self.assertRaises(TransitionNotAllowed): application.approve() @@ -328,7 +328,7 @@ class TestDomainApplication(TestCase): """Create an application with status withdrawn and call approve against transition rules""" - application = completed_application(status=DomainApplication.WITHDRAWN) + application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN) with self.assertRaises(TransitionNotAllowed): application.approve() @@ -337,7 +337,7 @@ class TestDomainApplication(TestCase): """Create an application with status started and call withdraw against transition rules""" - application = completed_application(status=DomainApplication.STARTED) + application = completed_application(status=DomainApplication.ApplicationStatus.STARTED) with self.assertRaises(TransitionNotAllowed): application.withdraw() @@ -346,7 +346,7 @@ class TestDomainApplication(TestCase): """Create an application with status approved and call withdraw against transition rules""" - application = completed_application(status=DomainApplication.APPROVED) + application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED) with self.assertRaises(TransitionNotAllowed): application.withdraw() @@ -355,7 +355,7 @@ class TestDomainApplication(TestCase): """Create an application with status action needed and call withdraw against transition rules""" - application = completed_application(status=DomainApplication.ACTION_NEEDED) + application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED) with self.assertRaises(TransitionNotAllowed): application.withdraw() @@ -364,7 +364,7 @@ class TestDomainApplication(TestCase): """Create an application with status rejected and call withdraw against transition rules""" - application = completed_application(status=DomainApplication.REJECTED) + application = completed_application(status=DomainApplication.ApplicationStatus.REJECTED) with self.assertRaises(TransitionNotAllowed): application.withdraw() @@ -373,7 +373,7 @@ class TestDomainApplication(TestCase): """Create an application with status withdrawn and call withdraw against transition rules""" - application = completed_application(status=DomainApplication.WITHDRAWN) + application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN) with self.assertRaises(TransitionNotAllowed): application.withdraw() @@ -382,7 +382,7 @@ class TestDomainApplication(TestCase): """Create an application with status ineligible and call withdraw against transition rules""" - application = completed_application(status=DomainApplication.INELIGIBLE) + application = completed_application(status=DomainApplication.ApplicationStatus.INELIGIBLE) with self.assertRaises(TransitionNotAllowed): application.withdraw() @@ -391,7 +391,7 @@ class TestDomainApplication(TestCase): """Create an application with status started and call reject against transition rules""" - application = completed_application(status=DomainApplication.STARTED) + application = completed_application(status=DomainApplication.ApplicationStatus.STARTED) with self.assertRaises(TransitionNotAllowed): application.reject() @@ -400,7 +400,7 @@ class TestDomainApplication(TestCase): """Create an application with status submitted and call reject against transition rules""" - application = completed_application(status=DomainApplication.SUBMITTED) + application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED) with self.assertRaises(TransitionNotAllowed): application.reject() @@ -409,7 +409,7 @@ class TestDomainApplication(TestCase): """Create an application with status action needed and call reject against transition rules""" - application = completed_application(status=DomainApplication.ACTION_NEEDED) + application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED) with self.assertRaises(TransitionNotAllowed): application.reject() @@ -418,7 +418,7 @@ class TestDomainApplication(TestCase): """Create an application with status withdrawn and call reject against transition rules""" - application = completed_application(status=DomainApplication.WITHDRAWN) + application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN) with self.assertRaises(TransitionNotAllowed): application.reject() @@ -427,7 +427,7 @@ class TestDomainApplication(TestCase): """Create an application with status rejected and call reject against transition rules""" - application = completed_application(status=DomainApplication.REJECTED) + application = completed_application(status=DomainApplication.ApplicationStatus.REJECTED) with self.assertRaises(TransitionNotAllowed): application.reject() @@ -436,7 +436,7 @@ class TestDomainApplication(TestCase): """Create an application with status ineligible and call reject against transition rules""" - application = completed_application(status=DomainApplication.INELIGIBLE) + application = completed_application(status=DomainApplication.ApplicationStatus.INELIGIBLE) with self.assertRaises(TransitionNotAllowed): application.reject() @@ -445,7 +445,7 @@ class TestDomainApplication(TestCase): """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) + application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED) domain = Domain.objects.create(name=application.requested_domain.name) application.approved_domain = domain application.save() @@ -464,7 +464,7 @@ class TestDomainApplication(TestCase): """Create an application with status started and call reject against transition rules""" - application = completed_application(status=DomainApplication.STARTED) + application = completed_application(status=DomainApplication.ApplicationStatus.STARTED) with self.assertRaises(TransitionNotAllowed): application.reject_with_prejudice() @@ -473,7 +473,7 @@ class TestDomainApplication(TestCase): """Create an application with status submitted and call reject against transition rules""" - application = completed_application(status=DomainApplication.SUBMITTED) + application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED) with self.assertRaises(TransitionNotAllowed): application.reject_with_prejudice() @@ -482,7 +482,7 @@ class TestDomainApplication(TestCase): """Create an application with status action needed and call reject against transition rules""" - application = completed_application(status=DomainApplication.ACTION_NEEDED) + application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED) with self.assertRaises(TransitionNotAllowed): application.reject_with_prejudice() @@ -491,7 +491,7 @@ class TestDomainApplication(TestCase): """Create an application with status withdrawn and call reject against transition rules""" - application = completed_application(status=DomainApplication.WITHDRAWN) + application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN) with self.assertRaises(TransitionNotAllowed): application.reject_with_prejudice() @@ -500,7 +500,7 @@ class TestDomainApplication(TestCase): """Create an application with status rejected and call reject against transition rules""" - application = completed_application(status=DomainApplication.REJECTED) + application = completed_application(status=DomainApplication.ApplicationStatus.REJECTED) with self.assertRaises(TransitionNotAllowed): application.reject_with_prejudice() @@ -509,7 +509,7 @@ class TestDomainApplication(TestCase): """Create an application with status ineligible and call reject against transition rules""" - application = completed_application(status=DomainApplication.INELIGIBLE) + application = completed_application(status=DomainApplication.ApplicationStatus.INELIGIBLE) with self.assertRaises(TransitionNotAllowed): application.reject_with_prejudice() @@ -518,7 +518,7 @@ class TestDomainApplication(TestCase): """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) + application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED) domain = Domain.objects.create(name=application.requested_domain.name) application.approved_domain = domain application.save() @@ -543,7 +543,7 @@ class TestPermissions(TestCase): user, _ = User.objects.get_or_create() application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain) # skip using the submit method - application.status = DomainApplication.SUBMITTED + application.status = DomainApplication.ApplicationStatus.SUBMITTED application.approve() # should be a role for this user @@ -560,7 +560,7 @@ class TestDomainInfo(TestCase): user, _ = User.objects.get_or_create() application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain) # skip using the submit method - application.status = DomainApplication.SUBMITTED + application.status = DomainApplication.ApplicationStatus.SUBMITTED application.approve() # should be an information present for this domain @@ -597,7 +597,7 @@ class TestInvitations(TestCase): # this is not an error but does produce a console warning with less_console_noise(): self.invitation.retrieve() - self.assertEqual(self.invitation.status, DomainInvitation.RETRIEVED) + self.assertEqual(self.invitation.status, DomainInvitation.DomainInvitationStatus.RETRIEVED) def test_retrieve_on_each_login(self): """A user's authenticate on_each_login callback retrieves their invitations.""" @@ -606,19 +606,15 @@ class TestInvitations(TestCase): class TestUser(TestCase): - """For now, just test actions that - occur on user login.""" + """Test actions that occur on user login, + test class method that controls how users get validated.""" 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) - # clean out the roles each time - UserDomainRole.objects.all().delete() - - TransitionDomain.objects.get_or_create(username="mayor@igorville.gov", domain_name=self.domain_name) - def tearDown(self): super().tearDown() Domain.objects.all().delete() @@ -626,6 +622,7 @@ class TestUser(TestCase): DomainInformation.objects.all().delete() TransitionDomain.objects.all().delete() User.objects.all().delete() + UserDomainRole.objects.all().delete() def test_check_transition_domains_without_domains_on_login(self): """A user's on_each_login callback does not check transition domains. @@ -634,3 +631,26 @@ class TestUser(TestCase): are created.""" self.user.on_each_login() self.assertFalse(Domain.objects.filter(name=self.domain_name).exists()) + + 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)) + + 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)) + + 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)) + + 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)) diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 4a2023243..39f63c942 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -261,7 +261,7 @@ class TestDomainCreation(MockEppLib): user, _ = User.objects.get_or_create() application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain) # skip using the submit method - application.status = DomainApplication.SUBMITTED + application.status = DomainApplication.ApplicationStatus.SUBMITTED # transition to approve state application.approve() # should have information present for this domain @@ -1506,7 +1506,7 @@ class TestRegistrantNameservers(MockEppLib): ] def test_setting_not_allowed(self): - """Scenario: A domain state is not Ready or DNS Needed + """Scenario: A domain state is not Ready or DNS needed then setting nameservers is not allowed""" domain, _ = Domain.objects.get_or_create(name="onholdDomain.gov", state=Domain.State.ON_HOLD) with self.assertRaises(ActionNotAllowed): diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py index b94316248..112b2ba34 100644 --- a/src/registrar/tests/test_reports.py +++ b/src/registrar/tests/test_reports.py @@ -258,7 +258,6 @@ class ExportDataTest(TestCase): ) def tearDown(self): - # Dummy push - will remove Domain.objects.all().delete() DomainInformation.objects.all().delete() User.objects.all().delete() diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index a71f85697..3c98c5fe7 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -100,7 +100,7 @@ class LoggedInTests(TestWithUser): response = self.client.get("/") # count = 2 because it is also in screenreader content self.assertContains(response, "igorville.gov", count=2) - self.assertContains(response, "DNS Needed") + self.assertContains(response, "DNS needed") # clean up role.delete() @@ -1079,7 +1079,7 @@ class DomainApplicationTests(TestWithUser, WebTest): Make sure the long name is displaying in the application summary page (manage your application) """ - completed_application(status=DomainApplication.SUBMITTED, user=self.user) + completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user) home_page = self.app.get("/") self.assertContains(home_page, "city.gov") # click the "Edit" link @@ -1941,19 +1941,19 @@ class TestDomainDNSSEC(TestDomainOverview): self.assertContains(updated_page, "Enable DNSSEC") def test_ds_form_loads_with_no_domain_data(self): - """DNSSEC Add DS Data page loads when there is no + """DNSSEC Add DS data page loads when there is no domain DNSSEC data and shows a button to Add new record""" page = self.client.get(reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dnssec_none.id})) - self.assertContains(page, "You have no DS Data added") + self.assertContains(page, "You have no DS data added") self.assertContains(page, "Add new record") def test_ds_form_loads_with_ds_data(self): - """DNSSEC Add DS Data page loads when there is + """DNSSEC Add DS data page loads when there is domain DNSSEC DS data and shows the data""" page = self.client.get(reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id})) - self.assertContains(page, "DS Data record 1") + self.assertContains(page, "DS data record 1") def test_ds_data_form_modal(self): """When user clicks on save, a modal pops up.""" @@ -1974,7 +1974,7 @@ class TestDomainDNSSEC(TestDomainOverview): self.assertContains(response, "Trigger Disable DNSSEC Modal") def test_ds_data_form_submits(self): - """DS Data form submits successfully + """DS data form submits successfully Uses self.app WebTest because we need to interact with forms. """ @@ -1991,10 +1991,10 @@ class TestDomainDNSSEC(TestDomainOverview): ) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) page = result.follow() - self.assertContains(page, "The DS Data records for this domain have been updated.") + self.assertContains(page, "The DS data records for this domain have been updated.") def test_ds_data_form_invalid(self): - """DS Data form errors with invalid data (missing required fields) + """DS data form errors with invalid data (missing required fields) Uses self.app WebTest because we need to interact with forms. """ @@ -2017,7 +2017,7 @@ class TestDomainDNSSEC(TestDomainOverview): self.assertContains(result, "Digest is required", count=2, status_code=200) def test_ds_data_form_invalid_keytag(self): - """DS Data form errors with invalid data (key tag too large) + """DS data form errors with invalid data (key tag too large) Uses self.app WebTest because we need to interact with forms. """ @@ -2040,7 +2040,7 @@ class TestDomainDNSSEC(TestDomainOverview): ) def test_ds_data_form_invalid_digest_chars(self): - """DS Data form errors with invalid data (digest contains non hexadecimal chars) + """DS data form errors with invalid data (digest contains non hexadecimal chars) Uses self.app WebTest because we need to interact with forms. """ @@ -2063,7 +2063,7 @@ class TestDomainDNSSEC(TestDomainOverview): ) def test_ds_data_form_invalid_digest_sha1(self): - """DS Data form errors with invalid data (digest is invalid sha-1) + """DS data form errors with invalid data (digest is invalid sha-1) Uses self.app WebTest because we need to interact with forms. """ @@ -2086,7 +2086,7 @@ class TestDomainDNSSEC(TestDomainOverview): ) def test_ds_data_form_invalid_digest_sha256(self): - """DS Data form errors with invalid data (digest is invalid sha-256) + """DS data form errors with invalid data (digest is invalid sha-256) Uses self.app WebTest because we need to interact with forms. """ @@ -2117,7 +2117,7 @@ class TestApplicationStatus(TestWithUser, WebTest): def test_application_status(self): """Checking application status page""" - application = completed_application(status=DomainApplication.SUBMITTED, user=self.user) + application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user) application.save() home_page = self.app.get("/") @@ -2137,7 +2137,7 @@ class TestApplicationStatus(TestWithUser, WebTest): self.user.status = "ineligible" self.user.save() - application = completed_application(status=DomainApplication.SUBMITTED, user=self.user) + application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user) application.save() home_page = self.app.get("/") @@ -2152,7 +2152,7 @@ class TestApplicationStatus(TestWithUser, WebTest): def test_application_withdraw(self): """Checking application status page""" - application = completed_application(status=DomainApplication.SUBMITTED, user=self.user) + application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user) application.save() home_page = self.app.get("/") @@ -2182,7 +2182,7 @@ class TestApplicationStatus(TestWithUser, WebTest): def test_application_status_no_permissions(self): """Can't access applications without being the creator.""" - application = completed_application(status=DomainApplication.SUBMITTED, user=self.user) + application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user) other_user = User() other_user.save() application.creator = other_user @@ -2202,7 +2202,7 @@ class TestApplicationStatus(TestWithUser, WebTest): def test_approved_application_not_in_active_requests(self): """An approved application is not shown in the Active Requests table on home.html.""" - application = completed_application(status=DomainApplication.APPROVED, user=self.user) + application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED, user=self.user) application.save() home_page = self.app.get("/") diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index 32633a89b..bb1b3aee6 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -293,9 +293,9 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): return self.pending_applications() def approved_applications_exist(self): - """Checks if user is creator of applications with APPROVED status""" + """Checks if user is creator of applications with ApplicationStatus.APPROVED status""" approved_application_count = DomainApplication.objects.filter( - creator=self.request.user, status=DomainApplication.APPROVED + creator=self.request.user, status=DomainApplication.ApplicationStatus.APPROVED ).count() return approved_application_count > 0 @@ -308,11 +308,15 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): def pending_applications(self): """Returns a List of user's applications with one of the following states: - SUBMITTED, IN_REVIEW, ACTION_NEEDED""" - # if the current application has ACTION_NEEDED status, this check should not be performed - if self.application.status == DomainApplication.ACTION_NEEDED: + ApplicationStatus.SUBMITTED, ApplicationStatus.IN_REVIEW, ApplicationStatus.ACTION_NEEDED""" + # if the current application has ApplicationStatus.ACTION_NEEDED status, this check should not be performed + if self.application.status == DomainApplication.ApplicationStatus.ACTION_NEEDED: return [] - check_statuses = [DomainApplication.SUBMITTED, DomainApplication.IN_REVIEW, DomainApplication.ACTION_NEEDED] + check_statuses = [ + DomainApplication.ApplicationStatus.SUBMITTED, + DomainApplication.ApplicationStatus.IN_REVIEW, + DomainApplication.ApplicationStatus.ACTION_NEEDED, + ] return DomainApplication.objects.filter(creator=self.request.user, status__in=check_statuses) def get_context_data(self): diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 81f1d0bb7..5ec4433f7 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -434,7 +434,7 @@ class DomainDsDataView(DomainFormBaseView): return initial_data def get_success_url(self): - """Redirect to the DS Data page for the domain.""" + """Redirect to the DS data page for the domain.""" return reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.object.pk}) def get_context_data(self, **kwargs): @@ -473,7 +473,7 @@ class DomainDsDataView(DomainFormBaseView): modal_button = ( '' + 'name="disable-override-click">Remove all DS data' ) # context to back out of a broken form on all fields delete @@ -523,7 +523,7 @@ class DomainDsDataView(DomainFormBaseView): logger.error(f"Registry error: {err}") return self.form_invalid(formset) else: - messages.success(self.request, "The DS Data records for this domain have been updated.") + messages.success(self.request, "The DS data records for this domain have been updated.") # superclass has the redirect return super().form_valid(formset) diff --git a/src/registrar/views/utility/mixins.py b/src/registrar/views/utility/mixins.py index aaa2e849c..37c0f6e98 100644 --- a/src/registrar/views/utility/mixins.py +++ b/src/registrar/views/utility/mixins.py @@ -101,10 +101,10 @@ class DomainPermission(PermissionsLoginMixin): # Analysts may manage domains, when they are in these statuses: valid_domain_statuses = [ - DomainApplication.APPROVED, - DomainApplication.IN_REVIEW, - DomainApplication.REJECTED, - DomainApplication.ACTION_NEEDED, + DomainApplication.ApplicationStatus.APPROVED, + DomainApplication.ApplicationStatus.IN_REVIEW, + DomainApplication.ApplicationStatus.REJECTED, + DomainApplication.ApplicationStatus.ACTION_NEEDED, # Edge case - some domains do not have # a status or DomainInformation... aka a status of 'None'. # It is necessary to access those to correct errors.