Skip to content

Commit

Permalink
Character Limits - General Info & Submission Access (#4026)
Browse files Browse the repository at this point in the history
* Character limits - baseline min/max values in JSON

* Character limits on general information fields

* Minor formatting changes for some schemas

* Character limits on Access and Submission Pages

* The super standard linting commit

* Remove irrelevant test

* audit_period_other_months regex - no '0' allowed

* Gen form - display uncaught validation errors

* audit_period_other_months regex OR on two words

* audit_period_other_months display nice regex error

* Linting, yay!

* Remove striptags on audit-period-other-months-error-message

* JS validate handles multiple length comparators

* Apply length validations to "step 3" fields

* space_before_striptags - adds whitespace between form errors
  • Loading branch information
jperson1 authored Jul 1, 2024
1 parent ddc8400 commit 67a8a67
Show file tree
Hide file tree
Showing 31 changed files with 1,111 additions and 157 deletions.
59 changes: 46 additions & 13 deletions backend/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@

from audit.models import SingleAuditChecklist, Access
from api.uei import get_uei_info_from_sam_gov

from audit.validators import (
validate_general_information_json,
)
from config.settings import CHARACTER_LIMITS_GENERAL


# Eligibility step messages
SPENDING_THRESHOLD = _(
Expand Down Expand Up @@ -197,29 +198,61 @@ class Meta:

class AccessAndSubmissionSerializer(serializers.Serializer):
# This serializer isn't tied to a model, so it's just input fields with the below layout
certifying_auditee_contact_fullname = serializers.CharField()
certifying_auditee_contact_email = serializers.EmailField()
certifying_auditor_contact_fullname = serializers.CharField()
certifying_auditor_contact_email = serializers.EmailField()
certifying_auditee_contact_fullname = serializers.CharField(
min_length=CHARACTER_LIMITS_GENERAL["auditee_contact_name"]["min"],
max_length=CHARACTER_LIMITS_GENERAL["auditee_contact_name"]["max"],
)
certifying_auditee_contact_email = serializers.EmailField(
min_length=CHARACTER_LIMITS_GENERAL["auditee_email"]["min"],
max_length=CHARACTER_LIMITS_GENERAL["auditee_email"]["max"],
)
certifying_auditor_contact_fullname = serializers.CharField(
min_length=CHARACTER_LIMITS_GENERAL["auditor_contact_name"]["min"],
max_length=CHARACTER_LIMITS_GENERAL["auditor_contact_name"]["max"],
)
certifying_auditor_contact_email = serializers.EmailField(
min_length=CHARACTER_LIMITS_GENERAL["auditor_email"]["min"],
max_length=CHARACTER_LIMITS_GENERAL["auditor_email"]["max"],
)
auditor_contacts_email = serializers.ListField(
child=serializers.EmailField(required=False, allow_null=True, allow_blank=True),
child=serializers.EmailField(
required=False,
allow_null=True,
allow_blank=True,
min_length=CHARACTER_LIMITS_GENERAL["auditor_email"]["min"],
max_length=CHARACTER_LIMITS_GENERAL["auditor_email"]["max"],
),
allow_empty=True,
min_length=0,
)
auditee_contacts_email = serializers.ListField(
child=serializers.EmailField(required=False, allow_null=True, allow_blank=True),
child=serializers.EmailField(
required=False,
allow_null=True,
allow_blank=True,
min_length=CHARACTER_LIMITS_GENERAL["auditee_email"]["min"],
max_length=CHARACTER_LIMITS_GENERAL["auditee_email"]["max"],
),
allow_empty=True,
min_length=0,
)
auditor_contacts_fullname = serializers.ListField(
child=serializers.CharField(required=False, allow_null=True, allow_blank=True),
child=serializers.CharField(
required=False,
allow_null=True,
allow_blank=True,
min_length=CHARACTER_LIMITS_GENERAL["auditor_contact_name"]["min"],
max_length=CHARACTER_LIMITS_GENERAL["auditor_contact_name"]["max"],
),
allow_empty=True,
min_length=0,
)
auditee_contacts_fullname = serializers.ListField(
child=serializers.CharField(required=False, allow_null=True, allow_blank=True),
child=serializers.CharField(
required=False,
allow_null=True,
allow_blank=True,
min_length=CHARACTER_LIMITS_GENERAL["auditee_contact_name"]["min"],
max_length=CHARACTER_LIMITS_GENERAL["auditee_contact_name"]["max"],
),
allow_empty=True,
min_length=0,
)

def validate(self, data):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
{% endif %}

{% comment %} Auditor Certification {% endcomment %}
<li class="usa-icon-list__item padding-bottom-105"
<li class="usa-icon-list__item padding-bottom-105 margin-top-5"
id="auditor-certification-completed">
{% if certification.auditor_certified %}
{% include './icon-list-icon.html' with completed=certification.auditor_certified %}
Expand Down
11 changes: 0 additions & 11 deletions backend/audit/test_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,6 @@ def test_invalid_auditee_fiscal_period_end(self):
schema,
)

def test_null_auditee_name(self):
"""
If the auditee_name is null, validation should pass
"""
schema = self.GENERAL_INFO_SCHEMA
instance = jsoncopy(self.SIMPLE_CASE)

instance["auditee_name"] = ""

validate(instance, schema)

def test_invalid_ein(self):
"""
If the EIN is not in a valid format, validation should fail
Expand Down
71 changes: 71 additions & 0 deletions backend/audit/test_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -1108,6 +1108,65 @@ def setUp(self):
"audit_period_covered": "Invalid",
}

character_limits = settings.CHARACTER_LIMITS_GENERAL

self.too_short_emails = self.valid_general_information | {
"auditee_email": "a@b",
"auditor_email": "a@b",
}

self.too_long_emails = self.valid_general_information | {
"auditee_email": "a" * character_limits["auditee_email"]["max"]
+ "NowItIsDefinitelyTooLong",
"auditor_email": "a" * character_limits["auditor_email"]["max"]
+ "NowItIsDefinitelyTooLong",
}

self.too_short_addresses = self.valid_general_information | {
"auditee_address_line_1": "a",
"auditee_city": "a",
}

self.too_long_addresses = self.valid_general_information | {
"auditee_address_line_1": "a"
* character_limits["auditee_address_line_1"]["max"]
+ "NowItIsDefinitelyTooLong",
"auditee_city": "a" * character_limits["auditee_city"]["max"]
+ "NowItIsDefinitelyTooLong",
}

self.too_short_names = self.valid_general_information | {
"auditee_name": "a",
"auditee_certify_name": "a",
"auditee_certify_title": "a",
"auditee_contact_name": "a",
"auditor_firm_name": "a",
"auditor_contact_title": "a",
"auditor_contact_name": "a",
}

self.too_long_names = self.valid_general_information | {
"auditee_name": "a" * character_limits["auditee_name"]["max"]
+ "NowItIsDefinitelyTooLong",
"auditee_certify_name": "a"
* character_limits["auditee_certify_name"]["max"]
+ "NowItIsDefinitelyTooLong",
"auditee_certify_title": "a"
* character_limits["auditee_certify_title"]["max"]
+ "NowItIsDefinitelyTooLong",
"auditee_contact_name": "a"
* character_limits["auditee_contact_name"]["max"]
+ "NowItIsDefinitelyTooLong",
"auditor_firm_name": "a" * character_limits["auditor_firm_name"]["max"]
+ "NowItIsDefinitelyTooLong",
"auditor_contact_title": "a"
* character_limits["auditor_contact_title"]["max"]
+ "NowItIsDefinitelyTooLong",
"auditor_contact_name": "a"
* character_limits["auditor_contact_name"]["max"]
+ "NowItIsDefinitelyTooLong",
}

def test_validate_general_information_schema_with_valid_data(self):
"""
Test the validation method with valid general information data.
Expand Down Expand Up @@ -1139,6 +1198,18 @@ def test_validate_general_information_schema_with_invalid_data(self):
validate_general_information_schema(self.invalid_email)
with self.assertRaises(ValidationError):
validate_general_information_schema(self.invalid_audit_period_covered)
with self.assertRaises(ValidationError):
validate_general_information_schema(self.too_short_emails)
with self.assertRaises(ValidationError):
validate_general_information_schema(self.too_long_emails)
with self.assertRaises(ValidationError):
validate_general_information_schema(self.too_short_addresses)
with self.assertRaises(ValidationError):
validate_general_information_schema(self.too_long_addresses)
with self.assertRaises(ValidationError):
validate_general_information_schema(self.too_short_names)
with self.assertRaises(ValidationError):
validate_general_information_schema(self.too_long_names)

def test_validate_general_information_schema_rules_with_valid_data(self):
"""
Expand Down
3 changes: 3 additions & 0 deletions backend/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,9 @@
STATE_ABBREVS = json.load(open(f"{SCHEMA_BASE_DIR}/States.json"))[
"UnitedStatesStateAbbr"
]
CHARACTER_LIMITS_GENERAL = json.load(
open(f"{SCHEMA_BASE_DIR}/character_limits/general.json")
)

ENABLE_DEBUG_TOOLBAR = (
env.bool("ENABLE_DEBUG_TOOLBAR", False) and ENVIRONMENT == "LOCAL" and not TEST_RUN
Expand Down
78 changes: 61 additions & 17 deletions backend/report_submission/forms.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django import forms
from django.core.validators import RegexValidator
from config.settings import STATE_ABBREVS
from config.settings import CHARACTER_LIMITS_GENERAL, STATE_ABBREVS

from api.uei import get_uei_info_from_sam_gov

Expand All @@ -15,7 +15,7 @@
)
date_validator = RegexValidator(
r"^([0-9]{2}/[0-9]{2}/[0-9]{4})|([0-9]{4}-[0-9]{2}-[0-9]{4})$",
"Dates should be in the format 00/00/0000",
"Dates should be in the format MM/DD/YYYY",
)
ein_validator = RegexValidator(
r"^[0-9]{9}$", "EINs should be nine characters long and be made up of only numbers."
Expand All @@ -27,6 +27,10 @@
zip_validator = RegexValidator(
r"^[0-9]{5}(?:[0-9]{4})?$", "Zip codes should be in the format 12345 or 12345-1234."
)
audit_period_other_months_validator = RegexValidator(
r"^0?[1-9]$|^1[0-8]$",
"The audit period should be between 1 and 18 months, with an optional leading zero.",
)


def validate_uei(value):
Expand Down Expand Up @@ -59,7 +63,6 @@ def clean(self):


class GeneralInformationForm(forms.Form):
max_string_length = 100
foreign_address_max_length = 500
choices_state_abbrevs = list((i, i) for i in STATE_ABBREVS)

Expand All @@ -71,7 +74,12 @@ class GeneralInformationForm(forms.Form):
required=False, validators=[date_validator]
)
audit_period_covered = forms.CharField(required=False)
audit_period_other_months = forms.CharField(required=False)
audit_period_other_months = forms.CharField(
min_length=CHARACTER_LIMITS_GENERAL["number_months"]["min"],
max_length=CHARACTER_LIMITS_GENERAL["number_months"]["max"],
required=False,
validators=[audit_period_other_months_validator],
)
ein = forms.CharField(
required=False,
validators=[ein_validator], # validators are not run against empty fields
Expand All @@ -80,47 +88,83 @@ class GeneralInformationForm(forms.Form):
multiple_eins_covered = forms.BooleanField(required=False)
auditee_uei = forms.CharField(required=False)
multiple_ueis_covered = forms.BooleanField(required=False)
auditee_name = forms.CharField(max_length=max_string_length, required=False)
auditee_name = forms.CharField(
min_length=CHARACTER_LIMITS_GENERAL["auditee_name"]["min"],
max_length=CHARACTER_LIMITS_GENERAL["auditee_name"]["max"],
required=False,
)
auditee_address_line_1 = forms.CharField(
max_length=max_string_length, required=False
min_length=CHARACTER_LIMITS_GENERAL["auditee_address_line_1"]["min"],
max_length=CHARACTER_LIMITS_GENERAL["auditee_address_line_1"]["max"],
required=False,
)
auditee_city = forms.CharField(
max_length=max_string_length,
min_length=CHARACTER_LIMITS_GENERAL["auditee_city"]["min"],
max_length=CHARACTER_LIMITS_GENERAL["auditee_city"]["max"],
required=False,
validators=[alpha_validator], # validators are not run against empty fields
)
auditee_state = forms.ChoiceField(choices=choices_state_abbrevs, required=False)
auditee_zip = forms.CharField(required=False, validators=[zip_validator])
auditee_contact_name = forms.CharField(max_length=max_string_length, required=False)
auditee_contact_name = forms.CharField(
min_length=CHARACTER_LIMITS_GENERAL["auditee_contact_name"]["min"],
max_length=CHARACTER_LIMITS_GENERAL["auditee_contact_name"]["max"],
required=False,
)
auditee_contact_title = forms.CharField(
max_length=max_string_length, required=False
min_length=CHARACTER_LIMITS_GENERAL["auditee_contact_title"]["min"],
max_length=CHARACTER_LIMITS_GENERAL["auditee_contact_title"]["max"],
required=False,
)
auditee_phone = forms.CharField(required=False, validators=[phone_validator])
auditee_email = forms.CharField(max_length=max_string_length, required=False)
auditor_firm_name = forms.CharField(max_length=max_string_length, required=False)
auditee_email = forms.CharField(
min_length=CHARACTER_LIMITS_GENERAL["auditee_email"]["min"],
max_length=CHARACTER_LIMITS_GENERAL["auditee_email"]["max"],
required=False,
)
auditor_firm_name = forms.CharField(
min_length=CHARACTER_LIMITS_GENERAL["auditor_firm_name"]["min"],
max_length=CHARACTER_LIMITS_GENERAL["auditor_firm_name"]["max"],
required=False,
)
auditor_ein = forms.CharField(
required=False,
validators=[ein_validator], # validators are not run against empty fields
)
auditor_ein_not_an_ssn_attestation = forms.BooleanField(required=False)
auditor_country = forms.CharField(required=False)
auditor_international_address = forms.CharField(
max_length=foreign_address_max_length, required=False
min_length=CHARACTER_LIMITS_GENERAL["auditor_foreign_address"]["min"],
max_length=CHARACTER_LIMITS_GENERAL["auditor_foreign_address"]["max"],
required=False,
)
auditor_address_line_1 = forms.CharField(
max_length=max_string_length, required=False
min_length=CHARACTER_LIMITS_GENERAL["auditor_address_line_1"]["min"],
max_length=CHARACTER_LIMITS_GENERAL["auditor_address_line_1"]["max"],
required=False,
)
auditor_city = forms.CharField(
max_length=max_string_length,
min_length=CHARACTER_LIMITS_GENERAL["auditor_city"]["min"],
max_length=CHARACTER_LIMITS_GENERAL["auditor_city"]["max"],
required=False,
validators=[alpha_validator], # validators are not run against empty fields
)
auditor_state = forms.ChoiceField(choices=choices_state_abbrevs, required=False)
auditor_zip = forms.CharField(required=False, validators=[zip_validator])
auditor_contact_name = forms.CharField(max_length=max_string_length, required=False)
auditor_contact_name = forms.CharField(
min_length=CHARACTER_LIMITS_GENERAL["auditor_contact_name"]["min"],
max_length=CHARACTER_LIMITS_GENERAL["auditor_contact_name"]["max"],
required=False,
)
auditor_contact_title = forms.CharField(
max_length=max_string_length, required=False
min_length=CHARACTER_LIMITS_GENERAL["auditor_contact_title"]["min"],
max_length=CHARACTER_LIMITS_GENERAL["auditor_contact_title"]["max"],
required=False,
)
auditor_phone = forms.CharField(required=False, validators=[phone_validator])
auditor_email = forms.CharField(max_length=max_string_length, required=False)
auditor_email = forms.CharField(
min_length=CHARACTER_LIMITS_GENERAL["auditor_email"]["min"],
max_length=CHARACTER_LIMITS_GENERAL["auditor_email"]["max"],
required=False,
)
secondary_auditors_exist = forms.BooleanField(required=False)
Loading

0 comments on commit 67a8a67

Please sign in to comment.