From cd40ebff766def14f2c35e644f8aea27cfbef726 Mon Sep 17 00:00:00 2001 From: Pavlo Smahin Date: Mon, 29 Jun 2026 16:22:30 +0300 Subject: [PATCH 1/2] feat(packages): update package post request with new attributes Closes: MODKBEKBJ-823 --- NEWS.md | 1 + pom.xml | 2 + .../packages/packagePostDataAttributes.json | 49 +++- ramls/types/proxies/proxyUrl.json | 2 +- ramls/types/proxy.json | 2 +- ramls/types/proxyPut.json | 15 -- ramls/types/tokenPut.json | 15 -- src/main/java/org/folio/common/ListUtils.java | 11 +- .../converter/common/attr/ProxyConverter.java | 22 -- .../CommonPackagePutRequestConverter.java | 81 +++++++ .../CustomPackagePostRequestConverter.java | 63 +++++ .../CustomPackagePutRequestConverter.java | 24 ++ .../ManagedPackagePutRequestConverter.java | 27 +++ .../converter/packages/PackageConverter.java | 7 +- .../packages/PackagePostRequestConverter.java | 33 --- .../packages/PackageRequestConverter.java | 91 ------- .../PackageRequestConvertionService.java | 29 +++ .../providers/ProviderConverter.java | 13 +- .../ProviderPutRequestConverter.java | 3 +- .../resources/CommonResourceConverter.java | 17 +- .../resources/ResourceRequestConverter.java | 21 +- .../rest/impl/EholdingsPackagesImpl.java | 33 +-- .../validator/PackagesPostBodyValidator.java | 55 ----- .../folio/rest/validator/ValidatorUtil.java | 10 + .../CustomPackagePutBodyValidator.java | 31 +-- .../ManagedPackagePutBodyValidator.java} | 41 ++-- .../PackageCustomAttributesValidator.java | 102 ++++++++ .../PackageTagsPutBodyValidator.java | 3 +- .../packages/PackageValidationService.java | 33 +++ .../packages/PackagesPostBodyValidator.java | 53 ++++ ...CustomPackagePostRequestConverterTest.java | 149 ++++++++++++ .../CustomPackagePutRequestConverterTest.java | 68 ++++++ ...ManagedPackagePutRequestConverterTest.java | 57 +++++ .../packages/PackageRequestConverterTest.java | 116 --------- .../ResourceRequestConverterTest.java | 9 +- .../validator/PackagePostValidatorTest.java | 228 ------------------ .../CustomPackagePutBodyValidatorTest.java | 41 ++-- .../ManagedPackagePutBodyValidatorTest.java} | 66 +++-- .../PackageCustomAttributesValidatorTest.java | 225 +++++++++++++++++ .../packages/PackagePostValidatorTest.java | 104 ++++++++ .../PackageTagsPutBodyValidatorTest.java | 8 +- .../package/post-package-request.json | 21 +- 42 files changed, 1251 insertions(+), 730 deletions(-) delete mode 100644 ramls/types/proxyPut.json delete mode 100644 ramls/types/tokenPut.json delete mode 100644 src/main/java/org/folio/rest/converter/common/attr/ProxyConverter.java create mode 100644 src/main/java/org/folio/rest/converter/packages/CommonPackagePutRequestConverter.java create mode 100644 src/main/java/org/folio/rest/converter/packages/CustomPackagePostRequestConverter.java create mode 100644 src/main/java/org/folio/rest/converter/packages/CustomPackagePutRequestConverter.java create mode 100644 src/main/java/org/folio/rest/converter/packages/ManagedPackagePutRequestConverter.java delete mode 100644 src/main/java/org/folio/rest/converter/packages/PackagePostRequestConverter.java delete mode 100644 src/main/java/org/folio/rest/converter/packages/PackageRequestConverter.java create mode 100644 src/main/java/org/folio/rest/converter/packages/PackageRequestConvertionService.java delete mode 100644 src/main/java/org/folio/rest/validator/PackagesPostBodyValidator.java rename src/main/java/org/folio/rest/validator/{ => packages}/CustomPackagePutBodyValidator.java (54%) rename src/main/java/org/folio/rest/validator/{PackagePutBodyValidator.java => packages/ManagedPackagePutBodyValidator.java} (56%) create mode 100644 src/main/java/org/folio/rest/validator/packages/PackageCustomAttributesValidator.java rename src/main/java/org/folio/rest/validator/{ => packages}/PackageTagsPutBodyValidator.java (92%) create mode 100644 src/main/java/org/folio/rest/validator/packages/PackageValidationService.java create mode 100644 src/main/java/org/folio/rest/validator/packages/PackagesPostBodyValidator.java create mode 100644 src/test/java/org/folio/rest/converter/packages/CustomPackagePostRequestConverterTest.java create mode 100644 src/test/java/org/folio/rest/converter/packages/CustomPackagePutRequestConverterTest.java create mode 100644 src/test/java/org/folio/rest/converter/packages/ManagedPackagePutRequestConverterTest.java delete mode 100644 src/test/java/org/folio/rest/converter/packages/PackageRequestConverterTest.java delete mode 100644 src/test/java/org/folio/rest/validator/PackagePostValidatorTest.java rename src/test/java/org/folio/rest/validator/{ => packages}/CustomPackagePutBodyValidatorTest.java (60%) rename src/test/java/org/folio/rest/validator/{PackagePutBodyValidatorTest.java => packages/ManagedPackagePutBodyValidatorTest.java} (68%) create mode 100644 src/test/java/org/folio/rest/validator/packages/PackageCustomAttributesValidatorTest.java create mode 100644 src/test/java/org/folio/rest/validator/packages/PackagePostValidatorTest.java rename src/test/java/org/folio/rest/validator/{ => packages}/PackageTagsPutBodyValidatorTest.java (83%) diff --git a/NEWS.md b/NEWS.md index 94134d6c2..2d01529f9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -8,6 +8,7 @@ ### Features * Migrate Package Endpoints to HoldingsIQ v2 API ([MODKBEKBJ-804](https://folio-org.atlassian.net/browse/MODKBEKBJ-804)) +* Add additional fields to POST /eholdings/packages endpoint ([MODKBEKBJ-823](https://folio-org.atlassian.net/browse/MODKBEKBJ-823)) ### Bug fixes * Fix offset handling when retrieving holdings from HoldingsIQ. ([MODKBEKBJ-825](https://folio-org.atlassian.net/browse/MODKBEKBJ-825)) diff --git a/pom.xml b/pom.xml index 1ef2afe8f..d6aa1e9ab 100644 --- a/pom.xml +++ b/pom.xml @@ -616,6 +616,7 @@ true + ${project.build.directory}/${project.artifactId}-fat.jar @@ -627,6 +628,7 @@ **/log4j.properties **/Log4j2Plugins.dat + module-info.class diff --git a/ramls/types/packages/packagePostDataAttributes.json b/ramls/types/packages/packagePostDataAttributes.json index 3e372c3c6..6a5d9145d 100644 --- a/ramls/types/packages/packagePostDataAttributes.json +++ b/ramls/types/packages/packagePostDataAttributes.json @@ -6,26 +6,57 @@ "javaType": "org.folio.rest.jaxrs.model.PackagePostDataAttributes", "additionalProperties": false, "properties": { - "name": { + "accessTypeId": { "type": "string", - "description": "Package name", - "example": "Advanced Placement Psychology Collection" + "description": "Access type id", + "$ref": "../../raml-util/schemas/uuid.schema", + "example": "f973c3b6-85fc-4d35-bda8-f31b568957bf" }, "contentType": { "type": "string", "description": "Package content type", "$ref": "contentTypeEnum.json" - }, - "accessTypeId": { - "type": "string", - "description": "Access type id", - "$ref": "../../raml-util/schemas/uuid.schema", - "example": "f973c3b6-85fc-4d35-bda8-f31b568957bf" + }, + "customAltNames": { + "description": "The list of custom alternate names for the package", + "items": { + "$ref": "packageAltName.json", + "type": "object" + }, + "type": "array" }, "customCoverage": { "type": "object", "description": "Custom Coverage", "$ref": "../coverage.json" + }, + "customDescription": { + "description": "The custom description of the package", + "example": "Package of medicinal journals (custom)", + "type": "string" + }, + "customDisplayName": { + "description": "The custom description of the package", + "type": "string" + }, + "isFreeAccess": { + "description": "Whether this package has free access designation or not", + "example": false, + "type": "boolean" + }, + "name": { + "type": "string", + "description": "Package name", + "example": "Advanced Placement Psychology Collection" + }, + "proxy": { + "$ref": "../proxy.json", + "description": "Proxy", + "type": "object" + }, + "url": { + "description": "Package URL", + "type": "string" } }, "required": [ diff --git a/ramls/types/proxies/proxyUrl.json b/ramls/types/proxies/proxyUrl.json index 87a11ea4d..3265dd1ae 100644 --- a/ramls/types/proxies/proxyUrl.json +++ b/ramls/types/proxies/proxyUrl.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-04/schema#", "title": "Proxy Url Schema", "description": "Proxy Url Schema", - "javaType": "org.folio.rest.jaxrs.model.ProxyUrl", + "javaType": "org.folio.rest.jaxrs.model.ProxyUrlDto", "type": "object", "additionalProperties": false, "extends": { diff --git a/ramls/types/proxy.json b/ramls/types/proxy.json index 49c62b12e..60dd01ec0 100644 --- a/ramls/types/proxy.json +++ b/ramls/types/proxy.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-04/schema#", "title": "Proxy Schema", "description": "Proxy Schema", - "javaType": "org.folio.rest.jaxrs.model.Proxy", + "javaType": "org.folio.rest.jaxrs.model.ProxyDto", "type": "object", "additionalProperties": true, "properties": { diff --git a/ramls/types/proxyPut.json b/ramls/types/proxyPut.json deleted file mode 100644 index 8b9331988..000000000 --- a/ramls/types/proxyPut.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Proxy Schema", - "description": "Proxy Schema", - "javaType": "org.folio.rest.jaxrs.model.Proxy", - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "type": "string", - "description": "Proxy Id", - "example": "EZProxy" - } - } -} diff --git a/ramls/types/tokenPut.json b/ramls/types/tokenPut.json deleted file mode 100644 index b67658b3a..000000000 --- a/ramls/types/tokenPut.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Token Put Schema", - "description": "Token Put Schema", - "javaType": "org.folio.rest.jaxrs.model.TokenPut", - "type": "object", - "additionalProperties": false, - "properties": { - "value": { - "type": "string", - "description": "Value", - "example": "hellotoken" - } - } -} \ No newline at end of file diff --git a/src/main/java/org/folio/common/ListUtils.java b/src/main/java/org/folio/common/ListUtils.java index 462e9776f..278b80b5d 100644 --- a/src/main/java/org/folio/common/ListUtils.java +++ b/src/main/java/org/folio/common/ListUtils.java @@ -6,26 +6,25 @@ import java.util.List; import java.util.Objects; import java.util.function.Function; -import java.util.regex.Pattern; import java.util.stream.Collectors; import lombok.experimental.UtilityClass; import org.apache.commons.lang3.StringUtils; +import org.jspecify.annotations.Nullable; @UtilityClass public class ListUtils { - private static final Pattern SPLIT_BY_COMMA_PATTERN = Pattern.compile("\\s*,\\s*"); - public static List mapItems(Collection source, Function mapper) { Objects.requireNonNull(source, "Collection is null"); return source.stream().map(mapper).collect(Collectors.toList()); } - public static List mapItemsNullable(Collection source, Function mapper) { + public static @Nullable List mapItemsNullable(Collection source, + Function mapper) { if (source == null) { return null; } - return source.stream().map(mapper).collect(Collectors.toList()); + return source.stream().map(mapper).filter(Objects::nonNull).collect(Collectors.toList()); } public static String createPlaceholders(int size) { @@ -40,6 +39,6 @@ public static String createPlaceholders(int placeholderSize, int copiesSize) { public static List parseByComma(String string) { return StringUtils.isBlank(string) ? Collections.emptyList() - : Arrays.asList(SPLIT_BY_COMMA_PATTERN.split(string)); + : Arrays.stream(StringUtils.split(string, ',')).map(String::trim).toList(); } } diff --git a/src/main/java/org/folio/rest/converter/common/attr/ProxyConverter.java b/src/main/java/org/folio/rest/converter/common/attr/ProxyConverter.java deleted file mode 100644 index dbf15bad3..000000000 --- a/src/main/java/org/folio/rest/converter/common/attr/ProxyConverter.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.folio.rest.converter.common.attr; - -import java.util.Objects; -import org.folio.holdingsiq.model.ProxyUrl; -import org.jspecify.annotations.Nullable; -import org.springframework.core.convert.converter.Converter; -import org.springframework.stereotype.Component; - -@Component -public class ProxyConverter implements Converter { - @Override - public org.folio.rest.jaxrs.model.ProxyUrl convert(@Nullable ProxyUrl proxy) { - if (Objects.isNull(proxy)) { - return null; - } - org.folio.rest.jaxrs.model.ProxyUrl p = new org.folio.rest.jaxrs.model.ProxyUrl(); - p.setId(proxy.getId()); - p.setInherited(proxy.getInherited()); - p.setProxiedUrl(proxy.getProxiedUrl()); - return p; - } -} diff --git a/src/main/java/org/folio/rest/converter/packages/CommonPackagePutRequestConverter.java b/src/main/java/org/folio/rest/converter/packages/CommonPackagePutRequestConverter.java new file mode 100644 index 000000000..1e90ef91b --- /dev/null +++ b/src/main/java/org/folio/rest/converter/packages/CommonPackagePutRequestConverter.java @@ -0,0 +1,81 @@ +package org.folio.rest.converter.packages; + +import static org.folio.common.ListUtils.mapItems; + +import java.util.Arrays; +import java.util.List; +import org.folio.holdingsiq.model.AlternateName; +import org.folio.holdingsiq.model.CoverageDates; +import org.folio.holdingsiq.model.PackagePut; +import org.folio.holdingsiq.model.Proxy; +import org.folio.holdingsiq.model.Visibility; +import org.folio.rest.jaxrs.model.PackagePutDataAttributes; +import org.folio.rest.jaxrs.model.PackageVisibility; +import org.folio.rest.jaxrs.model.VisibilityData; + +public abstract class CommonPackagePutRequestConverter { + + protected PackagePut.PackagePutBuilder convertCommonAttributes(PackagePutDataAttributes attributes) { + var builder = PackagePut.builder(); + + convertSimpleFields(attributes, builder); + + if (attributes.getProxy() != null) { + builder.proxy(convertProxy(attributes)); + } + + if (attributes.getCustomCoverage() != null) { + builder.customCoverage(convertCoverageDates(attributes)); + } + + if (attributes.getCustomAltNames() != null) { + builder.customAltNames(convertCustomAltNames(attributes)); + } + + var visibilityData = attributes.getVisibilityData(); + if (visibilityData == null || visibilityData.getIsHidden() == null) { + builder.visibilityDetails(mapItems(attributes.getVisibility(), + pv -> new Visibility(pv.getCategory().value(), pv.getHidden(), pv.getReason()))); + } else { + builder.visibilityDetails(convertVisibilities(visibilityData)); + } + + return builder; + } + + private void convertSimpleFields(PackagePutDataAttributes attributes, PackagePut.PackagePutBuilder builder) { + builder.isSelected(attributes.getIsSelected()); + builder.isFullPackage(attributes.getIsFullPackage()); + builder.customDescription(attributes.getCustomDescription()); + builder.customDisplayName(attributes.getCustomDisplayName()); + builder.packageFreeAccess(attributes.getIsFreeAccess()); + builder.packageUrl(attributes.getUrl()); + } + + private List convertVisibilities(VisibilityData visibilityData) { + return Arrays.stream(PackageVisibility.Category.values()) + .map(category -> new Visibility(category.value(), visibilityData.getIsHidden(), visibilityData.getReason())) + .toList(); + } + + private List convertCustomAltNames(PackagePutDataAttributes attributes) { + return mapItems(attributes.getCustomAltNames(), + packageAltName -> new AlternateName(packageAltName.getId(), packageAltName.getAltName())); + } + + private CoverageDates convertCoverageDates(PackagePutDataAttributes attributes) { + return CoverageDates.builder() + .beginCoverage(attributes.getCustomCoverage().getBeginCoverage()) + .endCoverage(attributes.getCustomCoverage().getEndCoverage()) + .build(); + } + + private Proxy convertProxy(PackagePutDataAttributes attributes) { + return Proxy.builder() + .id(attributes.getProxy().getId()) + // RM API gives an error when we pass inherited as true along with updated proxy value + // Hard code it to false; it should not affect the state of inherited that RM API maintains + .inherited(false) + .build(); + } +} diff --git a/src/main/java/org/folio/rest/converter/packages/CustomPackagePostRequestConverter.java b/src/main/java/org/folio/rest/converter/packages/CustomPackagePostRequestConverter.java new file mode 100644 index 000000000..64b52a7b6 --- /dev/null +++ b/src/main/java/org/folio/rest/converter/packages/CustomPackagePostRequestConverter.java @@ -0,0 +1,63 @@ +package org.folio.rest.converter.packages; + +import static org.folio.common.ListUtils.mapItemsNullable; +import static org.folio.rest.converter.packages.PackageConverterUtils.CONTENT_TYPE_TO_RMAPI_CODE; + +import javax.annotation.Nullable; +import org.folio.holdingsiq.model.AlternateName; +import org.folio.holdingsiq.model.CoverageDates; +import org.folio.holdingsiq.model.PackagePost; +import org.folio.holdingsiq.model.Proxy; +import org.folio.rest.jaxrs.model.Coverage; +import org.folio.rest.jaxrs.model.PackageAltName; +import org.folio.rest.jaxrs.model.PackagePostRequest; +import org.folio.rest.jaxrs.model.ProxyDto; +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + +@Component +public class CustomPackagePostRequestConverter implements Converter { + + @Override + public PackagePost convert(PackagePostRequest postPackageBody) { + var data = postPackageBody.getData(); + var attributes = data.getAttributes(); + return PackagePost.builder() + .customAltNames(mapItemsNullable(attributes.getCustomAltNames(), this::convertAlternateName)) + .coverage(convertCoverageDates(attributes.getCustomCoverage())) + .customDescription(attributes.getCustomDescription()) + .customDisplayName(attributes.getCustomDisplayName()) + .contentType(CONTENT_TYPE_TO_RMAPI_CODE.getOrDefault(attributes.getContentType(), 6)) + .packageName(attributes.getName()) + .packageFreeAccess(attributes.getIsFreeAccess()) + .proxy(convertProxy(attributes.getProxy())) + .build(); + } + + private @Nullable CoverageDates convertCoverageDates(@Nullable Coverage customCoverage) { + if (customCoverage == null) { + return null; + } + return CoverageDates.builder() + .beginCoverage(customCoverage.getBeginCoverage()) + .endCoverage(customCoverage.getEndCoverage()) + .build(); + } + + private @Nullable Proxy convertProxy(@Nullable ProxyDto proxy) { + if (proxy == null) { + return null; + } + return Proxy.builder() + .id(proxy.getId()) + .inherited(false) + .build(); + } + + private @Nullable AlternateName convertAlternateName(@Nullable PackageAltName packageAltName) { + if (packageAltName == null) { + return null; + } + return new AlternateName(packageAltName.getId(), packageAltName.getAltName()); + } +} diff --git a/src/main/java/org/folio/rest/converter/packages/CustomPackagePutRequestConverter.java b/src/main/java/org/folio/rest/converter/packages/CustomPackagePutRequestConverter.java new file mode 100644 index 000000000..afac2ee25 --- /dev/null +++ b/src/main/java/org/folio/rest/converter/packages/CustomPackagePutRequestConverter.java @@ -0,0 +1,24 @@ +package org.folio.rest.converter.packages; + +import static org.folio.rest.converter.packages.PackageConverterUtils.CONTENT_TYPE_TO_RMAPI_CODE; + +import org.folio.holdingsiq.model.PackagePut; +import org.folio.rest.jaxrs.model.PackagePutRequest; +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + +@Component +public class CustomPackagePutRequestConverter + extends CommonPackagePutRequestConverter + implements Converter { + + @Override + public PackagePut convert(PackagePutRequest request) { + var attributes = request.getData().getAttributes(); + var builder = convertCommonAttributes(attributes); + builder.packageName(attributes.getName()); + var contentType = CONTENT_TYPE_TO_RMAPI_CODE.get(attributes.getContentType()); + builder.contentType(contentType != null ? contentType : 6); + return builder.build(); + } +} diff --git a/src/main/java/org/folio/rest/converter/packages/ManagedPackagePutRequestConverter.java b/src/main/java/org/folio/rest/converter/packages/ManagedPackagePutRequestConverter.java new file mode 100644 index 000000000..81faf7182 --- /dev/null +++ b/src/main/java/org/folio/rest/converter/packages/ManagedPackagePutRequestConverter.java @@ -0,0 +1,27 @@ +package org.folio.rest.converter.packages; + +import org.folio.holdingsiq.model.PackagePut; +import org.folio.holdingsiq.model.TokenInfo; +import org.folio.rest.jaxrs.model.PackagePutRequest; +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + +@Component +public class ManagedPackagePutRequestConverter + extends CommonPackagePutRequestConverter + implements Converter { + + @Override + public PackagePut convert(PackagePutRequest request) { + var attributes = request.getData().getAttributes(); + var builder = convertCommonAttributes(attributes); + builder.allowEbscoToAddTitles(attributes.getAllowKbToAddTitles()); + if (attributes.getPackageToken() != null) { + var tokenInfo = TokenInfo.builder() + .value(attributes.getPackageToken().getValue()) + .build(); + builder.packageToken(tokenInfo); + } + return builder.build(); + } +} diff --git a/src/main/java/org/folio/rest/converter/packages/PackageConverter.java b/src/main/java/org/folio/rest/converter/packages/PackageConverter.java index 251f9ff1c..fffdb06e2 100644 --- a/src/main/java/org/folio/rest/converter/packages/PackageConverter.java +++ b/src/main/java/org/folio/rest/converter/packages/PackageConverter.java @@ -9,6 +9,7 @@ import java.util.List; import java.util.Objects; import org.folio.holdingsiq.model.PackageData; +import org.folio.holdingsiq.model.Proxy; import org.folio.holdingsiq.model.Titles; import org.folio.holdingsiq.model.TokenInfo; import org.folio.holdingsiq.model.VendorById; @@ -20,7 +21,7 @@ import org.folio.rest.jaxrs.model.PackageCollectionItem; import org.folio.rest.jaxrs.model.PackageRelationship; import org.folio.rest.jaxrs.model.Provider; -import org.folio.rest.jaxrs.model.Proxy; +import org.folio.rest.jaxrs.model.ProxyDto; import org.folio.rest.jaxrs.model.RelationshipData; import org.folio.rest.jaxrs.model.ResourceCollection; import org.folio.rest.jaxrs.model.Token; @@ -114,8 +115,8 @@ private void addAccessTypeRelationship(PackageResult result, Package packageData } } - private @Nullable Proxy convertToProxy(org.folio.holdingsiq.model.Proxy proxy) { - return proxy != null ? new Proxy().withId(proxy.getId()).withInherited(proxy.getInherited()) : null; + private @Nullable ProxyDto convertToProxy(@Nullable Proxy proxy) { + return proxy != null ? new ProxyDto().withId(proxy.getId()).withInherited(proxy.getInherited()) : null; } private List convertResourcesRelationship(PackageData packageData, Titles titles) { diff --git a/src/main/java/org/folio/rest/converter/packages/PackagePostRequestConverter.java b/src/main/java/org/folio/rest/converter/packages/PackagePostRequestConverter.java deleted file mode 100644 index 5622ba89e..000000000 --- a/src/main/java/org/folio/rest/converter/packages/PackagePostRequestConverter.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.folio.rest.converter.packages; - -import static org.folio.rest.converter.packages.PackageConverterUtils.CONTENT_TYPE_TO_RMAPI_CODE; - -import org.folio.holdingsiq.model.PackagePost; -import org.folio.rest.jaxrs.model.Coverage; -import org.folio.rest.jaxrs.model.PackagePostRequest; -import org.springframework.core.convert.converter.Converter; -import org.springframework.stereotype.Component; - -@Component -public class PackagePostRequestConverter implements Converter { - - @Override - public PackagePost convert(PackagePostRequest postPackageBody) { - var data = postPackageBody.getData(); - var attributes = data.getAttributes(); - PackagePost.PackagePostBuilder postRequest = PackagePost.builder() - .contentType(CONTENT_TYPE_TO_RMAPI_CODE.getOrDefault(attributes.getContentType(), 6)) - .packageName(attributes.getName()); - - Coverage customCoverage = attributes.getCustomCoverage(); - if (customCoverage != null) { - postRequest.coverage( - org.folio.holdingsiq.model.CoverageDates.builder() - .beginCoverage(customCoverage.getBeginCoverage()) - .endCoverage(customCoverage.getEndCoverage()) - .build()); - } - - return postRequest.build(); - } -} diff --git a/src/main/java/org/folio/rest/converter/packages/PackageRequestConverter.java b/src/main/java/org/folio/rest/converter/packages/PackageRequestConverter.java deleted file mode 100644 index 9b01a2f86..000000000 --- a/src/main/java/org/folio/rest/converter/packages/PackageRequestConverter.java +++ /dev/null @@ -1,91 +0,0 @@ -package org.folio.rest.converter.packages; - -import static org.folio.common.ListUtils.mapItems; -import static org.folio.rest.converter.packages.PackageConverterUtils.CONTENT_TYPE_TO_RMAPI_CODE; - -import java.util.Arrays; -import org.folio.holdingsiq.model.AlternateName; -import org.folio.holdingsiq.model.CoverageDates; -import org.folio.holdingsiq.model.PackagePut; -import org.folio.holdingsiq.model.Proxy; -import org.folio.holdingsiq.model.TokenInfo; -import org.folio.holdingsiq.model.Visibility; -import org.folio.rest.jaxrs.model.PackagePutDataAttributes; -import org.folio.rest.jaxrs.model.PackagePutRequest; -import org.folio.rest.jaxrs.model.PackageVisibility; -import org.springframework.stereotype.Component; - -@Component -public class PackageRequestConverter { - - public PackagePut convertToRmApiCustomPackagePutRequest(PackagePutRequest request) { - var attributes = request.getData().getAttributes(); - var builder = convertCommonAttributesToPackagePutRequest(attributes); - builder.packageName(attributes.getName()); - var contentType = CONTENT_TYPE_TO_RMAPI_CODE.get(attributes.getContentType()); - builder.contentType(contentType != null ? contentType : 6); - return builder.build(); - } - - public PackagePut convertToRmApiPackagePutRequest(PackagePutRequest request) { - var attributes = request.getData().getAttributes(); - var builder = convertCommonAttributesToPackagePutRequest(attributes); - builder.allowEbscoToAddTitles(attributes.getAllowKbToAddTitles()); - if (attributes.getPackageToken() != null) { - var tokenInfo = TokenInfo.builder() - .value(attributes.getPackageToken().getValue()) - .build(); - builder.packageToken(tokenInfo); - } - return builder.build(); - } - - @SuppressWarnings("checkstyle:MethodLength") - private PackagePut.PackagePutBuilder convertCommonAttributesToPackagePutRequest(PackagePutDataAttributes attributes) { - var builder = PackagePut.builder(); - - builder.isSelected(attributes.getIsSelected()); - builder.isFullPackage(attributes.getIsFullPackage()); - - if (attributes.getProxy() != null) { - var proxy = Proxy.builder() - .id(attributes.getProxy().getId()) - // RM API gives an error when we pass inherited as true along with updated proxy value - // Hard code it to false; it should not affect the state of inherited that RM API maintains - .inherited(false) - .build(); - builder.proxy(proxy); - } - - if (attributes.getCustomCoverage() != null) { - var coverageDates = CoverageDates.builder() - .beginCoverage(attributes.getCustomCoverage().getBeginCoverage()) - .endCoverage(attributes.getCustomCoverage().getEndCoverage()) - .build(); - builder.customCoverage(coverageDates); - } - - if (attributes.getCustomAltNames() != null) { - builder.customAltNames(mapItems(attributes.getCustomAltNames(), - packageAltName -> new AlternateName(packageAltName.getId(), packageAltName.getAltName()))); - } - - builder.customDescription(attributes.getCustomDescription()); - builder.customDisplayName(attributes.getCustomDisplayName()); - builder.packageFreeAccess(attributes.getIsFreeAccess()); - builder.packageUrl(attributes.getUrl()); - - var visibilityData = attributes.getVisibilityData(); - if (visibilityData == null || visibilityData.getIsHidden() == null) { - builder.visibilityDetails(mapItems(attributes.getVisibility(), - pv -> new Visibility(pv.getCategory().value(), pv.getHidden(), pv.getReason()))); - } else { - var visibilities = Arrays.stream(PackageVisibility.Category.values()) - .map(category -> new Visibility(category.value(), visibilityData.getIsHidden(), visibilityData.getReason())) - .toList(); - builder.visibilityDetails(visibilities); - } - - return builder; - } -} diff --git a/src/main/java/org/folio/rest/converter/packages/PackageRequestConvertionService.java b/src/main/java/org/folio/rest/converter/packages/PackageRequestConvertionService.java new file mode 100644 index 000000000..cd6d4ac9e --- /dev/null +++ b/src/main/java/org/folio/rest/converter/packages/PackageRequestConvertionService.java @@ -0,0 +1,29 @@ +package org.folio.rest.converter.packages; + +import lombok.RequiredArgsConstructor; +import org.folio.holdingsiq.model.PackagePost; +import org.folio.holdingsiq.model.PackagePut; +import org.folio.rest.jaxrs.model.PackagePostRequest; +import org.folio.rest.jaxrs.model.PackagePutRequest; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class PackageRequestConvertionService { + + private final ManagedPackagePutRequestConverter managedPackagePutRequestConverter; + private final CustomPackagePutRequestConverter customPackagePutRequestConverter; + private final CustomPackagePostRequestConverter customPackagePostRequestConverter; + + public PackagePut convertCustomPackagePutRequest(PackagePutRequest request) { + return customPackagePutRequestConverter.convert(request); + } + + public PackagePut convertManagedPackagePutRequest(PackagePutRequest request) { + return managedPackagePutRequestConverter.convert(request); + } + + public PackagePost convertCustomPackagePostRequest(PackagePostRequest request) { + return customPackagePostRequestConverter.convert(request); + } +} diff --git a/src/main/java/org/folio/rest/converter/providers/ProviderConverter.java b/src/main/java/org/folio/rest/converter/providers/ProviderConverter.java index f27c29e4a..075247b0f 100644 --- a/src/main/java/org/folio/rest/converter/providers/ProviderConverter.java +++ b/src/main/java/org/folio/rest/converter/providers/ProviderConverter.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Objects; +import org.folio.holdingsiq.model.Proxy; import org.folio.holdingsiq.model.TokenInfo; import org.folio.holdingsiq.model.VendorById; import org.folio.rest.jaxrs.model.MetaDataIncluded; @@ -15,7 +16,7 @@ import org.folio.rest.jaxrs.model.Provider; import org.folio.rest.jaxrs.model.ProviderData; import org.folio.rest.jaxrs.model.ProviderGetDataAttributes; -import org.folio.rest.jaxrs.model.Proxy; +import org.folio.rest.jaxrs.model.ProxyDto; import org.folio.rest.jaxrs.model.RelationshipData; import org.folio.rest.jaxrs.model.Relationships; import org.folio.rest.jaxrs.model.Token; @@ -67,14 +68,18 @@ private ProviderData convertProviderData(VendorResult result, VendorById vendor, .withPackagesSelected(vendor.getPackagesSelected()) .withSupportsCustomPackages(vendor.isCustomer()) .withProviderToken(tokenInfoConverter.convert(vendorToken)) - .withProxy(new Proxy() - .withId(vendor.getProxy().getId()) - .withInherited(vendor.getProxy().getInherited())) + .withProxy(convertProxy(vendor.getProxy())) .withTags(result.getTags()) ) .withRelationships(createEmptyProviderRelationships()); } + private ProxyDto convertProxy(Proxy proxy) { + return new ProxyDto() + .withId(proxy.getId()) + .withInherited(proxy.getInherited()); + } + private List convertPackagesRelationship(org.folio.holdingsiq.model.Packages packages) { return mapItems(packages.getPackagesList(), packageData -> new RelationshipData() diff --git a/src/main/java/org/folio/rest/converter/providers/ProviderPutRequestConverter.java b/src/main/java/org/folio/rest/converter/providers/ProviderPutRequestConverter.java index 29dd65ccc..3953a8c5f 100644 --- a/src/main/java/org/folio/rest/converter/providers/ProviderPutRequestConverter.java +++ b/src/main/java/org/folio/rest/converter/providers/ProviderPutRequestConverter.java @@ -1,5 +1,6 @@ package org.folio.rest.converter.providers; +import org.folio.holdingsiq.model.Proxy; import org.folio.holdingsiq.model.VendorPut; import org.folio.holdingsiq.model.VendorPutToken; import org.folio.rest.jaxrs.model.ProviderPutRequest; @@ -19,7 +20,7 @@ public VendorPut convert(ProviderPutRequest provider) { // API maintains if (provider.getData().getAttributes().getProxy() != null && provider.getData().getAttributes().getProxy().getId() != null) { - vpb.proxy(org.folio.holdingsiq.model.Proxy.builder() + vpb.proxy(Proxy.builder() .inherited(false) .id(provider.getData().getAttributes().getProxy().getId()) .build()); diff --git a/src/main/java/org/folio/rest/converter/resources/CommonResourceConverter.java b/src/main/java/org/folio/rest/converter/resources/CommonResourceConverter.java index 5d331a24a..eb8465a50 100644 --- a/src/main/java/org/folio/rest/converter/resources/CommonResourceConverter.java +++ b/src/main/java/org/folio/rest/converter/resources/CommonResourceConverter.java @@ -17,9 +17,11 @@ import org.folio.rest.jaxrs.model.AlternateTitle; import org.folio.rest.jaxrs.model.Contributors; import org.folio.rest.jaxrs.model.Coverage; +import org.folio.rest.jaxrs.model.ProxyUrlDto; import org.folio.rest.jaxrs.model.ResourceDataAttributes; import org.folio.rest.jaxrs.model.TitleSubject; import org.folio.rest.jaxrs.model.VisibilityData; +import org.jspecify.annotations.Nullable; import org.springframework.core.convert.converter.Converter; import org.springframework.stereotype.Component; @@ -32,7 +34,6 @@ public class CommonResourceConverter { private final Converter embargoPeriodConverter; private final Converter visibilityInfoConverter; private final Converter, List> coverageDatesConverter; - private final Converter proxyConverter; private final Converter, List> alternateTitleConverter; @@ -43,7 +44,6 @@ public CommonResourceConverter( Converter embargoPeriodConverter, Converter visibilityInfoConverter, Converter, List> coverageDatesConverter, - Converter proxyConverter, Converter, List> alternateTitleConverter) { this.contributorsConverter = contributorsConverter; this.identifiersConverter = identifiersConverter; @@ -51,7 +51,6 @@ public CommonResourceConverter( this.embargoPeriodConverter = embargoPeriodConverter; this.visibilityInfoConverter = visibilityInfoConverter; this.coverageDatesConverter = coverageDatesConverter; - this.proxyConverter = proxyConverter; this.alternateTitleConverter = alternateTitleConverter; } @@ -89,11 +88,21 @@ public ResourceDataAttributes createResourceDataAttributes(Title title, Customer resource.getCustomCoverageList().stream() .sorted(Comparator.comparing(CoverageDates::getBeginCoverage).reversed()) .toList())) - .withProxy(proxyConverter.convert(resource.getProxy())) + .withProxy(convertProxy(resource.getProxy())) .withUserDefinedField1(resource.getUserDefinedFields().getUserDefinedField1()) .withUserDefinedField2(resource.getUserDefinedFields().getUserDefinedField2()) .withUserDefinedField3(resource.getUserDefinedFields().getUserDefinedField3()) .withUserDefinedField4(resource.getUserDefinedFields().getUserDefinedField4()) .withUserDefinedField5(resource.getUserDefinedFields().getUserDefinedField5()); } + + public @Nullable ProxyUrlDto convertProxy(@Nullable ProxyUrl proxy) { + if (Objects.isNull(proxy)) { + return null; + } + return new ProxyUrlDto() + .withId(proxy.getId()) + .withInherited(proxy.getInherited()) + .withProxiedUrl(proxy.getProxiedUrl()); + } } diff --git a/src/main/java/org/folio/rest/converter/resources/ResourceRequestConverter.java b/src/main/java/org/folio/rest/converter/resources/ResourceRequestConverter.java index 935efe5fa..63da0fab2 100644 --- a/src/main/java/org/folio/rest/converter/resources/ResourceRequestConverter.java +++ b/src/main/java/org/folio/rest/converter/resources/ResourceRequestConverter.java @@ -6,10 +6,13 @@ import org.folio.holdingsiq.model.CoverageDates; import org.folio.holdingsiq.model.CustomerResources; import org.folio.holdingsiq.model.EmbargoPeriod; +import org.folio.holdingsiq.model.Proxy; +import org.folio.holdingsiq.model.ProxyUrl; import org.folio.holdingsiq.model.ResourcePut; import org.folio.holdingsiq.model.Title; import org.folio.holdingsiq.model.UserDefinedFields; import org.folio.rest.jaxrs.model.Coverage; +import org.folio.rest.jaxrs.model.ProxyDto; import org.folio.rest.jaxrs.model.ResourcePutDataAttributes; import org.folio.rest.jaxrs.model.ResourcePutRequest; import org.jspecify.annotations.Nullable; @@ -46,7 +49,7 @@ private ResourcePut.ResourcePutBuilder convertCommonAttributesToResourcePutReque var oldResource = oldTitle.getCustomerResourcesList().getFirst(); builder.isSelected(valueOrDefault(attributes.getIsSelected(), oldResource.getIsSelected())); - convertProxy(attributes, oldResource, builder); + convertProxy(builder, attributes.getProxy(), oldResource.getProxy()); convertHidden(attributes, oldResource, builder); if (Boolean.TRUE == oldResource.getIsPackageCustom()) { builder.url(valueOrDefault(attributes.getUrl(), oldResource.getUrl())); @@ -71,7 +74,7 @@ private ResourcePut.ResourcePutBuilder convertCommonAttributesToResourcePutReque } private void convertHidden(ResourcePutDataAttributes attributes, CustomerResources oldResource, - ResourcePut.ResourcePutBuilder builder) { + ResourcePut.ResourcePutBuilder builder) { var oldHidden = oldResource.getVisibilityData() != null ? oldResource.getVisibilityData().getIsHidden() : null; var isHidden = attributes.getVisibilityData() != null && attributes.getVisibilityData().getIsHidden() != null ? attributes.getVisibilityData().getIsHidden() : oldHidden; @@ -79,13 +82,11 @@ private void convertHidden(ResourcePutDataAttributes attributes, CustomerResourc builder.isHidden(isHidden); } - private void convertProxy(ResourcePutDataAttributes attributes, CustomerResources oldResource, - ResourcePut.ResourcePutBuilder builder) { - var proxy = attributes.getProxy(); - var proxyId = proxy != null && proxy.getId() != null ? proxy.getId() : oldResource.getProxy().getId(); + private void convertProxy(ResourcePut.ResourcePutBuilder builder, @Nullable ProxyDto newProxy, ProxyUrl oldProxy) { + var proxyId = newProxy != null && newProxy.getId() != null ? newProxy.getId() : oldProxy.getId(); //RM API gives an error when we pass inherited as true along with updated proxy value //Hard code it to false; it should not affect the state of inherited that RM API maintains - var rmApiProxy = org.folio.holdingsiq.model.Proxy.builder() + var rmApiProxy = Proxy.builder() .id(proxyId) .inherited(false) .build(); @@ -93,7 +94,7 @@ private void convertProxy(ResourcePutDataAttributes attributes, CustomerResource } private void convertCoverageDates(ResourcePutDataAttributes attributes, CustomerResources oldResource, - ResourcePut.ResourcePutBuilder builder) { + ResourcePut.ResourcePutBuilder builder) { var coverageDates = attributes.getCustomCoverages() != null ? convertToRmApiCustomCoverageList(attributes.getCustomCoverages()) : oldResource.getCustomCoverageList(); @@ -103,7 +104,7 @@ private void convertCoverageDates(ResourcePutDataAttributes attributes, Customer private void convertEmbargoPeriod(ResourcePutDataAttributes attributes, ResourcePut.ResourcePutBuilder builder) { var embargoPeriod = attributes.getCustomEmbargoPeriod(); if (embargoPeriod != null && embargoPeriod.getEmbargoUnit() != null - && embargoPeriod.getEmbargoValue() != null && embargoPeriod.getEmbargoValue() > 0) { + && embargoPeriod.getEmbargoValue() != null && embargoPeriod.getEmbargoValue() > 0) { var customEmbargo = EmbargoPeriod.builder() .embargoUnit(embargoPeriod.getEmbargoUnit().value()) .embargoValue(embargoPeriod.getEmbargoValue()) @@ -113,7 +114,7 @@ private void convertEmbargoPeriod(ResourcePutDataAttributes attributes, Resource } private void convertUserDefinedFields(ResourcePutDataAttributes attributes, ResourcePut.ResourcePutBuilder builder, - CustomerResources oldResource) { + CustomerResources oldResource) { builder.userDefinedFields(UserDefinedFields.builder() .userDefinedField1( valueOrDefault(attributes.getUserDefinedField1(), oldResource.getUserDefinedFields().getUserDefinedField1())) diff --git a/src/main/java/org/folio/rest/impl/EholdingsPackagesImpl.java b/src/main/java/org/folio/rest/impl/EholdingsPackagesImpl.java index 9a6dd172e..8ddd8a104 100644 --- a/src/main/java/org/folio/rest/impl/EholdingsPackagesImpl.java +++ b/src/main/java/org/folio/rest/impl/EholdingsPackagesImpl.java @@ -48,7 +48,7 @@ import org.folio.rest.annotations.Validate; import org.folio.rest.aspect.HandleValidationErrors; import org.folio.rest.converter.common.ConverterConsts; -import org.folio.rest.converter.packages.PackageRequestConverter; +import org.folio.rest.converter.packages.PackageRequestConvertionService; import org.folio.rest.exception.InputValidationException; import org.folio.rest.jaxrs.model.AccessType; import org.folio.rest.jaxrs.model.Package; @@ -73,10 +73,7 @@ import org.folio.rest.util.template.RmApiTemplate; import org.folio.rest.util.template.RmApiTemplateContext; import org.folio.rest.util.template.RmApiTemplateFactory; -import org.folio.rest.validator.CustomPackagePutBodyValidator; -import org.folio.rest.validator.PackagePutBodyValidator; -import org.folio.rest.validator.PackageTagsPutBodyValidator; -import org.folio.rest.validator.PackagesPostBodyValidator; +import org.folio.rest.validator.packages.PackageValidationService; import org.folio.rmapi.result.PackageResult; import org.folio.rmapi.result.TitleCollectionResult; import org.folio.rmapi.result.TitleResult; @@ -101,17 +98,9 @@ public class EholdingsPackagesImpl implements EholdingsPackages { private static final String PACKAGE_IS_CUSTOM_NOT_MATCHED_DETAILS = "Package isCustom: %s"; @Autowired - private PackageRequestConverter converter; + private PackageRequestConvertionService requestConvertionService; @Autowired - private Converter packagePostRequestConverter; - @Autowired - private PackagePutBodyValidator packagePutBodyValidator; - @Autowired - private CustomPackagePutBodyValidator customPackagePutBodyValidator; - @Autowired - private PackageTagsPutBodyValidator packageTagsPutBodyValidator; - @Autowired - private PackagesPostBodyValidator packagesPostBodyValidator; + private PackageValidationService validationService; @Autowired private RmApiTemplateFactory templateFactory; @Autowired @@ -193,10 +182,10 @@ public void getEholdingsPackages(String filterCustom, String q, String queryFiel @HandleValidationErrors public void postEholdingsPackages(String contentType, PackagePostRequest entity, Map okapiHeaders, Handler> asyncResultHandler, Context vertxContext) { - packagesPostBodyValidator.validate(entity); + validationService.validateCustomPackagePostRequest(entity); RmApiTemplate template = templateFactory.createTemplate(okapiHeaders, asyncResultHandler); - PackagePost packagePost = packagePostRequestConverter.convert(entity); + PackagePost packagePost = requestConvertionService.convertCustomPackagePostRequest(entity); String accessTypeId = entity.getData().getAttributes().getAccessTypeId(); if (accessTypeId == null) { @@ -335,7 +324,7 @@ public void putEholdingsPackagesTagsByPackageId(String packageId, String content Context vertxContext) { userKbCredentialsService.findByUser(headers) .thenCompose(creds -> { - packageTagsPutBodyValidator.validate(entity); + validationService.validatePackageTagsPutRequest(entity); PackageTagsDataAttributes attributes = entity.getData().getAttributes(); @@ -546,11 +535,11 @@ private CompletableFuture processUpdateRequest(PackagePutRequest entity, P PackagePut packagePutBody; if (BooleanUtils.isTrue(isEntityCustom)) { - customPackagePutBodyValidator.validate(entity); - packagePutBody = converter.convertToRmApiCustomPackagePutRequest(entity); + validationService.validateCustomPackagePutRequest(entity); + packagePutBody = requestConvertionService.convertCustomPackagePutRequest(entity); } else { - packagePutBodyValidator.validate(entity); - packagePutBody = converter.convertToRmApiPackagePutRequest(entity); + validationService.validateManagedPackagePutRequest(entity); + packagePutBody = requestConvertionService.convertManagedPackagePutRequest(entity); } return context.getPackagesService() .updatePackage(originalPackage.getPackageId(), packagePutBody); diff --git a/src/main/java/org/folio/rest/validator/PackagesPostBodyValidator.java b/src/main/java/org/folio/rest/validator/PackagesPostBodyValidator.java deleted file mode 100644 index 77d81792f..000000000 --- a/src/main/java/org/folio/rest/validator/PackagesPostBodyValidator.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.folio.rest.validator; - -import jakarta.validation.ValidationException; -import java.util.Objects; -import org.apache.commons.lang3.StringUtils; -import org.folio.rest.exception.InputValidationException; -import org.folio.rest.jaxrs.model.Coverage; -import org.folio.rest.jaxrs.model.PackagePostRequest; -import org.springframework.stereotype.Component; - -/** - * Verifies that post data for packages are valid. - */ -@Component -public class PackagesPostBodyValidator { - - private static final String INVALID_BEGIN_COVERAGE = "Begin Coverage has an invalid date."; - private static final String INVALID_POST_BODY = "Invalid request body"; - - /** - * Provides validation for the post data attributes. - * - * @throws ValidationException if validation fails - */ - public void validate(PackagePostRequest entity) { - - if (Objects.isNull(entity) || Objects.isNull(entity.getData()) - || Objects.isNull(entity.getData().getAttributes())) { - throw new InputValidationException(INVALID_POST_BODY, ""); - } - - ValidatorUtil.checkIsNotBlank("name", entity.getData().getAttributes().getName()); - ValidatorUtil.checkMaxLength("name", entity.getData().getAttributes().getName(), 200); - ValidatorUtil.checkIsNotNull("Content type", entity.getData().getAttributes().getContentType()); - - Coverage customCoverage = entity.getData().getAttributes().getCustomCoverage(); - if (Objects.nonNull(customCoverage)) { - - String beginCoverage = customCoverage.getBeginCoverage(); - String endCoverage = customCoverage.getEndCoverage(); - - ValidatorUtil.checkIsNotNull("beginCoverage", beginCoverage); - ValidatorUtil.checkDateValid("beginCoverage", beginCoverage); - - if (beginCoverage.isEmpty() && !StringUtils.isEmpty(endCoverage)) { - throw new InputValidationException(INVALID_BEGIN_COVERAGE, ""); - } - - if (StringUtils.isNotBlank(endCoverage)) { - ValidatorUtil.checkDateValid("endCoverage", endCoverage); - ValidatorUtil.checkDatesOrder(beginCoverage, endCoverage); - } - } - } -} diff --git a/src/main/java/org/folio/rest/validator/ValidatorUtil.java b/src/main/java/org/folio/rest/validator/ValidatorUtil.java index 1948876bd..2db7aa666 100644 --- a/src/main/java/org/folio/rest/validator/ValidatorUtil.java +++ b/src/main/java/org/folio/rest/validator/ValidatorUtil.java @@ -6,6 +6,7 @@ import java.time.format.DateTimeParseException; import java.util.Collection; import java.util.Objects; +import java.util.regex.Pattern; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.folio.rest.exception.InputValidationException; @@ -26,6 +27,7 @@ public final class ValidatorUtil { private static final String INVALID_DATES_ORDER = "Begin Coverage should be smaller than End Coverage"; private static final String MUST_BE_IN_RANGE = "%s should be in range %d - %d"; private static final String MUST_BE_EQUALS = "%s should be equals to '%s' but actual is '%s'"; + private static final Pattern HTML_TAG_PATTERN = Pattern.compile("<[^>]+>"); private ValidatorUtil() { } @@ -145,6 +147,14 @@ public static void checkInRange(int minInclusive, int maxInclusive, Integer valu } } + public static void checkNoHtml(String paramName, String value, String errorDetails) { + if (value != null && HTML_TAG_PATTERN.matcher(value).find()) { + throw new InputValidationException( + String.format(INVALID_FIELD_FORMAT, paramName), + errorDetails); + } + } + public static void checkIsEqual(String paramName, String expected, String actual) { if (!expected.equals(actual)) { throw new InputValidationException( diff --git a/src/main/java/org/folio/rest/validator/CustomPackagePutBodyValidator.java b/src/main/java/org/folio/rest/validator/packages/CustomPackagePutBodyValidator.java similarity index 54% rename from src/main/java/org/folio/rest/validator/CustomPackagePutBodyValidator.java rename to src/main/java/org/folio/rest/validator/packages/CustomPackagePutBodyValidator.java index 376c47c9e..a15d4ec7a 100644 --- a/src/main/java/org/folio/rest/validator/CustomPackagePutBodyValidator.java +++ b/src/main/java/org/folio/rest/validator/packages/CustomPackagePutBodyValidator.java @@ -1,15 +1,23 @@ -package org.folio.rest.validator; +package org.folio.rest.validator.packages; import org.folio.rest.exception.InputValidationException; import org.folio.rest.jaxrs.model.PackagePutDataAttributes; import org.folio.rest.jaxrs.model.PackagePutRequest; +import org.folio.rest.validator.ValidatorUtil; import org.springframework.stereotype.Component; @Component public class CustomPackagePutBodyValidator { + private static final String INVALID_REQUEST_BODY_TITLE = "Invalid request body"; private static final String INVALID_REQUEST_BODY_DETAILS = "Json body must contain data.attributes"; + private final PackageCustomAttributesValidator customAttributesValidator; + + public CustomPackagePutBodyValidator(PackageCustomAttributesValidator customAttributesValidator) { + this.customAttributesValidator = customAttributesValidator; + } + public void validate(PackagePutRequest request) { if (request == null || request.getData() == null @@ -17,19 +25,14 @@ public void validate(PackagePutRequest request) { throw new InputValidationException(INVALID_REQUEST_BODY_TITLE, INVALID_REQUEST_BODY_DETAILS); } PackagePutDataAttributes attributes = request.getData().getAttributes(); - String name = attributes.getName(); - - String beginCoverage = null; - String endCoverage = null; - if (attributes.getCustomCoverage() != null) { - beginCoverage = attributes.getCustomCoverage().getBeginCoverage(); - endCoverage = attributes.getCustomCoverage().getEndCoverage(); - } - - ValidatorUtil.checkIsNotBlank("name", name); - ValidatorUtil.checkMaxLength("name", name, 200); + ValidatorUtil.checkIsNotBlank("name", attributes.getName()); + ValidatorUtil.checkMaxLength("name", attributes.getName(), 200); ValidatorUtil.checkIsNotNull("contentType", attributes.getContentType()); - ValidatorUtil.checkDateValid("beginCoverage", beginCoverage); - ValidatorUtil.checkDateValid("endCoverage", endCoverage); + customAttributesValidator.validate(new PackageCustomAttributesValidator.PackageCustomAttributes( + attributes.getCustomDescription(), + attributes.getCustomDisplayName(), + attributes.getUrl(), + attributes.getCustomAltNames(), + attributes.getCustomCoverage())); } } diff --git a/src/main/java/org/folio/rest/validator/PackagePutBodyValidator.java b/src/main/java/org/folio/rest/validator/packages/ManagedPackagePutBodyValidator.java similarity index 56% rename from src/main/java/org/folio/rest/validator/PackagePutBodyValidator.java rename to src/main/java/org/folio/rest/validator/packages/ManagedPackagePutBodyValidator.java index 3f06a4121..5b8297f26 100644 --- a/src/main/java/org/folio/rest/validator/PackagePutBodyValidator.java +++ b/src/main/java/org/folio/rest/validator/packages/ManagedPackagePutBodyValidator.java @@ -1,39 +1,48 @@ -package org.folio.rest.validator; +package org.folio.rest.validator.packages; import java.util.List; import org.folio.rest.exception.InputValidationException; import org.folio.rest.jaxrs.model.PackagePutDataAttributes; import org.folio.rest.jaxrs.model.PackagePutRequest; import org.folio.rest.jaxrs.model.PackageVisibility; +import org.folio.rest.validator.ValidatorUtil; import org.springframework.stereotype.Component; @Component -public class PackagePutBodyValidator { +public class ManagedPackagePutBodyValidator { private static final String INVALID_REQUEST_BODY_TITLE = "Invalid request body"; private static final String INVALID_REQUEST_BODY_DETAILS = "Json body must contain data.attributes"; private static final int MAX_TOKEN_LENGTH = 500; + private final PackageCustomAttributesValidator customAttributesValidator; + + public ManagedPackagePutBodyValidator(PackageCustomAttributesValidator customAttributesValidator) { + this.customAttributesValidator = customAttributesValidator; + } + public void validate(PackagePutRequest request) { if (request == null || request.getData() == null || request.getData().getAttributes() == null) { throw new InputValidationException(INVALID_REQUEST_BODY_TITLE, INVALID_REQUEST_BODY_DETAILS); } PackagePutDataAttributes attributes = request.getData().getAttributes(); - Boolean allowKbToAddTitles = attributes.getAllowKbToAddTitles(); - - String beginCoverage = null; - String endCoverage = null; - if (attributes.getCustomCoverage() != null) { - beginCoverage = attributes.getCustomCoverage().getBeginCoverage(); - endCoverage = attributes.getCustomCoverage().getEndCoverage(); - } - - String value = attributes.getPackageToken() != null ? attributes.getPackageToken().getValue() : null; - validateNotSelected(attributes, allowKbToAddTitles, attributes.getVisibility(), beginCoverage, endCoverage, value); - ValidatorUtil.checkMaxLength("value", value, MAX_TOKEN_LENGTH); - ValidatorUtil.checkDateValid("beginCoverage", beginCoverage); - ValidatorUtil.checkDateValid("endCoverage", endCoverage); + String beginCoverage = attributes.getCustomCoverage() != null + ? attributes.getCustomCoverage().getBeginCoverage() : null; + String endCoverage = attributes.getCustomCoverage() != null + ? attributes.getCustomCoverage().getEndCoverage() : null; + String tokenValue = attributes.getPackageToken() != null + ? attributes.getPackageToken().getValue() : null; + + validateNotSelected(attributes, attributes.getAllowKbToAddTitles(), + attributes.getVisibility(), beginCoverage, endCoverage, tokenValue); + ValidatorUtil.checkMaxLength("value", tokenValue, MAX_TOKEN_LENGTH); + customAttributesValidator.validate(new PackageCustomAttributesValidator.PackageCustomAttributes( + attributes.getCustomDescription(), + attributes.getCustomDisplayName(), + attributes.getUrl(), + attributes.getCustomAltNames(), + attributes.getCustomCoverage())); } private void validateNotSelected(PackagePutDataAttributes attributes, Boolean allowKbToAddTitles, diff --git a/src/main/java/org/folio/rest/validator/packages/PackageCustomAttributesValidator.java b/src/main/java/org/folio/rest/validator/packages/PackageCustomAttributesValidator.java new file mode 100644 index 000000000..b4ce7bc4b --- /dev/null +++ b/src/main/java/org/folio/rest/validator/packages/PackageCustomAttributesValidator.java @@ -0,0 +1,102 @@ +package org.folio.rest.validator.packages; + +import java.util.List; +import org.apache.commons.lang3.StringUtils; +import org.folio.rest.exception.InputValidationException; +import org.folio.rest.jaxrs.model.Coverage; +import org.folio.rest.jaxrs.model.PackageAltName; +import org.folio.rest.validator.ValidatorUtil; +import org.springframework.stereotype.Component; + +@Component +public class PackageCustomAttributesValidator { + + private static final int MAX_DESCRIPTION_LENGTH = 2000; + private static final int MAX_DISPLAY_NAME_LENGTH = 300; + private static final int MAX_ALT_NAME_LENGTH = 300; + private static final int MAX_ALT_NAMES_COUNT = 10; + private static final int MAX_URL_LENGTH = 500; + + private static final String DESCRIPTION_TOO_LONG = "2000 character limit has been exceeded."; + private static final String DISPLAY_NAME_TOO_LONG = "300 character limit has been exceeded."; + private static final String DISPLAY_NAME_CONTAINS_HTML = + "Invalid characters. Custom alternate name field does not accept HTML."; + private static final String ALT_NAMES_LIMIT_EXCEEDED = + "Custom alternate names maximum limit of 10 has been exceeded."; + private static final String ALT_NAME_TOO_LONG = "300 character limit has been exceeded."; + private static final String ALT_NAME_CONTAINS_HTML = + "Invalid characters. Custom alternate name field does not accept HTML."; + private static final String URL_TOO_LONG = "500 character limit has been exceeded."; + private static final String INVALID_BEGIN_COVERAGE = "Begin Coverage has an invalid date."; + + public void validate(PackageCustomAttributes customAttributes) { + validateCustomDescription(customAttributes.customDescription); + validateCustomDisplayName(customAttributes.customDisplayName); + validateCustomAltNames(customAttributes.customAltNames); + validateUrl(customAttributes.url); + validateCustomCoverage(customAttributes.customCoverage); + } + + private void validateCustomDescription(String description) { + if (description != null && description.length() > MAX_DESCRIPTION_LENGTH) { + throw new InputValidationException(DESCRIPTION_TOO_LONG, ""); + } + } + + private void validateCustomDisplayName(String displayName) { + if (displayName != null && displayName.length() > MAX_DISPLAY_NAME_LENGTH) { + throw new InputValidationException(DISPLAY_NAME_TOO_LONG, ""); + } + ValidatorUtil.checkNoHtml("customDisplayName", displayName, DISPLAY_NAME_CONTAINS_HTML); + } + + private void validateCustomAltNames(List altNames) { + if (altNames == null) { + return; + } + if (altNames.size() > MAX_ALT_NAMES_COUNT) { + throw new InputValidationException(ALT_NAMES_LIMIT_EXCEEDED, ""); + } + for (PackageAltName altName : altNames) { + String name = altName.getAltName(); + if (name != null && name.length() > MAX_ALT_NAME_LENGTH) { + throw new InputValidationException(ALT_NAME_TOO_LONG, ""); + } + ValidatorUtil.checkNoHtml("customAltNames", name, ALT_NAME_CONTAINS_HTML); + } + } + + private void validateUrl(String url) { + if (url == null) { + return; + } + if (url.length() > MAX_URL_LENGTH) { + throw new InputValidationException(URL_TOO_LONG, ""); + } + ValidatorUtil.checkUrlFormat("url", url); + } + + private void validateCustomCoverage(Coverage coverage) { + if (coverage == null) { + return; + } + String beginCoverage = coverage.getBeginCoverage(); + String endCoverage = coverage.getEndCoverage(); + ValidatorUtil.checkDateValid("beginCoverage", beginCoverage); + if (StringUtils.isEmpty(beginCoverage) && !StringUtils.isEmpty(endCoverage)) { + throw new InputValidationException(INVALID_BEGIN_COVERAGE, ""); + } + if (StringUtils.isNotBlank(endCoverage)) { + ValidatorUtil.checkDateValid("endCoverage", endCoverage); + ValidatorUtil.checkDatesOrder(beginCoverage, endCoverage); + } + } + + public record PackageCustomAttributes( + String customDescription, + String customDisplayName, + String url, + List customAltNames, + Coverage customCoverage + ) { } +} diff --git a/src/main/java/org/folio/rest/validator/PackageTagsPutBodyValidator.java b/src/main/java/org/folio/rest/validator/packages/PackageTagsPutBodyValidator.java similarity index 92% rename from src/main/java/org/folio/rest/validator/PackageTagsPutBodyValidator.java rename to src/main/java/org/folio/rest/validator/packages/PackageTagsPutBodyValidator.java index 54f3cfe57..9cf6aaf82 100644 --- a/src/main/java/org/folio/rest/validator/PackageTagsPutBodyValidator.java +++ b/src/main/java/org/folio/rest/validator/packages/PackageTagsPutBodyValidator.java @@ -1,8 +1,9 @@ -package org.folio.rest.validator; +package org.folio.rest.validator.packages; import org.folio.rest.exception.InputValidationException; import org.folio.rest.jaxrs.model.PackageTagsDataAttributes; import org.folio.rest.jaxrs.model.PackageTagsPutRequest; +import org.folio.rest.validator.ValidatorUtil; import org.springframework.stereotype.Component; @Component diff --git a/src/main/java/org/folio/rest/validator/packages/PackageValidationService.java b/src/main/java/org/folio/rest/validator/packages/PackageValidationService.java new file mode 100644 index 000000000..daa7c9f19 --- /dev/null +++ b/src/main/java/org/folio/rest/validator/packages/PackageValidationService.java @@ -0,0 +1,33 @@ +package org.folio.rest.validator.packages; + +import lombok.RequiredArgsConstructor; +import org.folio.rest.jaxrs.model.PackagePostRequest; +import org.folio.rest.jaxrs.model.PackagePutRequest; +import org.folio.rest.jaxrs.model.PackageTagsPutRequest; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class PackageValidationService { + + private final ManagedPackagePutBodyValidator managedPackagePutBodyValidator; + private final CustomPackagePutBodyValidator customPackagePutBodyValidator; + private final PackageTagsPutBodyValidator packageTagsPutBodyValidator; + private final PackagesPostBodyValidator packagesPostBodyValidator; + + public void validateManagedPackagePutRequest(PackagePutRequest request) { + managedPackagePutBodyValidator.validate(request); + } + + public void validateCustomPackagePutRequest(PackagePutRequest request) { + customPackagePutBodyValidator.validate(request); + } + + public void validateCustomPackagePostRequest(PackagePostRequest request) { + packagesPostBodyValidator.validate(request); + } + + public void validatePackageTagsPutRequest(PackageTagsPutRequest request) { + packageTagsPutBodyValidator.validate(request); + } +} diff --git a/src/main/java/org/folio/rest/validator/packages/PackagesPostBodyValidator.java b/src/main/java/org/folio/rest/validator/packages/PackagesPostBodyValidator.java new file mode 100644 index 000000000..689da745e --- /dev/null +++ b/src/main/java/org/folio/rest/validator/packages/PackagesPostBodyValidator.java @@ -0,0 +1,53 @@ +package org.folio.rest.validator.packages; + +import jakarta.validation.ValidationException; +import java.util.Objects; +import org.folio.rest.exception.InputValidationException; +import org.folio.rest.jaxrs.model.Coverage; +import org.folio.rest.jaxrs.model.PackagePostRequest; +import org.folio.rest.validator.ValidatorUtil; +import org.springframework.stereotype.Component; + +/** + * Verifies that post data for packages are valid. + */ +@Component +public class PackagesPostBodyValidator { + + private static final String INVALID_POST_BODY = "Invalid request body"; + + private final PackageCustomAttributesValidator customAttributesValidator; + + public PackagesPostBodyValidator(PackageCustomAttributesValidator customAttributesValidator) { + this.customAttributesValidator = customAttributesValidator; + } + + /** + * Provides validation for the post data attributes. + * + * @throws ValidationException if validation fails + */ + public void validate(PackagePostRequest entity) { + if (Objects.isNull(entity) || Objects.isNull(entity.getData()) + || Objects.isNull(entity.getData().getAttributes())) { + throw new InputValidationException(INVALID_POST_BODY, ""); + } + var attributes = entity.getData().getAttributes(); + ValidatorUtil.checkIsNotBlank("name", attributes.getName()); + ValidatorUtil.checkMaxLength("name", attributes.getName(), 200); + ValidatorUtil.checkIsNotNull("Content type", attributes.getContentType()); + checkBeginCoverageNotNull(attributes.getCustomCoverage()); + customAttributesValidator.validate(new PackageCustomAttributesValidator.PackageCustomAttributes( + attributes.getCustomDescription(), + attributes.getCustomDisplayName(), + attributes.getUrl(), + attributes.getCustomAltNames(), + attributes.getCustomCoverage())); + } + + private void checkBeginCoverageNotNull(Coverage coverage) { + if (coverage != null) { + ValidatorUtil.checkIsNotNull("beginCoverage", coverage.getBeginCoverage()); + } + } +} diff --git a/src/test/java/org/folio/rest/converter/packages/CustomPackagePostRequestConverterTest.java b/src/test/java/org/folio/rest/converter/packages/CustomPackagePostRequestConverterTest.java new file mode 100644 index 000000000..06b0602ac --- /dev/null +++ b/src/test/java/org/folio/rest/converter/packages/CustomPackagePostRequestConverterTest.java @@ -0,0 +1,149 @@ +package org.folio.rest.converter.packages; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.folio.rest.jaxrs.model.ContentType; +import org.folio.rest.jaxrs.model.Coverage; +import org.folio.rest.jaxrs.model.PackageAltName; +import org.folio.rest.jaxrs.model.PackagePostData; +import org.folio.rest.jaxrs.model.PackagePostDataAttributes; +import org.folio.rest.jaxrs.model.PackagePostRequest; +import org.folio.rest.jaxrs.model.ProxyDto; +import org.junit.jupiter.api.Test; + +class CustomPackagePostRequestConverterTest { + + private final CustomPackagePostRequestConverter converter = new CustomPackagePostRequestConverter(); + + @Test + void shouldMapPackageName() { + var result = converter.convert(buildRequest(new PackagePostDataAttributes() + .withName("My Package") + .withContentType(ContentType.UNKNOWN))); + assertEquals("My Package", result.getPackageName()); + } + + @Test + void shouldMapCustomDescription() { + var result = converter.convert(buildRequest(new PackagePostDataAttributes() + .withContentType(ContentType.UNKNOWN) + .withCustomDescription("Some description"))); + assertEquals("Some description", result.getCustomDescription()); + } + + @Test + void shouldMapCustomDisplayName() { + var result = converter.convert(buildRequest(new PackagePostDataAttributes() + .withContentType(ContentType.UNKNOWN) + .withCustomDisplayName("Display Name"))); + assertEquals("Display Name", result.getCustomDisplayName()); + } + + @Test + void shouldMapFreeAccess() { + var result = converter.convert(buildRequest(new PackagePostDataAttributes() + .withContentType(ContentType.UNKNOWN) + .withIsFreeAccess(true))); + assertTrue(result.getPackageFreeAccess()); + } + + @Test + void shouldMapContentTypeToRmapiCode() { + var result = converter.convert(buildRequest(new PackagePostDataAttributes() + .withContentType(ContentType.AGGREGATED_FULL_TEXT))); + assertEquals(1, result.getContentType()); + } + + @Test + void shouldMapStreamingMediaContentType() { + var result = converter.convert(buildRequest(new PackagePostDataAttributes() + .withContentType(ContentType.STREAMING_MEDIA))); + assertEquals(8, result.getContentType()); + } + + @Test + void shouldUseDefaultContentTypeCodeForUnknown() { + var result = converter.convert(buildRequest(new PackagePostDataAttributes() + .withContentType(ContentType.UNKNOWN))); + assertEquals(6, result.getContentType()); + } + + @Test + void shouldMapCustomCoverage() { + var result = converter.convert(buildRequest(new PackagePostDataAttributes() + .withContentType(ContentType.UNKNOWN) + .withCustomCoverage(new Coverage() + .withBeginCoverage("2020-01-01") + .withEndCoverage("2021-12-31")))); + assertEquals("2020-01-01", result.getCoverage().getBeginCoverage()); + assertEquals("2021-12-31", result.getCoverage().getEndCoverage()); + } + + @Test + void shouldReturnNullCoverageWhenNotProvided() { + var result = converter.convert(buildRequest(new PackagePostDataAttributes() + .withContentType(ContentType.UNKNOWN))); + assertNull(result.getCoverage()); + } + + @Test + void shouldMapProxy() { + var result = converter.convert(buildRequest(new PackagePostDataAttributes() + .withContentType(ContentType.UNKNOWN) + .withProxy(new ProxyDto().withId("proxy-id")))); + assertEquals("proxy-id", result.getProxy().getId()); + assertFalse(result.getProxy().getInherited()); + } + + @Test + void shouldReturnNullProxyWhenNotProvided() { + var result = converter.convert(buildRequest(new PackagePostDataAttributes() + .withContentType(ContentType.UNKNOWN))); + assertNull(result.getProxy()); + } + + @Test + void shouldMapCustomAltNames() { + var result = converter.convert(buildRequest(new PackagePostDataAttributes() + .withContentType(ContentType.UNKNOWN) + .withCustomAltNames(List.of( + new PackageAltName().withId(1).withAltName("Alt One"), + new PackageAltName().withId(2).withAltName("Alt Two"))))); + assertEquals(2, result.getCustomAltNames().size()); + assertEquals(1, result.getCustomAltNames().get(0).id()); + assertEquals("Alt One", result.getCustomAltNames().get(0).altName()); + assertEquals(2, result.getCustomAltNames().get(1).id()); + assertEquals("Alt Two", result.getCustomAltNames().get(1).altName()); + } + + @Test + void shouldMapEmptyAltNamesToEmptyList() { + var result = converter.convert(buildRequest(new PackagePostDataAttributes() + .withContentType(ContentType.UNKNOWN) + .withCustomAltNames(emptyList()))); + assertTrue(result.getCustomAltNames().isEmpty()); + } + + @Test + void shouldMapSingleAltName() { + var result = converter.convert(buildRequest(new PackagePostDataAttributes() + .withContentType(ContentType.UNKNOWN) + .withCustomAltNames(singletonList( + new PackageAltName().withId(5).withAltName("Single Alt"))))); + assertEquals(1, result.getCustomAltNames().size()); + assertEquals("Single Alt", result.getCustomAltNames().getFirst().altName()); + } + + private PackagePostRequest buildRequest(PackagePostDataAttributes attributes) { + return new PackagePostRequest() + .withData(new PackagePostData() + .withType(PackagePostData.Type.PACKAGES) + .withAttributes(attributes)); + } +} diff --git a/src/test/java/org/folio/rest/converter/packages/CustomPackagePutRequestConverterTest.java b/src/test/java/org/folio/rest/converter/packages/CustomPackagePutRequestConverterTest.java new file mode 100644 index 000000000..8b4ffeb7f --- /dev/null +++ b/src/test/java/org/folio/rest/converter/packages/CustomPackagePutRequestConverterTest.java @@ -0,0 +1,68 @@ +package org.folio.rest.converter.packages; + +import static org.folio.util.PackagesTestUtil.getPackagePutRequest; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.folio.rest.jaxrs.model.ContentType; +import org.folio.rest.jaxrs.model.Coverage; +import org.folio.rest.jaxrs.model.PackagePutDataAttributes; +import org.folio.rest.jaxrs.model.PackageVisibility; +import org.junit.jupiter.api.Test; + +class CustomPackagePutRequestConverterTest { + + private final CustomPackagePutRequestConverter converter = new CustomPackagePutRequestConverter(); + + @Test + void shouldCreateRequestToChangeCustomPackageName() { + var packagePut = converter.convert(getPackagePutRequest( + new PackagePutDataAttributes() + .withName("new package name"))); + assertEquals("new package name", packagePut.getPackageName()); + } + + @Test + void shouldCreateRequestToChangeCustomPackageCoverageDates() { + var packagePut = converter.convert(getPackagePutRequest( + new PackagePutDataAttributes() + .withCustomCoverage(new Coverage() + .withBeginCoverage("2003-01-01") + .withEndCoverage("2004-01-01")))); + assertEquals("2003-01-01", packagePut.getCustomCoverage().getBeginCoverage()); + assertEquals("2004-01-01", packagePut.getCustomCoverage().getEndCoverage()); + } + + @Test + void shouldCreateRequestToChangeCustomPackageCoverageDatesToEmpty() { + var packagePut = converter.convert(getPackagePutRequest( + new PackagePutDataAttributes() + .withCustomCoverage(new Coverage() + .withBeginCoverage("") + .withEndCoverage("")))); + assertEquals("", packagePut.getCustomCoverage().getBeginCoverage()); + assertEquals("", packagePut.getCustomCoverage().getEndCoverage()); + } + + @Test + void shouldCreateRequestToChangeCustomPackageContentType() { + var packagePut = converter.convert(getPackagePutRequest( + new PackagePutDataAttributes() + .withContentType(ContentType.STREAMING_MEDIA))); + var aggregatedFullTextContentTypeCode = 8; + assertEquals(aggregatedFullTextContentTypeCode, packagePut.getContentType()); + } + + @Test + void shouldCreateRequestToChangeCustomPackageVisibility() { + var packagePut = + converter.convert(getPackagePutRequest( + new PackagePutDataAttributes() + .withVisibility(List.of(new PackageVisibility() + .withHidden(true) + .withCategory(PackageVisibility.Category.PF))) + )); + assertTrue(packagePut.getVisibilityDetails().getFirst().hidden()); + } +} diff --git a/src/test/java/org/folio/rest/converter/packages/ManagedPackagePutRequestConverterTest.java b/src/test/java/org/folio/rest/converter/packages/ManagedPackagePutRequestConverterTest.java new file mode 100644 index 000000000..add2bc4f4 --- /dev/null +++ b/src/test/java/org/folio/rest/converter/packages/ManagedPackagePutRequestConverterTest.java @@ -0,0 +1,57 @@ +package org.folio.rest.converter.packages; + +import static org.folio.util.PackagesTestUtil.getPackagePutRequest; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.folio.rest.jaxrs.model.Coverage; +import org.folio.rest.jaxrs.model.PackagePutDataAttributes; +import org.folio.rest.jaxrs.model.PackageVisibility; +import org.junit.jupiter.api.Test; + +class ManagedPackagePutRequestConverterTest { + + private final ManagedPackagePutRequestConverter converter = new ManagedPackagePutRequestConverter(); + + @Test + void shouldCreateRequestToSelectPackage() { + var packagePut = converter.convert(getPackagePutRequest( + new PackagePutDataAttributes() + .withIsSelected(true))); + assertTrue(packagePut.getIsSelected()); + } + + @Test + void shouldCreateRequestToHidePackage() { + var packagePut = converter.convert(getPackagePutRequest( + new PackagePutDataAttributes() + .withIsSelected(true) + .withVisibility(List.of(new PackageVisibility() + .withHidden(true) + .withCategory(PackageVisibility.Category.PF))) + )); + assertTrue(packagePut.getVisibilityDetails().getFirst().hidden()); + } + + @Test + void shouldCreateRequestToAllowKbAddTitlesToPackage() { + var packagePut = converter.convert(getPackagePutRequest( + new PackagePutDataAttributes() + .withIsSelected(true) + .withAllowKbToAddTitles(true))); + assertTrue(packagePut.getAllowEbscoToAddTitles()); + } + + @Test + void shouldCreateRequestToAddCustomCoverage() { + var packagePut = converter.convert(getPackagePutRequest( + new PackagePutDataAttributes() + .withIsSelected(true) + .withCustomCoverage(new Coverage() + .withBeginCoverage("2000-10-10") + .withEndCoverage("2000-11-10")))); + assertEquals("2000-10-10", packagePut.getCustomCoverage().getBeginCoverage()); + assertEquals("2000-11-10", packagePut.getCustomCoverage().getEndCoverage()); + } +} diff --git a/src/test/java/org/folio/rest/converter/packages/PackageRequestConverterTest.java b/src/test/java/org/folio/rest/converter/packages/PackageRequestConverterTest.java deleted file mode 100644 index aeedc16f8..000000000 --- a/src/test/java/org/folio/rest/converter/packages/PackageRequestConverterTest.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.folio.rest.converter.packages; - -import static org.folio.util.PackagesTestUtil.getPackagePutRequest; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.List; -import org.folio.rest.jaxrs.model.ContentType; -import org.folio.rest.jaxrs.model.Coverage; -import org.folio.rest.jaxrs.model.PackagePutDataAttributes; -import org.folio.rest.jaxrs.model.PackageVisibility; -import org.folio.spring.config.TestConfig; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -@ExtendWith(SpringExtension.class) -@ContextConfiguration(classes = TestConfig.class) -class PackageRequestConverterTest { - @Autowired - private PackageRequestConverter packagesConverter; - - @Test - void shouldCreateRequestToSelectPackage() { - var packagePut = packagesConverter.convertToRmApiPackagePutRequest(getPackagePutRequest( - new PackagePutDataAttributes() - .withIsSelected(true))); - assertTrue(packagePut.getIsSelected()); - } - - @Test - void shouldCreateRequestToHidePackage() { - var packagePut = packagesConverter.convertToRmApiPackagePutRequest(getPackagePutRequest( - new PackagePutDataAttributes() - .withIsSelected(true) - .withVisibility(List.of(new PackageVisibility() - .withHidden(true) - .withCategory(PackageVisibility.Category.PF))) - )); - assertTrue(packagePut.getVisibilityDetails().getFirst().hidden()); - } - - @Test - void shouldCreateRequestToAllowKbAddTitlesToPackage() { - var packagePut = packagesConverter.convertToRmApiPackagePutRequest(getPackagePutRequest( - new PackagePutDataAttributes() - .withIsSelected(true) - .withAllowKbToAddTitles(true))); - assertTrue(packagePut.getAllowEbscoToAddTitles()); - } - - @Test - void shouldCreateRequestToAddCustomCoverage() { - var packagePut = packagesConverter.convertToRmApiPackagePutRequest(getPackagePutRequest( - new PackagePutDataAttributes() - .withIsSelected(true) - .withCustomCoverage(new Coverage() - .withBeginCoverage("2000-10-10") - .withEndCoverage("2000-11-10")))); - assertEquals("2000-10-10", packagePut.getCustomCoverage().getBeginCoverage()); - assertEquals("2000-11-10", packagePut.getCustomCoverage().getEndCoverage()); - } - - @Test - void shouldCreateRequestToChangeCustomPackageName() { - var packagePut = packagesConverter.convertToRmApiCustomPackagePutRequest(getPackagePutRequest( - new PackagePutDataAttributes() - .withName("new package name"))); - assertEquals("new package name", packagePut.getPackageName()); - } - - @Test - void shouldCreateRequestToChangeCustomPackageCoverageDates() { - var packagePut = packagesConverter.convertToRmApiCustomPackagePutRequest(getPackagePutRequest( - new PackagePutDataAttributes() - .withCustomCoverage(new Coverage() - .withBeginCoverage("2003-01-01") - .withEndCoverage("2004-01-01")))); - assertEquals("2003-01-01", packagePut.getCustomCoverage().getBeginCoverage()); - assertEquals("2004-01-01", packagePut.getCustomCoverage().getEndCoverage()); - } - - @Test - void shouldCreateRequestToChangeCustomPackageCoverageDatesToEmpty() { - var packagePut = packagesConverter.convertToRmApiCustomPackagePutRequest(getPackagePutRequest( - new PackagePutDataAttributes() - .withCustomCoverage(new Coverage() - .withBeginCoverage("") - .withEndCoverage("")))); - assertEquals("", packagePut.getCustomCoverage().getBeginCoverage()); - assertEquals("", packagePut.getCustomCoverage().getEndCoverage()); - } - - @Test - void shouldCreateRequestToChangeCustomPackageContentType() { - var packagePut = packagesConverter.convertToRmApiCustomPackagePutRequest(getPackagePutRequest( - new PackagePutDataAttributes() - .withContentType(ContentType.STREAMING_MEDIA))); - var aggregatedFullTextContentTypeCode = 8; - assertEquals(aggregatedFullTextContentTypeCode, packagePut.getContentType()); - } - - @Test - void shouldCreateRequestToChangeCustomPackageVisibility() { - var packagePut = - packagesConverter.convertToRmApiCustomPackagePutRequest(getPackagePutRequest( - new PackagePutDataAttributes() - .withVisibility(List.of(new PackageVisibility() - .withHidden(true) - .withCategory(PackageVisibility.Category.PF))) - )); - assertTrue(packagePut.getVisibilityDetails().getFirst().hidden()); - } -} diff --git a/src/test/java/org/folio/rest/converter/resources/ResourceRequestConverterTest.java b/src/test/java/org/folio/rest/converter/resources/ResourceRequestConverterTest.java index 2f9f8d946..afa9b713f 100644 --- a/src/test/java/org/folio/rest/converter/resources/ResourceRequestConverterTest.java +++ b/src/test/java/org/folio/rest/converter/resources/ResourceRequestConverterTest.java @@ -10,12 +10,13 @@ import org.folio.holdingsiq.model.CoverageDates; import org.folio.holdingsiq.model.CustomerResources; +import org.folio.holdingsiq.model.ProxyUrl; import org.folio.holdingsiq.model.Title; import org.folio.holdingsiq.model.UserDefinedFields; import org.folio.holdingsiq.model.VisibilityInfo; import org.folio.rest.jaxrs.model.EmbargoPeriod; import org.folio.rest.jaxrs.model.EmbargoPeriod.EmbargoUnit; -import org.folio.rest.jaxrs.model.Proxy; +import org.folio.rest.jaxrs.model.ProxyDto; import org.folio.rest.jaxrs.model.ResourcePutDataAttributes; import org.folio.rest.jaxrs.model.VisibilityData; import org.junit.jupiter.api.BeforeEach; @@ -49,7 +50,7 @@ void setUp() { .beginCoverage(OLD_BEGIN_COVERAGE).endCoverage(OLD_END_COVERAGE).build())) .customEmbargoPeriod(org.folio.holdingsiq.model.EmbargoPeriod.builder() .embargoUnit(OLD_EMBARGO_UNIT).embargoValue(OLD_EMBARGO_VALUE).build()) - .proxy(org.folio.holdingsiq.model.ProxyUrl.builder().proxiedUrl(OLD_URL) + .proxy(ProxyUrl.builder().proxiedUrl(OLD_URL) .id(OLD_PROXY_ID).inherited(true).build()) .url(OLD_URL) .userDefinedFields(UserDefinedFields.builder().build()) @@ -82,7 +83,7 @@ void shouldCreateRequestToUpdateProxyForManagedResource() { var resourcePut = resourcesConverter.convertToRmApiResourcePutRequest(getResourcePutRequest( new ResourcePutDataAttributes() .withIsSelected(true) - .withProxy(new Proxy() + .withProxy(new ProxyDto() .withId("test-proxy-id"))), resourceData); assertEquals("test-proxy-id", resourcePut.getProxy().getId()); } @@ -92,7 +93,7 @@ void shouldCreateRequestToUpdateProxyForCustomResource() { var resourcePut = resourcesConverter.convertToRmApiCustomResourcePutRequest(getResourcePutRequest( new ResourcePutDataAttributes() .withIsSelected(true) - .withProxy(new Proxy() + .withProxy(new ProxyDto() .withId("test-proxy-id"))), resourceData); assertEquals("test-proxy-id", resourcePut.getProxy().getId()); } diff --git a/src/test/java/org/folio/rest/validator/PackagePostValidatorTest.java b/src/test/java/org/folio/rest/validator/PackagePostValidatorTest.java deleted file mode 100644 index e12068983..000000000 --- a/src/test/java/org/folio/rest/validator/PackagePostValidatorTest.java +++ /dev/null @@ -1,228 +0,0 @@ -package org.folio.rest.validator; - -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.folio.rest.exception.InputValidationException; -import org.folio.rest.jaxrs.model.ContentType; -import org.folio.rest.jaxrs.model.Coverage; -import org.folio.rest.jaxrs.model.PackagePostData; -import org.folio.rest.jaxrs.model.PackagePostDataAttributes; -import org.folio.rest.jaxrs.model.PackagePostRequest; -import org.junit.jupiter.api.Test; - -class PackagePostValidatorTest { - - private final PackagesPostBodyValidator validator = new PackagesPostBodyValidator(); - - @Test - void shouldThrowExceptionWhenNoPostBody() { - PackagePostRequest postRequest = null; - assertThrows(InputValidationException.class, () -> - validator.validate(postRequest)); - } - - @Test - void shouldThrowExceptionWhenEmptyBody() { - PackagePostRequest postRequest = new PackagePostRequest(); - assertThrows(InputValidationException.class, () -> - validator.validate(postRequest)); - } - - @Test - void shouldThrowExceptionWhenNoPostData() { - PackagePostRequest postRequest = new PackagePostRequest() - .withData(null); - assertThrows(InputValidationException.class, () -> - validator.validate(postRequest)); - } - - @Test - void shouldThrowExceptionWhenEmptyPostData() { - PackagePostRequest postRequest = new PackagePostRequest() - .withData(new PackagePostData()); - assertThrows(InputValidationException.class, () -> - validator.validate(postRequest)); - } - - @Test - void shouldThrowExceptionWhenEmptyPostDataAttributes() { - PackagePostRequest postRequest = new PackagePostRequest() - .withData(new PackagePostData() - .withAttributes(new PackagePostDataAttributes())); - assertThrows(InputValidationException.class, () -> - validator.validate(postRequest)); - } - - @Test - void shouldThrowExceptionWhenPostDataAttributesNameIsEmpty() { - PackagePostRequest postRequest = new PackagePostRequest() - .withData(new PackagePostData() - .withAttributes(new PackagePostDataAttributes() - .withName(""))); - assertThrows(InputValidationException.class, () -> - validator.validate(postRequest)); - } - - @Test - void shouldThrowExceptionWhenPostDataAttributesTypeIsNull() { - PackagePostRequest postRequest = new PackagePostRequest() - .withData(new PackagePostData() - .withAttributes(new PackagePostDataAttributes() - .withName("name"))); - assertThrows(InputValidationException.class, () -> - validator.validate(postRequest)); - } - - @Test - void shouldThrowExceptionWhenPostDataAttributeCustomCoverageBeginDateIsNull() { - PackagePostRequest postRequest = new PackagePostRequest() - .withData(new PackagePostData() - .withAttributes(new PackagePostDataAttributes() - .withName("name").withContentType(ContentType.STREAMING_MEDIA) - .withCustomCoverage(new Coverage()))); - assertThrows(InputValidationException.class, () -> - validator.validate(postRequest)); - } - - @Test - void shouldThrowExceptionWhenPostDataAttributeCustomCoverageWithEmptyBeginDate() { - PackagePostRequest postRequest = new PackagePostRequest().withData(new PackagePostData() - .withAttributes(new PackagePostDataAttributes() - .withName("name") - .withContentType(ContentType.STREAMING_MEDIA) - .withCustomCoverage(new Coverage() - .withBeginCoverage("") - .withEndCoverage("2003-11-01")) - )); - assertThrows(InputValidationException.class, () -> - validator.validate(postRequest)); - } - - @Test - void shouldThrowExceptionWhenPostDataAttributeCustomCoverageBeginDateIsInvalidFormat() { - PackagePostRequest postRequest = new PackagePostRequest() - .withData(new PackagePostData() - .withAttributes(new PackagePostDataAttributes() - .withName("name") - .withContentType(ContentType.STREAMING_MEDIA) - .withCustomCoverage(new Coverage() - .withBeginCoverage("-01")))); - assertThrows(InputValidationException.class, () -> - validator.validate(postRequest)); - } - - @Test - void shouldThrowExceptionWhenPostDataAttributeCustomCoverageEndDateIsInvalidFormat() { - PackagePostRequest postRequest = new PackagePostRequest() - .withData(new PackagePostData() - .withAttributes(new PackagePostDataAttributes() - .withName("name") - .withContentType(ContentType.STREAMING_MEDIA) - .withCustomCoverage(new Coverage() - .withBeginCoverage("2003-11-01") - .withEndCoverage("-01")))); - assertThrows(InputValidationException.class, () -> - validator.validate(postRequest)); - } - - @Test - void shouldThrowExceptionWhenPostDataAttributeCustomCoverageBeginDateIsBeforeEndDate() { - PackagePostRequest postRequest = new PackagePostRequest().withData(new PackagePostData() - .withAttributes(new PackagePostDataAttributes() - .withName("name") - .withContentType(ContentType.STREAMING_MEDIA) - .withCustomCoverage(new Coverage() - .withBeginCoverage("2003-12-01") - .withEndCoverage("2003-11-01")) - )); - assertThrows(InputValidationException.class, () -> - validator.validate(postRequest)); - } - - @Test - void shouldNotThrowExceptionWhenPostDataAttributeCustomCoverageIsNull() { - PackagePostRequest postRequest = new PackagePostRequest().withData(new PackagePostData() - .withAttributes(new PackagePostDataAttributes() - .withName("name") - .withContentType(ContentType.STREAMING_MEDIA) - )); - validator.validate(postRequest); - } - - @Test - void shouldNotThrowExceptionWhenPostDataAttributeCustomCoverageWithValidDates() { - PackagePostRequest postRequest = new PackagePostRequest().withData(new PackagePostData() - .withAttributes(new PackagePostDataAttributes() - .withName("name") - .withContentType(ContentType.STREAMING_MEDIA) - .withCustomCoverage(new Coverage() - .withBeginCoverage("2003-11-01") - .withEndCoverage("2003-12-01")) - )); - validator.validate(postRequest); - } - - @Test - void shouldNotThrowExceptionWhenPostDataAttributeCustomCoverageWithEmptyDates() { - PackagePostRequest postRequest = new PackagePostRequest().withData(new PackagePostData() - .withAttributes(new PackagePostDataAttributes() - .withName("name") - .withContentType(ContentType.STREAMING_MEDIA) - .withCustomCoverage(new Coverage() - .withBeginCoverage("") - .withEndCoverage("")) - )); - validator.validate(postRequest); - } - - @Test - void shouldNotThrowExceptionWhenPostDataAttributeCustomCoverageBeginDateIsEmpty() { - PackagePostRequest postRequest = new PackagePostRequest() - .withData(new PackagePostData() - .withAttributes(new PackagePostDataAttributes() - .withName("name") - .withContentType(ContentType.STREAMING_MEDIA) - .withCustomCoverage(new Coverage() - .withBeginCoverage("")))); - validator.validate(postRequest); - } - - @Test - void shouldNotThrowExceptionWhenPostDataAttributeCustomCoverageWithValidBeginDateAndEndDateIsEmpty() { - PackagePostRequest postRequest = new PackagePostRequest().withData(new PackagePostData() - .withAttributes(new PackagePostDataAttributes() - .withName("name") - .withContentType(ContentType.STREAMING_MEDIA) - .withCustomCoverage(new Coverage() - .withBeginCoverage("2003-11-01") - .withEndCoverage("")) - )); - validator.validate(postRequest); - } - - @Test - void shouldNotThrowExceptionWhenPostDataAttributeCustomCoverageWithValidBeginDateAndEndDateIsNull() { - PackagePostRequest postRequest = new PackagePostRequest().withData(new PackagePostData() - .withAttributes(new PackagePostDataAttributes() - .withName("name") - .withContentType(ContentType.STREAMING_MEDIA) - .withCustomCoverage(new Coverage() - .withBeginCoverage("2003-11-01") - .withEndCoverage(null)) - )); - validator.validate(postRequest); - } - - @Test - void shouldNotThrowExceptionWhenPostDataAttributeCustomCoverageWithValidBeginDateAndEndDateIsNullw() { - PackagePostRequest postRequest = new PackagePostRequest().withData(new PackagePostData() - .withAttributes(new PackagePostDataAttributes() - .withName("name") - .withContentType(ContentType.STREAMING_MEDIA) - .withCustomCoverage(new Coverage() - .withBeginCoverage("") - .withEndCoverage(null)) - )); - validator.validate(postRequest); - } -} diff --git a/src/test/java/org/folio/rest/validator/CustomPackagePutBodyValidatorTest.java b/src/test/java/org/folio/rest/validator/packages/CustomPackagePutBodyValidatorTest.java similarity index 60% rename from src/test/java/org/folio/rest/validator/CustomPackagePutBodyValidatorTest.java rename to src/test/java/org/folio/rest/validator/packages/CustomPackagePutBodyValidatorTest.java index 6508e4ba0..22ee62c2b 100644 --- a/src/test/java/org/folio/rest/validator/CustomPackagePutBodyValidatorTest.java +++ b/src/test/java/org/folio/rest/validator/packages/CustomPackagePutBodyValidatorTest.java @@ -1,30 +1,34 @@ -package org.folio.rest.validator; +package org.folio.rest.validator.packages; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.lenient; import org.folio.rest.exception.InputValidationException; import org.folio.rest.jaxrs.model.ContentType; -import org.folio.rest.jaxrs.model.Coverage; import org.folio.rest.jaxrs.model.PackagePutDataAttributes; import org.folio.util.PackagesTestUtil; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +@ExtendWith(MockitoExtension.class) class CustomPackagePutBodyValidatorTest { - private final CustomPackagePutBodyValidator validator = new CustomPackagePutBodyValidator(); + @Mock + private PackageCustomAttributesValidator customAttributesValidator; - @Test - void shouldThrowExceptionOnInvalidCoverageDate() { - var request = PackagesTestUtil.getPackagePutRequest( - new PackagePutDataAttributes() - .withIsSelected(true) - .withContentType(ContentType.MIXED_CONTENT) - .withName("package name") - .withCustomCoverage(new Coverage() - .withBeginCoverage("abcd-10-ab"))); - var exception = assertThrows(InputValidationException.class, () -> validator.validate(request)); - assertTrue(exception.getMessage().contains("beginCoverage")); + @InjectMocks + private CustomPackagePutBodyValidator validator; + + @BeforeEach + void setUp() { + lenient().doNothing().when(customAttributesValidator).validate(any()); } @Test @@ -45,4 +49,13 @@ void shouldThrowExceptionOnNullContentType() { var exception = assertThrows(InputValidationException.class, () -> validator.validate(request)); assertTrue(exception.getMessage().contains("contentType")); } + + @Test + void shouldNotThrowExceptionForValidRequest() { + var request = PackagesTestUtil.getPackagePutRequest( + new PackagePutDataAttributes() + .withName("package name") + .withContentType(ContentType.MIXED_CONTENT)); + assertDoesNotThrow(() -> validator.validate(request)); + } } diff --git a/src/test/java/org/folio/rest/validator/PackagePutBodyValidatorTest.java b/src/test/java/org/folio/rest/validator/packages/ManagedPackagePutBodyValidatorTest.java similarity index 68% rename from src/test/java/org/folio/rest/validator/PackagePutBodyValidatorTest.java rename to src/test/java/org/folio/rest/validator/packages/ManagedPackagePutBodyValidatorTest.java index 9ca1eae0c..7318551a1 100644 --- a/src/test/java/org/folio/rest/validator/PackagePutBodyValidatorTest.java +++ b/src/test/java/org/folio/rest/validator/packages/ManagedPackagePutBodyValidatorTest.java @@ -1,8 +1,10 @@ -package org.folio.rest.validator; +package org.folio.rest.validator.packages; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.lenient; import java.util.List; import org.apache.commons.lang3.StringUtils; @@ -12,21 +14,35 @@ import org.folio.rest.jaxrs.model.PackageVisibility; import org.folio.rest.jaxrs.model.Token; import org.folio.util.PackagesTestUtil; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; -class PackagePutBodyValidatorTest { +@ExtendWith(MockitoExtension.class) +class ManagedPackagePutBodyValidatorTest { - private final PackagePutBodyValidator validator = new PackagePutBodyValidator(); + @Mock + private PackageCustomAttributesValidator customAttributesValidator; + + @InjectMocks + private ManagedPackagePutBodyValidator validator; + + @BeforeEach + void setUp() { + lenient().doNothing().when(customAttributesValidator).validate(any()); + } @Test void shouldValidateWhenPackageIsSelected() { - var packagePutRequest = PackagesTestUtil.getPackagePutRequest( + var request = PackagesTestUtil.getPackagePutRequest( new PackagePutDataAttributes() .withIsSelected(true) .withCustomCoverage(new Coverage()) - .withAllowKbToAddTitles(true) - ); - assertDoesNotThrow(() -> validator.validate(packagePutRequest)); + .withAllowKbToAddTitles(true)); + assertDoesNotThrow(() -> validator.validate(request)); } @Test @@ -34,9 +50,10 @@ void shouldThrowExceptionWhenPackageIsNotSelectedAndIsHiddenIsTrue() { var request = PackagesTestUtil.getPackagePutRequest( new PackagePutDataAttributes() .withIsSelected(false) - .withVisibility(List.of(new PackageVisibility().withCategory(PackageVisibility.Category.PF).withHidden(true))) - ); - + .withVisibility(List.of( + new PackageVisibility() + .withCategory(PackageVisibility.Category.PF) + .withHidden(true)))); var exception = assertThrows(InputValidationException.class, () -> validator.validate(request)); assertTrue(exception.getMessage().contains("visibility")); } @@ -46,9 +63,7 @@ void shouldThrowExceptionWhenPackageIsNotSelectedAndCoverageIsNotEmpty() { var request = PackagesTestUtil.getPackagePutRequest( new PackagePutDataAttributes() .withIsSelected(false) - .withCustomCoverage(new Coverage() - .withBeginCoverage("2000-01-01"))); - + .withCustomCoverage(new Coverage().withBeginCoverage("2000-01-01"))); var exception = assertThrows(InputValidationException.class, () -> validator.validate(request)); assertTrue(exception.getMessage().contains("beginCoverage")); } @@ -59,7 +74,6 @@ void shouldThrowExceptionWhenPackageIsNotSelectedAndAllowToAddTitlesTrue() { new PackagePutDataAttributes() .withIsSelected(false) .withAllowKbToAddTitles(true)); - var exception = assertThrows(InputValidationException.class, () -> validator.validate(request)); assertTrue(exception.getMessage().contains("allowKbToAddTitles")); } @@ -70,7 +84,6 @@ void shouldThrowExceptionWhenPackageIsNotSelectedAndTokenIsNotEmpty() { new PackagePutDataAttributes() .withIsSelected(false) .withPackageToken(new Token().withValue("tokenValue"))); - var exception = assertThrows(InputValidationException.class, () -> validator.validate(request)); assertTrue(exception.getMessage().contains("value")); } @@ -81,30 +94,7 @@ void shouldThrowExceptionWhenPackageIsSelectedAndTokenIsTooLong() { new PackagePutDataAttributes() .withIsSelected(true) .withPackageToken(new Token().withValue(StringUtils.repeat("tokenvalue", 200)))); - var exception = assertThrows(InputValidationException.class, () -> validator.validate(request)); assertTrue(exception.getMessage().contains("value")); } - - @Test - void shouldThrowExceptionWhenPackageIsSelectedAndCoverageDateIsInvalid() { - var request = PackagesTestUtil.getPackagePutRequest( - new PackagePutDataAttributes() - .withIsSelected(true) - .withCustomCoverage(new Coverage() - .withBeginCoverage("abcd-ab-ab"))); - - var exception = assertThrows(InputValidationException.class, () -> validator.validate(request)); - assertTrue(exception.getMessage().contains("beginCoverage")); - } - - @Test - void shouldValidateWhenPackageIsSelectedAndCoverageDateIsEmpty() { - var packagePutRequest = PackagesTestUtil.getPackagePutRequest( - new PackagePutDataAttributes() - .withIsSelected(true) - .withCustomCoverage(new Coverage() - .withBeginCoverage(""))); - assertDoesNotThrow(() -> validator.validate(packagePutRequest)); - } } diff --git a/src/test/java/org/folio/rest/validator/packages/PackageCustomAttributesValidatorTest.java b/src/test/java/org/folio/rest/validator/packages/PackageCustomAttributesValidatorTest.java new file mode 100644 index 000000000..5fc8a9751 --- /dev/null +++ b/src/test/java/org/folio/rest/validator/packages/PackageCustomAttributesValidatorTest.java @@ -0,0 +1,225 @@ +package org.folio.rest.validator.packages; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.Collections; +import java.util.List; +import java.util.stream.IntStream; +import org.folio.rest.exception.InputValidationException; +import org.folio.rest.jaxrs.model.Coverage; +import org.folio.rest.jaxrs.model.PackageAltName; +import org.folio.rest.validator.packages.PackageCustomAttributesValidator.PackageCustomAttributes; +import org.junit.jupiter.api.Test; + +class PackageCustomAttributesValidatorTest { + + private final PackageCustomAttributesValidator validator = new PackageCustomAttributesValidator(); + + @Test + void shouldThrowWhenCustomDescriptionExceedsMaxLength() { + var customAttributes = withDescription("a".repeat(2001)); + assertThrows(InputValidationException.class, () -> validator.validate(customAttributes)); + } + + @Test + void shouldNotThrowWhenCustomDescriptionIsExactlyMaxLength() { + var customAttributes = withDescription("a".repeat(2000)); + assertDoesNotThrow(() -> validator.validate(customAttributes)); + } + + @Test + void shouldNotThrowWhenCustomDescriptionContainsHtml() { + var customAttributes = withDescription("bold"); + assertDoesNotThrow(() -> validator.validate(customAttributes)); + } + + @Test + void shouldNotThrowWhenCustomDescriptionIsNull() { + var customAttributes = withDescription(null); + assertDoesNotThrow(() -> validator.validate(customAttributes)); + } + + @Test + void shouldThrowWhenCustomDisplayNameExceedsMaxLength() { + var customAttributes = withDisplayName("a".repeat(301)); + assertThrows(InputValidationException.class, () -> validator.validate(customAttributes)); + } + + @Test + void shouldNotThrowWhenCustomDisplayNameIsExactlyMaxLength() { + var customAttributes = withDisplayName("a".repeat(300)); + assertDoesNotThrow(() -> validator.validate(customAttributes)); + } + + @Test + void shouldThrowWhenCustomDisplayNameContainsHtml() { + var customAttributes = withDisplayName("name"); + assertThrows(InputValidationException.class, () -> validator.validate(customAttributes)); + } + + @Test + void shouldNotThrowWhenCustomDisplayNameIsNull() { + var customAttributes = withDisplayName(null); + assertDoesNotThrow(() -> validator.validate(customAttributes)); + } + + @Test + void shouldThrowWhenCustomAltNamesExceedMaxCount() { + var altNames = IntStream.rangeClosed(1, 11) + .mapToObj(i -> new PackageAltName().withAltName("name" + i)) + .toList(); + var customAttributes = withAltNames(altNames); + assertThrows(InputValidationException.class, () -> validator.validate(customAttributes)); + } + + @Test + void shouldNotThrowWhenCustomAltNamesCountIsExactlyMax() { + var altNames = IntStream.rangeClosed(1, 10) + .mapToObj(i -> new PackageAltName().withAltName("name" + i)) + .toList(); + var customAttributes = withAltNames(altNames); + assertDoesNotThrow(() -> validator.validate(customAttributes)); + } + + @Test + void shouldThrowWhenAltNameExceedsMaxLength() { + var customAttributes = withAltNames(Collections.singletonList( + new PackageAltName().withAltName("a".repeat(301)))); + assertThrows(InputValidationException.class, () -> validator.validate(customAttributes)); + } + + @Test + void shouldThrowWhenAltNameContainsHtml() { + var customAttributes = withAltNames(Collections.singletonList( + new PackageAltName().withAltName("alt"))); + assertThrows(InputValidationException.class, () -> validator.validate(customAttributes)); + } + + @Test + void shouldNotThrowWhenCustomAltNamesIsNull() { + var customAttributes = withAltNames(null); + assertDoesNotThrow(() -> validator.validate(customAttributes)); + } + + @Test + void shouldThrowWhenUrlExceedsMaxLength() { + var customAttributes = withUrl("a".repeat(501)); + assertThrows(InputValidationException.class, () -> validator.validate(customAttributes)); + } + + @Test + void shouldNotThrowWhenUrlIsExactlyMaxLength() { + var customAttributes = withUrl("https://example.com/" + "a".repeat(480)); + assertDoesNotThrow(() -> validator.validate(customAttributes)); + } + + @Test + void shouldNotThrowWhenUrlIsValid() { + var customAttributes = withUrl("https://example.com/path"); + assertDoesNotThrow(() -> validator.validate(customAttributes)); + } + + @Test + void shouldThrowWhenUrlHasNoProtocol() { + var customAttributes = withUrl("example.com"); + assertThrows(InputValidationException.class, () -> validator.validate(customAttributes)); + } + + @Test + void shouldThrowWhenUrlIsInvalidFormat() { + var customAttributes = withUrl("not a url"); + assertThrows(InputValidationException.class, () -> validator.validate(customAttributes)); + } + + @Test + void shouldNotThrowWhenUrlIsNull() { + var customAttributes = withUrl(null); + assertDoesNotThrow(() -> validator.validate(customAttributes)); + } + + @Test + void shouldNotThrowWhenCustomCoverageIsNull() { + var customAttributes = withCoverage(null); + assertDoesNotThrow(() -> validator.validate(customAttributes)); + } + + @Test + void shouldNotThrowWhenBeginCoverageIsNull() { + var customAttributes = withCoverage(new Coverage()); + assertDoesNotThrow(() -> validator.validate(customAttributes)); + } + + @Test + void shouldThrowWhenBeginCoverageHasInvalidFormat() { + var customAttributes = withCoverage(new Coverage().withBeginCoverage("not-a-date")); + assertThrows(InputValidationException.class, () -> validator.validate(customAttributes)); + } + + @Test + void shouldThrowWhenEndCoverageHasInvalidFormat() { + var customAttributes = withCoverage(new Coverage() + .withBeginCoverage("2020-01-01") + .withEndCoverage("not-a-date")); + assertThrows(InputValidationException.class, () -> validator.validate(customAttributes)); + } + + @Test + void shouldThrowWhenBeginCoverageIsEmptyButEndCoverageIsNot() { + var customAttributes = withCoverage(new Coverage() + .withBeginCoverage("") + .withEndCoverage("2020-12-31")); + assertThrows(InputValidationException.class, () -> validator.validate(customAttributes)); + } + + @Test + void shouldThrowWhenBeginCoverageIsAfterEndCoverage() { + var customAttributes = withCoverage(new Coverage() + .withBeginCoverage("2021-01-01") + .withEndCoverage("2020-01-01")); + assertThrows(InputValidationException.class, () -> validator.validate(customAttributes)); + } + + @Test + void shouldNotThrowWhenCoverageHasValidDates() { + var customAttributes = withCoverage(new Coverage() + .withBeginCoverage("2020-01-01") + .withEndCoverage("2021-12-31")); + assertDoesNotThrow(() -> validator.validate(customAttributes)); + } + + @Test + void shouldNotThrowWhenBeginCoverageIsValidAndEndIsNull() { + var customAttributes = withCoverage(new Coverage() + .withBeginCoverage("2020-01-01")); + assertDoesNotThrow(() -> validator.validate(customAttributes)); + } + + @Test + void shouldNotThrowWhenBothCoverageDatesAreEmpty() { + var customAttributes = withCoverage(new Coverage() + .withBeginCoverage("") + .withEndCoverage("")); + assertDoesNotThrow(() -> validator.validate(customAttributes)); + } + + private PackageCustomAttributes withDescription(String description) { + return new PackageCustomAttributes(description, null, null, null, null); + } + + private PackageCustomAttributes withDisplayName(String displayName) { + return new PackageCustomAttributes(null, displayName, null, null, null); + } + + private PackageCustomAttributes withAltNames(List altNames) { + return new PackageCustomAttributes(null, null, null, altNames, null); + } + + private PackageCustomAttributes withUrl(String url) { + return new PackageCustomAttributes(null, null, url, null, null); + } + + private PackageCustomAttributes withCoverage(Coverage coverage) { + return new PackageCustomAttributes(null, null, null, null, coverage); + } +} diff --git a/src/test/java/org/folio/rest/validator/packages/PackagePostValidatorTest.java b/src/test/java/org/folio/rest/validator/packages/PackagePostValidatorTest.java new file mode 100644 index 000000000..3272751db --- /dev/null +++ b/src/test/java/org/folio/rest/validator/packages/PackagePostValidatorTest.java @@ -0,0 +1,104 @@ +package org.folio.rest.validator.packages; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.lenient; + +import org.folio.rest.exception.InputValidationException; +import org.folio.rest.jaxrs.model.ContentType; +import org.folio.rest.jaxrs.model.Coverage; +import org.folio.rest.jaxrs.model.PackagePostData; +import org.folio.rest.jaxrs.model.PackagePostDataAttributes; +import org.folio.rest.jaxrs.model.PackagePostRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class PackagePostValidatorTest { + + @Mock + private PackageCustomAttributesValidator customAttributesValidator; + + @InjectMocks + private PackagesPostBodyValidator validator; + + @BeforeEach + void setUp() { + lenient().doNothing().when(customAttributesValidator).validate(any()); + } + + @Test + void shouldThrowExceptionWhenNoPostBody() { + assertThrows(InputValidationException.class, () -> validator.validate(null)); + } + + @Test + void shouldThrowExceptionWhenEmptyBody() { + var postRequest = new PackagePostRequest(); + assertThrows(InputValidationException.class, () -> validator.validate(postRequest)); + } + + @Test + void shouldThrowExceptionWhenNoPostData() { + var postRequest = new PackagePostRequest().withData(null); + assertThrows(InputValidationException.class, () -> validator.validate(postRequest)); + } + + @Test + void shouldThrowExceptionWhenEmptyPostData() { + var postRequest = new PackagePostRequest().withData(new PackagePostData()); + assertThrows(InputValidationException.class, () -> validator.validate(postRequest)); + } + + @Test + void shouldThrowExceptionWhenEmptyPostDataAttributes() { + var postRequest = new PackagePostRequest() + .withData(new PackagePostData() + .withAttributes(new PackagePostDataAttributes())); + assertThrows(InputValidationException.class, () -> validator.validate(postRequest)); + } + + @Test + void shouldThrowExceptionWhenPostDataAttributesNameIsEmpty() { + var postRequest = new PackagePostRequest() + .withData(new PackagePostData() + .withAttributes(new PackagePostDataAttributes() + .withName(""))); + assertThrows(InputValidationException.class, () -> validator.validate(postRequest)); + } + + @Test + void shouldThrowExceptionWhenPostDataAttributesTypeIsNull() { + var postRequest = new PackagePostRequest() + .withData(new PackagePostData() + .withAttributes(new PackagePostDataAttributes() + .withName("name"))); + assertThrows(InputValidationException.class, () -> validator.validate(postRequest)); + } + + @Test + void shouldThrowExceptionWhenBeginCoverageIsNull() { + var postRequest = new PackagePostRequest() + .withData(new PackagePostData() + .withAttributes(new PackagePostDataAttributes() + .withName("name") + .withContentType(ContentType.STREAMING_MEDIA) + .withCustomCoverage(new Coverage()))); + assertThrows(InputValidationException.class, () -> validator.validate(postRequest)); + } + + @Test + void shouldNotThrowExceptionWhenCoverageIsNull() { + var postRequest = new PackagePostRequest() + .withData(new PackagePostData() + .withAttributes(new PackagePostDataAttributes() + .withName("name") + .withContentType(ContentType.STREAMING_MEDIA))); + assertDoesNotThrow(() -> validator.validate(postRequest)); + } +} diff --git a/src/test/java/org/folio/rest/validator/PackageTagsPutBodyValidatorTest.java b/src/test/java/org/folio/rest/validator/packages/PackageTagsPutBodyValidatorTest.java similarity index 83% rename from src/test/java/org/folio/rest/validator/PackageTagsPutBodyValidatorTest.java rename to src/test/java/org/folio/rest/validator/packages/PackageTagsPutBodyValidatorTest.java index 693351712..44e617584 100644 --- a/src/test/java/org/folio/rest/validator/PackageTagsPutBodyValidatorTest.java +++ b/src/test/java/org/folio/rest/validator/packages/PackageTagsPutBodyValidatorTest.java @@ -1,4 +1,4 @@ -package org.folio.rest.validator; +package org.folio.rest.validator.packages; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -20,8 +20,7 @@ void shouldThrowExceptionWhenNameIsEmpty() { .withName("") .withContentType(ContentType.E_BOOK) )); - assertThrows(InputValidationException.class, () -> - validator.validate(request)); + assertThrows(InputValidationException.class, () -> validator.validate(request)); } @Test @@ -31,7 +30,6 @@ void shouldThrowExceptionWhenContentTypeIsNull() { .withAttributes(new PackageTagsDataAttributes() .withName("name") )); - assertThrows(InputValidationException.class, () -> - validator.validate(request)); + assertThrows(InputValidationException.class, () -> validator.validate(request)); } } diff --git a/src/test/resources/requests/kb-ebsco/package/post-package-request.json b/src/test/resources/requests/kb-ebsco/package/post-package-request.json index a8d902f1b..4f0231267 100644 --- a/src/test/resources/requests/kb-ebsco/package/post-package-request.json +++ b/src/test/resources/requests/kb-ebsco/package/post-package-request.json @@ -2,12 +2,29 @@ "data": { "type": "packages", "attributes": { - "name": "TEST_NAME", "contentType": "Streaming Media", + "customAltNames": [ + { + "altName": "TEST_ALT_NAME" + }, + { + "id": 1, + "altName": "TEST_ALT_NAME_2" + } + ], "customCoverage": { "beginCoverage": "2017-12-23", "endCoverage": "2018-03-30" - } + }, + "customDescription": "TEST_DESCRIPTION", + "customDisplayName": "TEST_DISPLAY_NAME", + "isFreeAccess": true, + "name": "TEST_NAME", + "proxy": { + "id": "proxy-id", + "inherited": true + }, + "url": "https://example.com/test-url" } } } From 7c757190044f86a2fc281debfdc48c91c9edf60a Mon Sep 17 00:00:00 2001 From: Pavlo Smahin Date: Tue, 30 Jun 2026 14:52:52 +0300 Subject: [PATCH 2/2] - add ValidatorUtil tests --- .../CustomLabelsPutBodyValidator.java | 2 +- .../folio/rest/validator/ValidatorUtil.java | 2 +- .../rest/validator/ValidatorUtilTest.java | 403 ++++++++++++++++++ 3 files changed, 405 insertions(+), 2 deletions(-) create mode 100644 src/test/java/org/folio/rest/validator/ValidatorUtilTest.java diff --git a/src/main/java/org/folio/rest/validator/CustomLabelsPutBodyValidator.java b/src/main/java/org/folio/rest/validator/CustomLabelsPutBodyValidator.java index 0dfe1c465..16d5fac7c 100644 --- a/src/main/java/org/folio/rest/validator/CustomLabelsPutBodyValidator.java +++ b/src/main/java/org/folio/rest/validator/CustomLabelsPutBodyValidator.java @@ -39,7 +39,7 @@ private void checkEachLabelIsValid(@NonNull CustomLabelsPutRequest request) { private void validateCollectionItem(@NonNull CustomLabel customLabel) { CustomLabelDataAttributes attributes = customLabel.getAttributes(); - checkInRange(1, 5, attributes.getId(), CUSTOM_LABEL_ID_PARAM); + checkInRange(CUSTOM_LABEL_ID_PARAM, attributes.getId(), 1, 5); checkMaxLength(CUSTOM_LABEL_NAME_PARAM, attributes.getDisplayLabel(), properties.labelMaxLength()); checkIsNotNull(FULL_TEXT_FINDER_PARAM, attributes.getDisplayOnFullTextFinder()); checkIsNotNull(PUBLICATION_FINDER_PARAM, attributes.getDisplayOnPublicationFinder()); diff --git a/src/main/java/org/folio/rest/validator/ValidatorUtil.java b/src/main/java/org/folio/rest/validator/ValidatorUtil.java index 2db7aa666..e0536943c 100644 --- a/src/main/java/org/folio/rest/validator/ValidatorUtil.java +++ b/src/main/java/org/folio/rest/validator/ValidatorUtil.java @@ -139,7 +139,7 @@ public static void checkUrlFormat(String paramName, String value) { } } - public static void checkInRange(int minInclusive, int maxInclusive, Integer value, String paramName) { + public static void checkInRange(String paramName, Integer value, int minInclusive, int maxInclusive) { if (value == null || value < minInclusive || value > maxInclusive) { throw new InputValidationException( String.format(INVALID_FIELD_FORMAT, paramName), diff --git a/src/test/java/org/folio/rest/validator/ValidatorUtilTest.java b/src/test/java/org/folio/rest/validator/ValidatorUtilTest.java new file mode 100644 index 000000000..431342a72 --- /dev/null +++ b/src/test/java/org/folio/rest/validator/ValidatorUtilTest.java @@ -0,0 +1,403 @@ +package org.folio.rest.validator; + +import static org.folio.rest.validator.ValidatorUtil.checkDateValid; +import static org.folio.rest.validator.ValidatorUtil.checkDatesOrder; +import static org.folio.rest.validator.ValidatorUtil.checkFalseOrNull; +import static org.folio.rest.validator.ValidatorUtil.checkInRange; +import static org.folio.rest.validator.ValidatorUtil.checkIsBlank; +import static org.folio.rest.validator.ValidatorUtil.checkIsEmpty; +import static org.folio.rest.validator.ValidatorUtil.checkIsEmptyCollection; +import static org.folio.rest.validator.ValidatorUtil.checkIsEqual; +import static org.folio.rest.validator.ValidatorUtil.checkIsNotAllBlank; +import static org.folio.rest.validator.ValidatorUtil.checkIsNotBlank; +import static org.folio.rest.validator.ValidatorUtil.checkIsNotEmpty; +import static org.folio.rest.validator.ValidatorUtil.checkIsNotNull; +import static org.folio.rest.validator.ValidatorUtil.checkIsNull; +import static org.folio.rest.validator.ValidatorUtil.checkMaxLength; +import static org.folio.rest.validator.ValidatorUtil.checkNoHtml; +import static org.folio.rest.validator.ValidatorUtil.checkUrlFormat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.Collections; +import java.util.List; +import org.folio.rest.exception.InputValidationException; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class ValidatorUtilTest { + + @Nested + class CheckIsNotEmpty { + + @Test + void shouldThrow_whenValueIsEmpty() { + var ex = assertThrows(InputValidationException.class, () -> checkIsNotEmpty("field", "")); + assertEquals("Invalid field", ex.getMessage()); + assertEquals("field must not be empty", ex.getMessageDetail()); + } + + @Test + void shouldThrow_whenValueIsNull() { + var ex = assertThrows(InputValidationException.class, () -> checkIsNotEmpty("field", null)); + assertEquals("Invalid field", ex.getMessage()); + assertEquals("field must not be empty", ex.getMessageDetail()); + } + + @Test + void shouldNotThrow_whenValueIsPresent() { + assertDoesNotThrow(() -> checkIsNotEmpty("field", "value")); + } + } + + @Nested + class CheckIsEmpty { + + @Test + void shouldThrow_whenValueIsNotEmpty() { + var ex = assertThrows(InputValidationException.class, () -> checkIsEmpty("field", "value")); + assertEquals("Invalid field", ex.getMessage()); + assertEquals("field must be empty", ex.getMessageDetail()); + } + + @Test + void shouldNotThrow_whenValueIsEmpty() { + assertDoesNotThrow(() -> checkIsEmpty("field", "")); + } + + @Test + void shouldNotThrow_whenValueIsNull() { + assertDoesNotThrow(() -> checkIsEmpty("field", null)); + } + } + + @Nested + class CheckIsBlank { + + @Test + void shouldThrow_whenValueIsNotBlank() { + var ex = assertThrows(InputValidationException.class, () -> checkIsBlank("field", "text")); + assertEquals("Invalid field", ex.getMessage()); + assertEquals("field must be empty", ex.getMessageDetail()); + } + + @Test + void shouldNotThrow_whenValueIsBlank() { + assertDoesNotThrow(() -> checkIsBlank("field", " ")); + } + + @Test + void shouldNotThrow_whenValueIsNull() { + assertDoesNotThrow(() -> checkIsBlank("field", null)); + } + } + + @Nested + class CheckIsNotBlank { + + @Test + void shouldThrow_whenValueIsBlank() { + var ex = assertThrows(InputValidationException.class, () -> checkIsNotBlank("field", " ")); + assertEquals("Invalid field", ex.getMessage()); + assertEquals("field must not be empty", ex.getMessageDetail()); + } + + @Test + void shouldThrow_whenValueIsNull() { + var ex = assertThrows(InputValidationException.class, () -> checkIsNotBlank("field", null)); + assertEquals("Invalid field", ex.getMessage()); + assertEquals("field must not be empty", ex.getMessageDetail()); + } + + @Test + void shouldNotThrow_whenValueHasContent() { + assertDoesNotThrow(() -> checkIsNotBlank("field", "text")); + } + } + + @Nested + class CheckIsNotAllBlank { + + @Test + void shouldThrow_whenAllValuesAreBlank() { + var ex = assertThrows(InputValidationException.class, + () -> checkIsNotAllBlank("field", "", " ", null)); + assertEquals("Invalid field", ex.getMessage()); + assertEquals("At least one of field must not be empty", ex.getMessageDetail()); + } + + @Test + void shouldNotThrow_whenAtLeastOneValueIsNotBlank() { + assertDoesNotThrow(() -> checkIsNotAllBlank("field", "", "value")); + } + } + + @Nested + class CheckFalseOrNull { + + @Test + void shouldThrow_whenValueIsTrue() { + var ex = assertThrows(InputValidationException.class, () -> checkFalseOrNull("field", true)); + assertEquals("Invalid field", ex.getMessage()); + assertEquals("field must be false", ex.getMessageDetail()); + } + + @Test + void shouldNotThrow_whenValueIsFalse() { + assertDoesNotThrow(() -> checkFalseOrNull("field", false)); + } + + @Test + void shouldNotThrow_whenValueIsNull() { + assertDoesNotThrow(() -> checkFalseOrNull("field", null)); + } + } + + @Nested + class CheckIsNotNull { + + @Test + void shouldThrow_whenValueIsNull() { + var ex = assertThrows(InputValidationException.class, () -> checkIsNotNull("field", null)); + assertEquals("Invalid field", ex.getMessage()); + assertEquals("field must not be null", ex.getMessageDetail()); + } + + @Test + void shouldNotThrow_whenValueIsPresent() { + assertDoesNotThrow(() -> checkIsNotNull("field", "value")); + } + } + + @Nested + class CheckIsNull { + + @Test + void shouldThrow_whenValueIsNotNull() { + var ex = assertThrows(InputValidationException.class, () -> checkIsNull("field", "value")); + assertEquals("Invalid field", ex.getMessage()); + assertEquals("field must be null or not specified", ex.getMessageDetail()); + } + + @Test + void shouldNotThrow_whenValueIsNull() { + assertDoesNotThrow(() -> checkIsNull("field", null)); + } + } + + @Nested + class CheckIsEmptyCollection { + + @Test + void shouldThrow_whenCollectionIsNotEmpty() { + var collection = List.of("item"); + var ex = assertThrows(InputValidationException.class, () -> checkIsEmptyCollection("field", collection)); + assertEquals("Invalid field", ex.getMessage()); + assertEquals("field must be null or not specified", ex.getMessageDetail()); + } + + @Test + void shouldNotThrow_whenCollectionIsEmpty() { + var collection = Collections.emptyList(); + assertDoesNotThrow(() -> checkIsEmptyCollection("field", collection)); + } + + @Test + void shouldNotThrow_whenCollectionIsNull() { + assertDoesNotThrow(() -> checkIsEmptyCollection("field", null)); + } + } + + @Nested + class CheckDateValid { + + @Test + void shouldThrow_whenDateFormatIsInvalid() { + var ex = assertThrows(InputValidationException.class, () -> checkDateValid("date", "2024/01/01")); + assertEquals("Invalid date", ex.getMessage()); + assertEquals("date has invalid format. Should be YYYY-MM-DD", ex.getMessageDetail()); + } + + @Test + void shouldThrow_whenDateHasInvalidValue() { + var ex = assertThrows(InputValidationException.class, () -> checkDateValid("date", "not-a-date")); + assertEquals("Invalid date", ex.getMessage()); + assertEquals("date has invalid format. Should be YYYY-MM-DD", ex.getMessageDetail()); + } + + @Test + void shouldNotThrow_whenDateIsValid() { + assertDoesNotThrow(() -> checkDateValid("date", "2024-01-15")); + } + + @Test + void shouldNotThrow_whenDateIsEmpty() { + assertDoesNotThrow(() -> checkDateValid("date", "")); + } + + @Test + void shouldNotThrow_whenDateIsNull() { + assertDoesNotThrow(() -> checkDateValid("date", null)); + } + } + + @Nested + class CheckMaxLength { + + @Test + void shouldThrow_whenValueExceedsMaxLength() { + var ex = assertThrows(InputValidationException.class, + () -> checkMaxLength("field", "toolongvalue", 5)); + assertEquals("Invalid field", ex.getMessage()); + assertEquals("field is too long (maximum is 5 characters)", ex.getMessageDetail()); + } + + @Test + void shouldNotThrow_whenValueEqualsMaxLength() { + assertDoesNotThrow(() -> checkMaxLength("field", "exact", 5)); + } + + @Test + void shouldNotThrow_whenValueIsShorterThanMaxLength() { + assertDoesNotThrow(() -> checkMaxLength("field", "ok", 5)); + } + + @Test + void shouldNotThrow_whenValueIsNull() { + assertDoesNotThrow(() -> checkMaxLength("field", null, 5)); + } + } + + @Nested + class CheckDatesOrder { + + @Test + void shouldThrow_whenStartIsAfterEnd() { + var ex = assertThrows(InputValidationException.class, + () -> checkDatesOrder("2024-06-01", "2024-01-01")); + assertEquals("Begin Coverage should be smaller than End Coverage", ex.getMessage()); + assertEquals("", ex.getMessageDetail()); + } + + @Test + void shouldNotThrow_whenStartIsBeforeEnd() { + assertDoesNotThrow(() -> checkDatesOrder("2024-01-01", "2024-06-01")); + } + + @Test + void shouldNotThrow_whenStartEqualsEnd() { + assertDoesNotThrow(() -> checkDatesOrder("2024-01-01", "2024-01-01")); + } + } + + @Nested + class CheckUrlFormat { + + @Test + void shouldThrow_whenUrlIsInvalid() { + var ex = assertThrows(InputValidationException.class, + () -> checkUrlFormat("url", "not a url")); + assertEquals("Invalid url", ex.getMessage()); + assertEquals("url has invalid format. Should start with https:// or http://", ex.getMessageDetail()); + } + + @Test + void shouldNotThrow_whenUrlIsHttps() { + assertDoesNotThrow(() -> checkUrlFormat("url", "https://example.com")); + } + + @Test + void shouldNotThrow_whenUrlIsHttp() { + assertDoesNotThrow(() -> checkUrlFormat("url", "http://example.com")); + } + } + + @Nested + class CheckInRange { + + @Test + void shouldThrow_whenValueIsNull() { + var ex = assertThrows(InputValidationException.class, + () -> checkInRange("field", null, 1, 10)); + assertEquals("Invalid field", ex.getMessage()); + assertEquals("field should be in range 1 - 10", ex.getMessageDetail()); + } + + @Test + void shouldThrow_whenValueIsBelowMin() { + var ex = assertThrows(InputValidationException.class, () -> checkInRange("field", 0, 1, 10)); + assertEquals("Invalid field", ex.getMessage()); + assertEquals("field should be in range 1 - 10", ex.getMessageDetail()); + } + + @Test + void shouldThrow_whenValueIsAboveMax() { + var ex = assertThrows(InputValidationException.class, () -> checkInRange("field", 11, 1, 10)); + assertEquals("Invalid field", ex.getMessage()); + assertEquals("field should be in range 1 - 10", ex.getMessageDetail()); + } + + @Test + void shouldNotThrow_whenValueIsAtMin() { + assertDoesNotThrow(() -> checkInRange("field", 1, 1, 10)); + } + + @Test + void shouldNotThrow_whenValueIsAtMax() { + assertDoesNotThrow(() -> checkInRange("field", 10, 1, 10)); + } + + @Test + void shouldNotThrow_whenValueIsInRange() { + assertDoesNotThrow(() -> checkInRange("field", 5, 1, 10)); + } + } + + @Nested + class CheckNoHtml { + + @Test + void shouldThrow_whenValueContainsHtmlTag() { + var ex = assertThrows(InputValidationException.class, + () -> checkNoHtml("field", "bold", "HTML not allowed")); + assertEquals("Invalid field", ex.getMessage()); + assertEquals("HTML not allowed", ex.getMessageDetail()); + } + + @Test + void shouldThrow_whenValueContainsSelfClosingTag() { + var ex = assertThrows(InputValidationException.class, + () -> checkNoHtml("field", "text
more", "HTML not allowed")); + assertEquals("Invalid field", ex.getMessage()); + assertEquals("HTML not allowed", ex.getMessageDetail()); + } + + @Test + void shouldNotThrow_whenValueHasNoHtml() { + assertDoesNotThrow(() -> checkNoHtml("field", "plain text", "HTML not allowed")); + } + + @Test + void shouldNotThrow_whenValueIsNull() { + assertDoesNotThrow(() -> checkNoHtml("field", null, "HTML not allowed")); + } + } + + @Nested + class CheckIsEqual { + + @Test + void shouldThrow_whenValuesAreNotEqual() { + var ex = assertThrows(InputValidationException.class, + () -> checkIsEqual("field", "expected", "actual")); + assertEquals("Invalid field", ex.getMessage()); + assertEquals("field should be equals to 'expected' but actual is 'actual'", ex.getMessageDetail()); + } + + @Test + void shouldNotThrow_whenValuesAreEqual() { + assertDoesNotThrow(() -> checkIsEqual("field", "value", "value")); + } + } +}