diff --git a/ramls/acq-models b/ramls/acq-models index 4819a57e9..82e7e5606 160000 --- a/ramls/acq-models +++ b/ramls/acq-models @@ -1 +1 @@ -Subproject commit 4819a57e98a7ff795735248e935667dcd3a40902 +Subproject commit 82e7e5606af023935220552ef00391b13f192229 diff --git a/src/main/java/org/folio/rest/core/exceptions/ErrorCodes.java b/src/main/java/org/folio/rest/core/exceptions/ErrorCodes.java index dc7eca941..0fccb9089 100644 --- a/src/main/java/org/folio/rest/core/exceptions/ErrorCodes.java +++ b/src/main/java/org/folio/rest/core/exceptions/ErrorCodes.java @@ -125,6 +125,7 @@ public enum ErrorCodes { FUND_LOCATION_RESTRICTION_VIOLATION("fundLocationRestrictionViolation", "One of the locations is restricted to be used by all funds."), ENCUMBRANCES_FOR_RE_ENCUMBER_NOT_FOUND("encumbrancesForReEncumberNotFound", "The encumbrances were correctly created during the rollover or have already been updated."), CLAIMING_CONFIG_INVALID("claimingConfigInvalid", "Claiming interval should be set and greater than 0 if claiming is active"), + POL_NUMBER_INVALID_OR_TOO_LONG("polNumberInvalidOrTooLong", "POL number is invalid or bigger than 26 symbols"), TEMPLATE_NAME_ALREADY_EXISTS("templateNameNotUnique", "Template name already exists"), BARCODE_IS_NOT_UNIQUE("barcodeIsNotUnique", "The barcode already exists. The barcode must be unique"), DELETE_WITH_EXPENDED_AMOUNT("deleteWithExpendedAmount", "Cannot delete an encumbrance with an expended amount"), diff --git a/src/main/java/org/folio/service/titles/TitleValidationService.java b/src/main/java/org/folio/service/titles/TitleValidationService.java index bc167ce40..b1a57a8c4 100644 --- a/src/main/java/org/folio/service/titles/TitleValidationService.java +++ b/src/main/java/org/folio/service/titles/TitleValidationService.java @@ -1,13 +1,19 @@ package org.folio.service.titles; +import org.apache.commons.lang3.StringUtils; import org.folio.rest.core.exceptions.ErrorCodes; import org.folio.rest.jaxrs.model.Error; import org.folio.rest.jaxrs.model.Title; import org.folio.service.orders.BaseValidationService; +import java.util.ArrayList; import java.util.List; +import java.util.regex.Pattern; public class TitleValidationService extends BaseValidationService { + // Pattern matches the JSON schema definition in acq-models/mod-orders-storage/schemas/po_line.json + private static final Pattern POL_NUMBER_PATTERN = Pattern.compile("^[a-zA-Z0-9]{1,22}-[0-9]{1,3}$"); + /** * Validates title. * @@ -15,10 +21,21 @@ public class TitleValidationService extends BaseValidationService { * @return list of errors or empty list in case if title is valid */ public List validateTitle(Title title) { - List errorCodes = checkClaimingConfig(title.getClaimingActive(), title.getClaimingInterval()); + List errorCodes = new ArrayList<>(checkClaimingConfig(title.getClaimingActive(), title.getClaimingInterval())); + errorCodes.addAll(validatePoLineNumber(title.getPoLineNumber())); return convertErrorCodesToErrors(errorCodes); } + private List validatePoLineNumber(String poLineNumber) { + List errors = new ArrayList<>(); + + if (StringUtils.isNotEmpty(poLineNumber) && !POL_NUMBER_PATTERN.matcher(poLineNumber).matches()) { + errors.add(ErrorCodes.POL_NUMBER_INVALID_OR_TOO_LONG); + } + + return errors; + } + private List convertErrorCodesToErrors(List errorCodes) { return errorCodes.stream().map(ErrorCodes::toError).toList(); } diff --git a/src/test/java/org/folio/rest/impl/PoNumberApiTest.java b/src/test/java/org/folio/rest/impl/PoNumberApiTest.java index 0259b609d..44445c34f 100644 --- a/src/test/java/org/folio/rest/impl/PoNumberApiTest.java +++ b/src/test/java/org/folio/rest/impl/PoNumberApiTest.java @@ -81,10 +81,9 @@ void testPoNumberValidateWithUniquePONumber() } @Test - void testPoNumberValidateWithInvalidPattern() - { + void testPoNumberValidateWithInvalidPattern() { JsonObject poNumber=new JsonObject(); - poNumber.put(PO_NUMBER, "1111111111111111111111"); + poNumber.put(PO_NUMBER, "12345678901234567890123"); // 23 characters - exceeds limit of 22 verifyPostResponse(PONUMBER_VALIDATE_PATH, poNumber.encodePrettily(), prepareHeaders(EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10), APPLICATION_JSON, 422); } diff --git a/src/test/java/org/folio/service/titles/TitleValidationServiceTest.java b/src/test/java/org/folio/service/titles/TitleValidationServiceTest.java new file mode 100644 index 000000000..0cf816878 --- /dev/null +++ b/src/test/java/org/folio/service/titles/TitleValidationServiceTest.java @@ -0,0 +1,122 @@ +package org.folio.service.titles; + +import static org.folio.rest.core.exceptions.ErrorCodes.POL_NUMBER_INVALID_OR_TOO_LONG; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import org.folio.rest.jaxrs.model.Error; +import org.folio.rest.jaxrs.model.Title; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("Title Validation Service Unit Tests") +class TitleValidationServiceTest { + + private TitleValidationService titleValidationService; + + @BeforeEach + void setUp() { + titleValidationService = new TitleValidationService(); + } + + @Test + @DisplayName("Should pass validation for valid POL number") + void testValidPoLineNumber() { + Title title = new Title().withPoLineNumber("ABC123-1"); + List errors = titleValidationService.validateTitle(title); + assertThat(errors, is(empty())); + } + + @Test + @DisplayName("Should pass validation for POL number with 22 character prefix") + void testPoLineNumberWith22CharPrefix() { + Title title = new Title().withPoLineNumber("1234567890123456789012-999"); + List errors = titleValidationService.validateTitle(title); + assertThat(errors, is(empty())); + } + + @Test + @DisplayName("Should fail validation when POL number prefix exceeds 22 characters") + void testPoLineNumberPrefixTooLong() { + // Given - 23 character prefix + Title title = new Title().withPoLineNumber("12345678901234567890123-1"); + List errors = titleValidationService.validateTitle(title); + assertThat(errors, hasSize(1)); + assertEquals(POL_NUMBER_INVALID_OR_TOO_LONG.toError().getCode(), errors.get(0).getCode()); + assertEquals("POL number is invalid or bigger than 26 symbols", errors.get(0).getMessage()); + } + + @Test + @DisplayName("Should fail validation for POL number with invalid format (no dash)") + void testPoLineNumberNoDash() { + Title title = new Title().withPoLineNumber("ABC1231"); + List errors = titleValidationService.validateTitle(title); + assertThat(errors, hasSize(1)); + assertEquals(POL_NUMBER_INVALID_OR_TOO_LONG.toError().getCode(), errors.get(0).getCode()); + } + + @Test + @DisplayName("Should fail validation for POL number with special characters") + void testPoLineNumberWithSpecialCharacters() { + Title title = new Title().withPoLineNumber("ABC!@#-1"); + List errors = titleValidationService.validateTitle(title); + assertThat(errors, hasSize(1)); + assertEquals(POL_NUMBER_INVALID_OR_TOO_LONG.toError().getCode(), errors.get(0).getCode()); + } + + @Test + @DisplayName("Should fail validation when POL suffix is non-numeric") + void testPoLineNumberNonNumericSuffix() { + Title title = new Title().withPoLineNumber("ABC123-ABC"); + List errors = titleValidationService.validateTitle(title); + assertThat(errors, hasSize(1)); + assertEquals(POL_NUMBER_INVALID_OR_TOO_LONG.toError().getCode(), errors.get(0).getCode()); + } + + @Test + @DisplayName("Should fail validation when POL suffix exceeds 3 digits") + void testPoLineNumberSuffixTooLong() { + Title title = new Title().withPoLineNumber("ABC123-1234"); + List errors = titleValidationService.validateTitle(title); + assertThat(errors, hasSize(1)); + assertEquals(POL_NUMBER_INVALID_OR_TOO_LONG.toError().getCode(), errors.get(0).getCode()); + } + + @Test + @DisplayName("Should pass validation for null POL number") + void testNullPoLineNumber() { + Title title = new Title().withPoLineNumber(null); + List errors = titleValidationService.validateTitle(title); + assertThat(errors, is(empty())); + } + + @Test + @DisplayName("Should pass validation for empty POL number") + void testEmptyPoLineNumber() { + Title title = new Title().withPoLineNumber(""); + List errors = titleValidationService.validateTitle(title); + assertThat(errors, is(empty())); + } + + @Test + @DisplayName("Should pass validation for POL number with minimum length") + void testPoLineNumberMinimumLength() { + Title title = new Title().withPoLineNumber("A-1"); + List errors = titleValidationService.validateTitle(title); + assertThat(errors, is(empty())); + } + + @Test + @DisplayName("Should pass validation for POL number with alphanumeric prefix") + void testPoLineNumberAlphanumericPrefix() { + Title title = new Title().withPoLineNumber("Mon123Ogr456-999"); + List errors = titleValidationService.validateTitle(title); + assertThat(errors, is(empty())); + } +}