Skip to content

Commit

Permalink
feat: permit more names in YouthApplication (first|last)_name & school
Browse files Browse the repository at this point in the history
these now accept any other strings except empty/whitespace only strings:
 - YouthApplication.first_name
 - YouthApplication.last_name
 - YouthApplication.school

refs YJDH-661
  • Loading branch information
karisal-anders committed Feb 14, 2024
1 parent f8d46a0 commit cc10fb1
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,25 @@
from applications.enums import AttachmentType, EmployerApplicationStatus
from applications.models import School, validate_name, YouthApplication
from applications.tests.test_applications_api import get_detail_url
from shared.common.tests.names import INVALID_NAMES, VALID_NAMES


@pytest.mark.django_db
def test_validate_name_with_all_listed_schools():
def test_validate_name_with_all_listed_schools(school_list):
for school in School.objects.all():
validate_name(school.name)


@pytest.mark.django_db
@pytest.mark.parametrize(
"name",
[
VALID_NAMES
+ [
"Jokin muu koulu",
"Testikoulu",
"Testikoulu 1",
"Testikoulu: Arabian yläaste",
"Yläaste (Arabia)",
],
)
def test_validate_name_with_valid_unlisted_school(name):
Expand Down Expand Up @@ -74,14 +79,7 @@ def clean_vtj_json_field():


@pytest.mark.django_db
@pytest.mark.parametrize(
"name",
[
"Testikoulu 1", # Number is not allowed after the first character
"Testikoulu: Arabian yläaste", # Colon is not allowed
"Yläaste (Arabia)", # Parentheses are not allowed
],
)
@pytest.mark.parametrize("name", INVALID_NAMES)
def test_validate_name_with_invalid_unlisted_school(name):
with pytest.raises(ValidationError):
validate_name(name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,13 @@
superuser_client,
)
from shared.common.tests.factories import UserFactory
from shared.common.tests.names import INVALID_NAMES, VALID_NAMES
from shared.common.tests.test_validators import get_invalid_postcode_values
from shared.common.tests.utils import normalize_whitespace

# YouthApplication's fields that are validated as names
YOUTH_APPLICATION_NAME_FIELDS = ["first_name", "last_name", "school"]

# Mandatory fields of YouthSummerVoucher in youth summer voucher email
MANDATORY_YOUTH_SUMMER_VOUCHER_FIELDS_IN_VOUCHER_EMAIL = [
"employer_summer_voucher_application_end_date_localized_string",
Expand Down Expand Up @@ -1306,6 +1310,57 @@ def test_youth_application_post_valid_random_data( # noqa: C901
), f"{read_only_field} created youth application attribute incorrect"


@override_settings(
NEXT_PUBLIC_MOCK_FLAG=False,
NEXT_PUBLIC_DISABLE_VTJ=True,
)
@pytest.mark.django_db
@pytest.mark.parametrize("name_field", YOUTH_APPLICATION_NAME_FIELDS)
@pytest.mark.parametrize("name", VALID_NAMES)
def test_youth_application_post_valid_non_ascii_names(api_client, name_field, name):
youth_application = YouthApplicationFactory.build()
data = YouthApplicationSerializer(youth_application).data
data[name_field] = name
response = api_client.post(get_list_url(), data)

assert response.status_code == status.HTTP_201_CREATED
assert "id" in response.data
created_youth_application = YouthApplication.objects.get(pk=response.data["id"])
assert getattr(created_youth_application, name_field) == name


@override_settings(
NEXT_PUBLIC_MOCK_FLAG=False,
NEXT_PUBLIC_DISABLE_VTJ=True,
)
@pytest.mark.django_db
@pytest.mark.parametrize("name_field", YOUTH_APPLICATION_NAME_FIELDS)
@pytest.mark.parametrize("name", VALID_NAMES)
def test_youth_application_post_names_with_whitespace(api_client, name_field, name):
youth_application = YouthApplicationFactory.build()
data = YouthApplicationSerializer(youth_application).data
whitespace = "\t\r\n "
data[name_field] = whitespace + name + whitespace
response = api_client.post(get_list_url(), data)

assert response.status_code == status.HTTP_201_CREATED
assert "id" in response.data
created_youth_application = YouthApplication.objects.get(pk=response.data["id"])
assert getattr(created_youth_application, name_field) == name.strip()


@pytest.mark.django_db
@pytest.mark.parametrize("name_field", YOUTH_APPLICATION_NAME_FIELDS)
@pytest.mark.parametrize("name", INVALID_NAMES)
def test_youth_application_post_invalid_names(api_client, name_field, name):
youth_application = YouthApplicationFactory.build()
data = YouthApplicationSerializer(youth_application).data
data[name_field] = name
response = api_client.post(get_list_url(), data)

assert response.status_code == status.HTTP_400_BAD_REQUEST


@override_settings(
NEXT_PUBLIC_MOCK_FLAG=False,
NEXT_PUBLIC_DISABLE_VTJ=True,
Expand Down Expand Up @@ -2528,6 +2583,7 @@ def test_youth_applications_set_excess_additional_info(
[AdditionalInfoUserReason.OTHER],
" \tLeading\n and trailing whitespace should get removed \n\t ",
),
*[([AdditionalInfoUserReason.OTHER], name) for name in VALID_NAMES],
]
],
)
Expand Down
59 changes: 59 additions & 0 deletions backend/shared/shared/common/tests/names.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
ARABIC_NAME = "حَسَّان" # Ḥassān (benefactor) in Arabic
CHINESE_NAME = "慧芬" # Huì Fēn (wise scent) in Mandarin Chinese
ESTONIAN_NAME = "Õras"
FINNISH_NAME = "Matti Meikäläinen"
GERMAN_NAME = "Strauß Jünemann"
HEBREW_NAME = "אברהם" # Abraham (father of many) in Hebrew
ICELANDIC_FEMALE_NAME = "María Kristín Þorkelsdóttir"
ICELANDIC_MALE_NAME = "Ingólfur Álfheiður"
RUSSIAN_NAME = "Мельник" # Melnik (miller) in Russian
SHORT_CHINESE_NAME = "王" # Wáng (king) in Mandarin Chinese
SPANISH_NAME = "Peña"
SWEDISH_NAME = "Åse-Marie Öllegård"
THAI_NAME = "อาทิตย์" # Arthit (sun) in Thai
TURKISH_NAME = "Ümit" # Ümit (hope) in Turkish

VALID_NAMES = [
# should match Finnish first names, last names and full names
"Helinä",
"Aalto",
"Kalle Väyrynen",
"Janne Ö",
# should match Swedish first names, last names and full names
"Gun-Britt",
"Lindén",
"Ögge Ekström",
# should match English first names, last names and full names
"Eric",
"Bradtke",
"Daniela O'Brian",
# should match special characters
"!@#$%^&*()_+-=[]{}|;':\",./<>?",
# should match digits
"1234567890",
# should match more languages than just Finnish, Swedish, English
ARABIC_NAME,
CHINESE_NAME,
ESTONIAN_NAME,
FINNISH_NAME,
GERMAN_NAME,
HEBREW_NAME,
ICELANDIC_FEMALE_NAME,
ICELANDIC_MALE_NAME,
RUSSIAN_NAME,
SHORT_CHINESE_NAME,
SPANISH_NAME,
SWEDISH_NAME,
THAI_NAME,
TURKISH_NAME,
]

INVALID_NAMES = [
"",
" ",
"\t",
"\r",
"\n",
"\r\n",
" \t\r\n ",
]
30 changes: 6 additions & 24 deletions backend/shared/shared/common/tests/test_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.core.exceptions import ValidationError
from django.db import models

from shared.common.tests.names import INVALID_NAMES, VALID_NAMES
from shared.common.validators import (
validate_json,
validate_name,
Expand Down Expand Up @@ -136,37 +137,18 @@ def test_validate_phone_number_with_invalid_input(value):


# Based on frontend/shared/src/__tests__/constants.test.ts
@pytest.mark.parametrize(
"value",
[
# should match Finnish first names, last names and full names
"Helinä",
"Aalto",
"Kalle Väyrynen",
"Janne Ö",
# should match Swedish first names, last names and full names
"Gun-Britt",
"Lindén",
"Ögge Ekström",
# should match English first names, last names and full names
"Eric",
"Bradtke",
"Daniela O'Brian",
],
)
@pytest.mark.parametrize("value", VALID_NAMES)
def test_validate_name_with_valid_input(value):
validate_name(value)


# Based on frontend/shared/src/__tests__/constants.test.ts
@pytest.mark.parametrize(
"value",
[
# should fail to match invalid characters
"!@#$%^&*()_+-=[]{}|;':\",./<>?",
# should fail to match digits
"1234567890",
],
INVALID_NAMES
+ [" " + name for name in VALID_NAMES]
+ [name + " " for name in VALID_NAMES]
+ [" " + name + " " for name in VALID_NAMES],
)
def test_validate_name_with_invalid_input(value):
with pytest.raises(ValidationError):
Expand Down
16 changes: 7 additions & 9 deletions backend/shared/shared/common/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,10 @@
# \d in Javascript matches [0-9] and has been replaced:
POSTAL_CODE_REGEX = r"^[0-9]{5}$"

# \w in Javascript matches [A-Za-z0-9_] and has been replaced:
NAMES_REGEX = r"^[A-Za-z0-9_',.ÄÅÖäåö-][^\d!#$%&()*+/:;<=>?@[\\\]_{|}~¡¿÷ˆ]+$"

# Please note that using a RegexValidator in a Django model field hardcodes the used
# regular expression into the model and its migration but using a function does not.
PHONE_NUMBER_REGEX_VALIDATOR = RegexValidator(PHONE_NUMBER_REGEX)
POSTAL_CODE_REGEX_VALIDATOR = RegexValidator(POSTAL_CODE_REGEX)
NAMES_REGEX_VALIDATOR = RegexValidator(NAMES_REGEX)


def validate_phone_number(phone_number) -> None:
Expand All @@ -52,13 +48,15 @@ def validate_postcode(postcode) -> None:

def validate_name(name) -> None:
"""
Function wrapper for NAMES_REGEX_VALIDATOR. If used as a validator in a
Django model's field this does not hardcode the underlying regular expression into
the migration nor into the model.
Validates name to be a non-empty string with no trailing or leading whitespace.
Raise ValidationError if the given value doesn't pass NAMES_REGEX_VALIDATOR.
Raise ValidationError if the given value is not a non-empty string with no trailing
or leading whitespace.
"""
NAMES_REGEX_VALIDATOR(name)
if not (isinstance(name, str) and name == name.strip() and name.strip()):
raise ValidationError(
"Name must be a non-empty string with no trailing or leading whitespace"
)


def validate_json(value) -> None:
Expand Down

0 comments on commit cc10fb1

Please sign in to comment.