From 52be53778db4a3b65acd1f76dce52fb28987aa72 Mon Sep 17 00:00:00 2001 From: Adam Saghy Date: Mon, 28 Jul 2025 17:24:14 +0200 Subject: [PATCH] FINERACT-2326: Locale Validator shall accept all valid language codes --- .../BusinessDateValidationTest.java | 2 +- .../constraints/LocaleValidator.java | 18 +++++++++- .../constraints/LocaleValidationTest.java | 35 +++++++++++-------- 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/validation/BusinessDateValidationTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/validation/BusinessDateValidationTest.java index c165f4100..761911c03 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/validation/BusinessDateValidationTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/businessdate/validation/BusinessDateValidationTest.java @@ -55,7 +55,7 @@ class BusinessDateValidationTest { @Test void invalidLocale() { var request = BusinessDateUpdateRequest.builder().dateFormat("dd-MM-yyyy").type(BusinessDateType.BUSINESS_DATE).date("12-05-2025") - .locale("EN").build(); + .locale("INVALID").build(); var errors = validator.validateObject(request); diff --git a/fineract-validation/src/main/java/org/apache/fineract/validation/constraints/LocaleValidator.java b/fineract-validation/src/main/java/org/apache/fineract/validation/constraints/LocaleValidator.java index fb410aec4..664559524 100644 --- a/fineract-validation/src/main/java/org/apache/fineract/validation/constraints/LocaleValidator.java +++ b/fineract-validation/src/main/java/org/apache/fineract/validation/constraints/LocaleValidator.java @@ -27,6 +27,22 @@ class LocaleValidator implements ConstraintValidator { @Override public boolean isValid(String value, ConstraintValidatorContext context) { - return StringUtils.isNotBlank(value) && Arrays.asList(java.util.Locale.getISOLanguages()).contains(value); + if (StringUtils.isBlank(value)) { + return false; // empty string is invalid + } + + // Normalize input to use BCP 47 format (e.g., "en-US") + String languageTag = value.replace('_', '-'); + + java.util.Locale inputLocale = java.util.Locale.forLanguageTag(languageTag); + + // If language is empty, it's not a valid locale + if (inputLocale.getLanguage().isEmpty()) { + return false; + } + + // Check if it matches any available locale + return Arrays.stream(java.util.Locale.getAvailableLocales()) + .anyMatch(available -> available.toLanguageTag().equalsIgnoreCase(inputLocale.toLanguageTag())); } } diff --git a/fineract-validation/src/test/java/org/apache/fineract/validation/constraints/LocaleValidationTest.java b/fineract-validation/src/test/java/org/apache/fineract/validation/constraints/LocaleValidationTest.java index 0de66519f..192f59945 100644 --- a/fineract-validation/src/test/java/org/apache/fineract/validation/constraints/LocaleValidationTest.java +++ b/fineract-validation/src/test/java/org/apache/fineract/validation/constraints/LocaleValidationTest.java @@ -27,6 +27,8 @@ import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.validation.config.ValidationConfig; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ContextConfiguration; @@ -51,24 +53,29 @@ class LocaleValidationTest { assertThat(errors.getFieldErrors()).anyMatch(e -> e.getField().equals("locale")); } - @Test - void invalidFormat() { - var request = LocaleModel.builder().locale("EN").build(); - + @ParameterizedTest + @ValueSource(strings = { "invalid-locale", // invalid format + "xx-YY", // non-existent locale + "random text", // random text + "123", // numbers + "en-US-extra" // extra segment + }) + void invalidFormats(String locale) { + var request = LocaleModel.builder().locale(locale).build(); var errors = validator.validateObject(request); - - assertThat(errors.getFieldErrorCount()).isEqualTo(1); - - assertThat(errors.getFieldErrors()).anyMatch(e -> e.getField().equals("locale")); + assertThat(errors.getFieldErrorCount()).as("Expected locale '%s' to be invalid but it was valid", locale).isGreaterThan(0); } - @Test - void valid() { - var request = LocaleModel.builder().locale("en").build(); - + @ParameterizedTest + @ValueSource(strings = { "en", // language only + "EN", // uppercase language only + "en-US", // language with country (hyphen) + "en_US", // language with country (underscore) + }) + void validLocales(String locale) { + var request = LocaleModel.builder().locale(locale).build(); var errors = validator.validateObject(request); - - assertThat(errors.getFieldErrorCount()).isEqualTo(0); + assertThat(errors.getFieldErrorCount()).as("Expected locale '%s' to be valid but it was invalid", locale).isZero(); } @Builder