diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json
index 375082e46..b9b45eee4 100644
--- a/descriptors/ModuleDescriptor-template.json
+++ b/descriptors/ModuleDescriptor-template.json
@@ -1003,6 +1003,36 @@
"id": "orders.routing-list",
"version": "1.0",
"handlers": [
+ {
+ "methods": ["GET"],
+ "pathPattern": "/orders/routing-lists",
+ "permissionsRequired": ["orders.routing-lists.collection.get"],
+ "modulePermissions": ["orders-storage.routing-lists.collection.get"]
+ },
+ {
+ "methods": ["POST"],
+ "pathPattern": "/orders/routing-lists",
+ "permissionsRequired": ["orders.routing-lists.item.post"],
+ "modulePermissions": ["orders-storage.routing-lists.item.post"]
+ },
+ {
+ "methods": ["GET"],
+ "pathPattern": "/orders/routing-lists/{id}",
+ "permissionsRequired": ["orders.routing-lists.item.get"],
+ "modulePermissions": ["orders-storage.routing-lists.item.get"]
+ },
+ {
+ "methods": ["PUT"],
+ "pathPattern": "/orders/routing-lists/{id}",
+ "permissionsRequired": ["orders.routing-lists.item.put"],
+ "modulePermissions": ["orders-storage.routing-lists.item.put"]
+ },
+ {
+ "methods": ["DELETE"],
+ "pathPattern": "/orders/routing-lists/{id}",
+ "permissionsRequired": ["orders.routing-lists.item.delete"],
+ "modulePermissions": ["orders-storage.routing-lists.item.delete"]
+ },
{
"methods": ["GET"],
"pathPattern": "/orders/routing-lists/{id}/template",
@@ -1731,11 +1761,49 @@
"displayName" : "orders holding-summary get",
"description" : "Holding summary"
},
+ {
+ "permissionName" : "orders.routing-lists.collection.get",
+ "displayName" : "orders routing-list collection get",
+ "description" : "Orders routing-list collection get"
+ },
+ {
+ "permissionName" : "orders.routing-lists.item.post",
+ "displayName" : "orders routing-list item post",
+ "description" : "Orders routing-list item post"
+ },
+ {
+ "permissionName" : "orders.routing-lists.item.get",
+ "displayName" : "orders routing-list item get",
+ "description" : "Orders routing-list item get"
+ },
+ {
+ "permissionName" : "orders.routing-lists.item.put",
+ "displayName" : "orders routing-list item put",
+ "description" : "Orders routing-list item put"
+ },
+ {
+ "permissionName" : "orders.routing-lists.item.delete",
+ "displayName" : "orders routing-list item delete",
+ "description" : "Orders routing-list item delete"
+ },
{
"permissionName": "orders.routing-list-template.item.get",
"displayName" : "orders routing-list-template item get",
"description" : "Orders routing-list-template item get"
},
+ {
+ "permissionName" : "orders.routing-lists.all",
+ "displayName" : "All routing list perms",
+ "description" : "All permissions for the routing list",
+ "subPermissions" : [
+ "orders.routing-lists.collection.get",
+ "orders.routing-lists.item.post",
+ "orders.routing-lists.item.get",
+ "orders.routing-lists.item.put",
+ "orders.routing-lists.item.delete",
+ "orders.routing-list-template.item.get"
+ ]
+ },
{
"permissionName": "orders.all",
"displayName": "orders - all permissions",
@@ -1771,7 +1839,7 @@
"orders.holding-summary.collection.get",
"orders.acquisition-methods.all",
"orders.export-history.all",
- "orders.routing-list-template.item.get"
+ "orders.routing-lists.all"
]
},
{
diff --git a/ramls/routing-lists.raml b/ramls/routing-lists.raml
index b98725dad..922e4de34 100644
--- a/ramls/routing-lists.raml
+++ b/ramls/routing-lists.raml
@@ -1,43 +1,84 @@
#%RAML 1.0
-title: "RoutingList"
+title: Routing Lists
baseUri: https://github.com/folio-org/mod-orders
version: v1.0
documentation:
- - title: Routing lists
- content: CRUD API to manage routing lists.
+ - title: Routing Lists API
+ content: API for routing lists
types:
- routing_list: !include acq-models/mod-orders-storage/schemas/routing_list.json
- routing_list_collection: !include acq-models/mod-orders-storage/schemas/routing_list_collection.json
- UUID:
- type: string
- pattern: ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
+ routing-list: !include acq-models/mod-orders-storage/schemas/routing_list.json
+ routing-list-collection: !include acq-models/mod-orders-storage/schemas/routing_list_collection.json
+ error: !include raml-util/schemas/error.schema
+ errors: !include raml-util/schemas/errors.schema
+ UUID:
+ type: string
+ pattern: ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
traits:
- pageable: !include raml-util/traits/pageable.raml
- searchable: !include raml-util/traits/searchable.raml
+ pageable: !include raml-util/traits/pageable.raml
+ searchable: !include raml-util/traits/searchable.raml
+ validate: !include raml-util/traits/validation.raml
resourceTypes:
collection: !include rtypes/collection-with-json-response.raml
collection-item: !include rtypes/item-collection-with-json-response.raml
/orders/routing-lists:
+ displayName: Routing Lists
type:
collection:
exampleCollection: !include acq-models/mod-orders-storage/examples/routing_list_collection.sample
exampleItem: !include acq-models/mod-orders-storage/examples/routing_list_get.sample
- schemaCollection: routing_list_collection
- schemaItem: routing_list
+ schemaCollection: routing-list-collection
+ schemaItem: routing-list
get:
- description: Get routing lists
+ description: Get a collection of routing lists
is: [
- searchable: {description: "with valid searchable fields: for example routing list", example: "[\"routing_list\", \"ROUTING_LIST\", \"=\"]"},
+ searchable: { description: "CQL query", example: "name=MyRoutingList" },
pageable
]
post:
- description: Create routing lists
-
+ description: Create a new routing list record
+ is: [validate]
+ body:
+ application/json:
+ type: routing-list
+ example:
+ strict: false
+ value: !include acq-models/mod-orders-storage/examples/routing_list_get.sample
+ responses:
+ 201:
+ description: "Returns a newly created item, with server-controlled fields like 'id' populated"
+ body:
+ application/json:
+ example: !include acq-models/mod-orders-storage/examples/routing_list_get.sample
+ 400:
+ description: "Bad request, e.g. malformed request body or query parameter. Details of the error (e.g. name of the parameter or line/character number with malformed data) provided in the response."
+ body:
+ application/json:
+ type: error
+ 401:
+ description: "Not authorized to perform requested action"
+ body:
+ application/json:
+ type: error
+ 500:
+ description: "Internal server error, e.g. due to misconfiguration"
+ body:
+ application/json:
+ type: error
+ /{id}:
+ uriParameters:
+ id:
+ description: The UUID of a Routing List
+ type: UUID
+ description: Get, Delete or Update a specific routing list
+ type:
+ collection-item:
+ exampleItem: !include acq-models/mod-orders-storage/examples/routing_list_get.sample
+ schema: routing-list
/{id}/template:
uriParameters:
id:
@@ -45,3 +86,4 @@ resourceTypes:
type: UUID
get:
description: Execute mod-template-engine to process templates with replaced token placeholders [update]
+
diff --git a/src/main/java/org/folio/config/ApplicationConfig.java b/src/main/java/org/folio/config/ApplicationConfig.java
index 6c1a45321..4870506b7 100644
--- a/src/main/java/org/folio/config/ApplicationConfig.java
+++ b/src/main/java/org/folio/config/ApplicationConfig.java
@@ -112,7 +112,7 @@
import org.folio.service.pieces.flows.update.PieceUpdateFlowInventoryManager;
import org.folio.service.pieces.flows.update.PieceUpdateFlowManager;
import org.folio.service.pieces.flows.update.PieceUpdateFlowPoLineService;
-import org.folio.service.RoutingListService;
+import org.folio.service.routinglists.RoutingListService;
import org.folio.service.titles.TitleValidationService;
import org.folio.service.titles.TitlesService;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -437,8 +437,8 @@ CompositeOrderDynamicDataPopulateService combinedPopulateService(CompositeOrderR
}
@Bean
- RoutingListService routingListService(RestClient restClient, UserService userService) {
- return new RoutingListService(restClient, userService);
+ RoutingListService routingListService(RestClient restClient, PurchaseOrderLineService purchaseOrderLineService, UserService userService) {
+ return new RoutingListService(restClient, purchaseOrderLineService, userService);
}
@Bean
diff --git a/src/main/java/org/folio/rest/core/exceptions/ErrorCodes.java b/src/main/java/org/folio/rest/core/exceptions/ErrorCodes.java
index e79a11145..238d306d2 100644
--- a/src/main/java/org/folio/rest/core/exceptions/ErrorCodes.java
+++ b/src/main/java/org/folio/rest/core/exceptions/ErrorCodes.java
@@ -117,7 +117,10 @@ public enum ErrorCodes {
CLAIMING_CONFIG_INVALID("claimingConfigInvalid", "Claiming interval should be set and greater than 0 if claiming is active"),
TEMPLATE_NAME_ALREADY_EXISTS("templateNameNotUnique", "Template name already exists"),
BARCODE_IS_NOT_UNIQUE("barcodeIsNotUnique", "The barcode already exists. The barcode must be unique"),
- DELETE_WITH_EXPENDED_AMOUNT("deleteWithExpendedAmount", "Cannot delete en encumbrance with an expended amount");
+ DELETE_WITH_EXPENDED_AMOUNT("deleteWithExpendedAmount", "Cannot delete an encumbrance with an expended amount"),
+ INVALID_ROUTING_LIST_FOR_PO_LINE_FORMAT("invalidRoutingListForPoLineFormat", "Cannot create routing list for POL without 'Physical' or 'P/E Mix' order format"),
+ ROUTING_LIST_LIMIT_REACHED_FOR_PO_LINE("routingListLimitReachedForPoLine", "Cannot create routing list for POL as the associated lists' amount is not less than Physical copies"),
+ PO_LINE_NOT_FOUND_FOR_ROUTING_LIST("poLineNotFoundForRoutingList", "Cannot find a corresponding PO Line with the provided id");
private final String code;
private final String description;
diff --git a/src/main/java/org/folio/rest/impl/RoutingListsAPI.java b/src/main/java/org/folio/rest/impl/RoutingListsAPI.java
index 23a61328d..c57a08f0a 100644
--- a/src/main/java/org/folio/rest/impl/RoutingListsAPI.java
+++ b/src/main/java/org/folio/rest/impl/RoutingListsAPI.java
@@ -5,18 +5,19 @@
import javax.ws.rs.core.Response;
import java.util.Map;
-import io.vertx.core.AsyncResult;
-import io.vertx.core.Context;
-import io.vertx.core.Handler;
-import io.vertx.core.Vertx;
-import org.apache.commons.lang.NotImplementedException;
+import org.folio.rest.annotations.Validate;
import org.folio.rest.core.models.RequestContext;
import org.folio.rest.jaxrs.model.RoutingList;
import org.folio.rest.jaxrs.resource.OrdersRoutingLists;
-import org.folio.service.RoutingListService;
+import org.folio.service.routinglists.RoutingListService;
import org.folio.spring.SpringContextUtil;
import org.springframework.beans.factory.annotation.Autowired;
+import io.vertx.core.AsyncResult;
+import io.vertx.core.Context;
+import io.vertx.core.Handler;
+import io.vertx.core.Vertx;
+
public class RoutingListsAPI extends BaseApi implements OrdersRoutingLists {
@Autowired
@@ -27,15 +28,44 @@ public RoutingListsAPI() {
}
@Override
- public void getOrdersRoutingLists(String query, String totalRecords, int offset, int limit, Map okapiHeaders,
- Handler> asyncResultHandler, Context vertxContext) {
- throw new NotImplementedException();
+ @Validate
+ public void getOrdersRoutingLists(String query, String totalRecords, int offset, int limit, Map okapiHeaders, Handler> asyncResultHandler, Context vertxContext) {
+ routingListService.getRoutingLists(limit, offset, query, new RequestContext(vertxContext, okapiHeaders))
+ .onSuccess(lists -> asyncResultHandler.handle(succeededFuture(buildOkResponse(lists))))
+ .onFailure(fail -> handleErrorResponse(asyncResultHandler, fail));
+ }
+
+ @Override
+ @Validate
+ public void postOrdersRoutingLists(RoutingList entity, Map okapiHeaders, Handler> asyncResultHandler, Context vertxContext) {
+ routingListService.createRoutingList(entity, new RequestContext(vertxContext, okapiHeaders))
+ .onSuccess(list -> asyncResultHandler.handle(succeededFuture(buildOkResponse(list))))
+ .onFailure(fail -> handleErrorResponse(asyncResultHandler, fail));
+ }
+
+ @Override
+ @Validate
+ public void getOrdersRoutingListsById(String id, Map okapiHeaders, Handler> asyncResultHandler, Context vertxContext) {
+ routingListService.getRoutingList(id, new RequestContext(vertxContext, okapiHeaders))
+ .onSuccess(list -> asyncResultHandler.handle(succeededFuture(buildOkResponse(list))))
+ .onFailure(fail -> handleErrorResponse(asyncResultHandler, fail));
+ }
+
+ @Override
+ @Validate
+ public void deleteOrdersRoutingListsById(String id, Map okapiHeaders, Handler> asyncResultHandler, Context vertxContext) {
+ routingListService.deleteRoutingList(id, new RequestContext(vertxContext, okapiHeaders))
+ .onSuccess(list -> asyncResultHandler.handle(succeededFuture(buildNoContentResponse())))
+ .onFailure(fail -> handleErrorResponse(asyncResultHandler, fail));
+
}
@Override
- public void postOrdersRoutingLists(RoutingList entity, Map okapiHeaders,
- Handler> asyncResultHandler, Context vertxContext) {
- throw new NotImplementedException();
+ @Validate
+ public void putOrdersRoutingListsById(String id, RoutingList entity, Map okapiHeaders, Handler> asyncResultHandler, Context vertxContext) {
+ routingListService.updateRoutingList(entity, new RequestContext(vertxContext, okapiHeaders))
+ .onSuccess(list -> asyncResultHandler.handle(succeededFuture(buildNoContentResponse())))
+ .onFailure(fail -> handleErrorResponse(asyncResultHandler, fail));
}
@Override
diff --git a/src/main/java/org/folio/service/RoutingListService.java b/src/main/java/org/folio/service/routinglists/RoutingListService.java
similarity index 57%
rename from src/main/java/org/folio/service/RoutingListService.java
rename to src/main/java/org/folio/service/routinglists/RoutingListService.java
index 8c9b92b9e..6837354f2 100644
--- a/src/main/java/org/folio/service/RoutingListService.java
+++ b/src/main/java/org/folio/service/routinglists/RoutingListService.java
@@ -1,4 +1,4 @@
-package org.folio.service;
+package org.folio.service.routinglists;
import static org.folio.orders.utils.ResourcePathResolver.ORDER_SETTINGS;
import static org.folio.orders.utils.ResourcePathResolver.ROUTING_LISTS;
@@ -9,8 +9,7 @@
import java.util.List;
import java.util.UUID;
-import io.vertx.core.Future;
-import io.vertx.core.json.JsonObject;
+import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.kafka.common.errors.ResourceNotFoundException;
@@ -18,34 +17,104 @@
import org.apache.logging.log4j.Logger;
import org.folio.models.TemplateProcessingRequest;
import org.folio.models.UserCollection;
+import org.folio.rest.RestConstants;
import org.folio.rest.acq.model.SettingCollection;
import org.folio.rest.core.RestClient;
+import org.folio.rest.core.exceptions.HttpException;
import org.folio.rest.core.models.RequestContext;
import org.folio.rest.core.models.RequestEntry;
+import org.folio.rest.jaxrs.model.Error;
+import org.folio.rest.jaxrs.model.Errors;
+import org.folio.rest.jaxrs.model.PoLine;
import org.folio.rest.jaxrs.model.RoutingList;
+import org.folio.rest.jaxrs.model.RoutingListCollection;
+import org.folio.service.UserService;
+import org.folio.service.orders.PurchaseOrderLineService;
+import org.folio.service.routinglists.validators.RoutingListValidatorUtil;
+
+import io.vertx.core.Future;
+import io.vertx.core.json.JsonObject;
public class RoutingListService {
+ private static final Logger logger = LogManager.getLogger(RoutingListService.class);
- private static final Logger log = LogManager.getLogger();
- private static final UUID TEMPLATE_REQUEST_ID = UUID.fromString("9465105a-e8a1-470c-9817-142d33bc4fcd");
- private static final String TEMPLATE_REQUEST_LANG = "en";
- private static final String TEMPLATE_REQUEST_OUTPUT = "text/html";
private static final String ROUTING_LIST_ENDPOINT = resourcesPath(ROUTING_LISTS);
private static final String ORDER_SETTINGS_ENDPOINT = resourcesPath(ORDER_SETTINGS);
private static final String ROUTING_USER_ADDRESS_TYPE_ID = "ROUTING_USER_ADDRESS_TYPE_ID";
private static final String ROUTING_LIST_BY_ID_ENDPOINT = ROUTING_LIST_ENDPOINT + "/{id}";
+ private static final String ROUTING_LIST_BY_POL_ID = "poLineId==%s";
+
private static final String TEMPLATE_REQUEST_ENDPOINT = resourcesPath(TEMPLATE_REQUEST);
+ private static final UUID TEMPLATE_REQUEST_ID = UUID.fromString("9465105a-e8a1-470c-9817-142d33bc4fcd");
+ private static final String TEMPLATE_REQUEST_LANG = "en";
+ private static final String TEMPLATE_REQUEST_OUTPUT = "text/html";
- private final RestClient restClient;
+ private final PurchaseOrderLineService poLineService;
private final UserService userService;
+ private final RestClient restClient;
- public RoutingListService(RestClient restClient, UserService userService) {
+ public RoutingListService(RestClient restClient, PurchaseOrderLineService poLineService, UserService userService) {
this.restClient = restClient;
+ this.poLineService = poLineService;
this.userService = userService;
}
+ public Future getRoutingList(String rListId, RequestContext requestContext) {
+ RequestEntry requestEntry = new RequestEntry(ROUTING_LIST_BY_ID_ENDPOINT).withId(rListId);
+ return restClient.get(requestEntry, RoutingList.class, requestContext);
+ }
+
+ public Future updateRoutingList(RoutingList routingList, RequestContext requestContext) {
+ try {
+ validateRoutingList(routingList, requestContext);
+ } catch (HttpException e) {
+ return Future.failedFuture(e);
+ }
+ RequestEntry requestEntry = new RequestEntry(ROUTING_LIST_BY_ID_ENDPOINT).withId(routingList.getId());
+ return restClient.put(requestEntry, routingList, requestContext);
+ }
+
+ public Future deleteRoutingList(String rListId, RequestContext requestContext) {
+ RequestEntry requestEntry = new RequestEntry(ROUTING_LIST_BY_ID_ENDPOINT).withId(rListId);
+ return restClient.delete(requestEntry, requestContext);
+ }
+
+ public Future createRoutingList(RoutingList routingList, RequestContext requestContext) {
+ try {
+ validateRoutingList(routingList, requestContext);
+ } catch (HttpException e) {
+ return Future.failedFuture(e);
+ }
+ RequestEntry requestEntry = new RequestEntry(ROUTING_LIST_ENDPOINT);
+ return restClient.post(requestEntry, routingList, RoutingList.class, requestContext);
+ }
+
+ public Future getRoutingLists(int limit, int offset, String query, RequestContext requestContext) {
+ RequestEntry requestEntry = new RequestEntry(ROUTING_LIST_ENDPOINT)
+ .withLimit(limit)
+ .withOffset(offset)
+ .withQuery(query);
+ return restClient.get(requestEntry, RoutingListCollection.class, requestContext);
+ }
+
+ private Future getRoutingListsByPoLineId(String poLineId, RequestContext requestContext) {
+ String query = String.format(ROUTING_LIST_BY_POL_ID, poLineId);
+ return getRoutingLists(Integer.MAX_VALUE, 0, query, requestContext);
+ }
+
+ private void validateRoutingList(RoutingList routingList, RequestContext requestContext) throws HttpException {
+ RoutingListCollection routingLists = getRoutingListsByPoLineId(routingList.getPoLineId(), requestContext).result();
+ PoLine poLine = poLineService.getOrderLineById(routingList.getPoLineId(), requestContext).result();
+ List combinedErrors = RoutingListValidatorUtil.validateRoutingList(routingLists, poLine);
+ if (CollectionUtils.isNotEmpty(combinedErrors)) {
+ Errors errors = new Errors().withErrors(combinedErrors).withTotalRecords(combinedErrors.size());
+ logger.error("Validation error: {}", JsonObject.mapFrom(errors).encodePrettily());
+ throw new HttpException(RestConstants.VALIDATION_ERROR, errors);
+ }
+ }
+
public Future processTemplateRequest(String routingListId, RequestContext requestContext) {
- log.debug("processTemplateRequest: Tying to process template request for routingListId={}", routingListId);
+ logger.debug("processTemplateRequest: Tying to process template request for routingListId={}", routingListId);
return getRoutingListById(routingListId, requestContext)
.compose(routingList -> getUsersAndCreateTemplate(routingList, requestContext))
.compose(templateProcessingRequest -> postTemplateRequest(templateProcessingRequest, requestContext));
@@ -69,7 +138,7 @@ private Future getAddressTypeId(RequestContext requestContext) {
.map(settingCollection -> {
var settings = settingCollection.getSettings();
if (ObjectUtils.isEmpty(settings) || StringUtils.isBlank(settings.get(0).getValue())) {
- log.error("getAddressTypeId:: Setting is not found with key={}", ROUTING_USER_ADDRESS_TYPE_ID);
+ logger.error("getAddressTypeId:: Setting is not found with key={}", ROUTING_USER_ADDRESS_TYPE_ID);
throw new ResourceNotFoundException(String.format("Setting is not found with key=%s", ROUTING_USER_ADDRESS_TYPE_ID));
}
return settings.get(0).getValue();
@@ -82,7 +151,7 @@ private TemplateProcessingRequest createTemplateRequest(RoutingList routingList,
.withRoutingList(fillRoutingListForContext(routingList))
.withUsers(fillUsersForContext(users, addressTypeId)));
- log.info("createTemplateRequest:: TemplateProcessingRequest object created for routing list name: {}",
+ logger.info("createTemplateRequest:: TemplateProcessingRequest object created for routing list name: {}",
templateRequest.getContext().getRoutingList().getName());
return templateRequest;
}
@@ -117,11 +186,11 @@ private List fillUsersForContext(UserCollection
private String getUserAddress(List addressList, String addressTypeId) {
for (UserCollection.User.Personal.Address address : addressList) {
if (address.getAddressTypeId().equals(addressTypeId)) {
- log.info("getUserAddress:: Required address with addressTypeId={} is found", addressTypeId);
+ logger.info("getUserAddress:: Required address with addressTypeId={} is found", addressTypeId);
return address.getAddressLine1();
}
}
- log.warn("getUserAddress:: Required address is not found with addressTypId={}", addressTypeId);
+ logger.warn("getUserAddress:: Required address is not found with addressTypId={}", addressTypeId);
return "";
}
@@ -133,7 +202,8 @@ private RoutingList fillRoutingListForContext(RoutingList routingList) {
private Future postTemplateRequest(TemplateProcessingRequest templateRequest, RequestContext requestContext) {
var requestEntry = new RequestEntry(TEMPLATE_REQUEST_ENDPOINT);
- log.info("postTemplateRequest:: Sending template request with routing list name={}", templateRequest.getContext().getRoutingList().getName());
+ logger.info("postTemplateRequest:: Sending template request with routing list name={}", templateRequest.getContext().getRoutingList().getName());
return restClient.postJsonObject(requestEntry, JsonObject.mapFrom(templateRequest), requestContext);
}
+
}
diff --git a/src/main/java/org/folio/service/routinglists/validators/RoutingListValidatorUtil.java b/src/main/java/org/folio/service/routinglists/validators/RoutingListValidatorUtil.java
new file mode 100644
index 000000000..3f68e28d9
--- /dev/null
+++ b/src/main/java/org/folio/service/routinglists/validators/RoutingListValidatorUtil.java
@@ -0,0 +1,41 @@
+package org.folio.service.routinglists.validators;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.folio.rest.core.exceptions.ErrorCodes;
+import org.folio.rest.jaxrs.model.Error;
+import org.folio.rest.jaxrs.model.Location;
+import org.folio.rest.jaxrs.model.PoLine;
+import org.folio.rest.jaxrs.model.RoutingListCollection;
+
+public class RoutingListValidatorUtil {
+
+ private RoutingListValidatorUtil() { }
+
+ public static List validateRoutingList(RoutingListCollection rListExisting, PoLine poLine) {
+ List errors = new ArrayList<>();
+ if (poLine == null) {
+ errors.add(ErrorCodes.PO_LINE_NOT_FOUND_FOR_ROUTING_LIST);
+ } else if (!isPoLineFormatValid(poLine)) {
+ errors.add(ErrorCodes.INVALID_ROUTING_LIST_FOR_PO_LINE_FORMAT);
+ } else if (isRoutingListsLimitReached(rListExisting, poLine)) {
+ errors.add(ErrorCodes.ROUTING_LIST_LIMIT_REACHED_FOR_PO_LINE);
+ }
+ return errors.stream().map(ErrorCodes::toError).toList();
+ }
+
+ private static boolean isPoLineFormatValid(PoLine poLine) {
+ return poLine.getOrderFormat() == PoLine.OrderFormat.PHYSICAL_RESOURCE
+ || poLine.getOrderFormat() == PoLine.OrderFormat.P_E_MIX;
+ }
+
+ private static boolean isRoutingListsLimitReached(RoutingListCollection rListExisting, PoLine poLine) {
+ return getQuantityPhysicalTotal(poLine) <= rListExisting.getTotalRecords();
+ }
+
+ private static int getQuantityPhysicalTotal(PoLine poLine) {
+ return poLine.getLocations().stream().mapToInt(Location::getQuantityPhysical).sum();
+ }
+
+}
diff --git a/src/test/java/org/folio/ApiTestSuite.java b/src/test/java/org/folio/ApiTestSuite.java
index f0dac243e..145383176 100644
--- a/src/test/java/org/folio/ApiTestSuite.java
+++ b/src/test/java/org/folio/ApiTestSuite.java
@@ -99,7 +99,7 @@
import org.folio.service.pieces.flows.update.PieceUpdateFlowManagerTest;
import org.folio.service.pieces.flows.update.PieceUpdateFlowPoLineServiceTest;
import org.folio.service.pieces.validators.PieceValidatorUtilTest;
-import org.folio.service.RoutingListServiceTest;
+import org.folio.service.routinglists.RoutingListServiceTest;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Nested;
diff --git a/src/test/java/org/folio/TestUtils.java b/src/test/java/org/folio/TestUtils.java
index 1c5273899..ba9216596 100644
--- a/src/test/java/org/folio/TestUtils.java
+++ b/src/test/java/org/folio/TestUtils.java
@@ -175,6 +175,22 @@ public static CompositePoLine getMinimalContentCompositePoLine(String orderId) {
.withPurchaseOrderId(orderId);
}
+ public static PoLine getMinimalContentPoLine() {
+ return getMinimalContentPoLine(MIN_PO_ID);
+ }
+
+ public static PoLine getMinimalContentPoLine(String orderId) {
+ return new PoLine().withSource(PoLine.Source.EDI)
+ .withId(MIN_PO_LINE_ID)
+ .withOrderFormat(PoLine.OrderFormat.PHYSICAL_RESOURCE)
+ .withAcquisitionMethod(PURCHASE_METHOD)
+ .withPhysical(new Physical().withMaterialType("2d1398ae-e1aa-4c7c-b9c9-15adf8cf6425"))
+ .withCost(new Cost().withCurrency("EUR").withQuantityPhysical(1).withListUnitPrice(10.0))
+ .withLocations(Collections.singletonList(new Location().withLocationId("2a00b0be-1447-42a1-a112-124450991899").withQuantityPhysical(1).withQuantity(1)))
+ .withTitleOrPackage("Title")
+ .withPurchaseOrderId(orderId);
+ }
+
public static Title getMinimalContentTitle() {
return new Title().withTitle("Test title").withId(SAMPLE_TITLE_ID);
}
@@ -240,4 +256,13 @@ public static void validateSavedPoLines() {
poline.mapTo(PoLine.class);
});
}
+
+ public static List getLocationPhysicalCopies(int n) {
+ return List.of(new Location()
+ .withLocationId(UUID.randomUUID().toString())
+ .withQuantityElectronic(0)
+ .withQuantityPhysical(n)
+ .withQuantity(n));
+ }
+
}
diff --git a/src/test/java/org/folio/rest/impl/MockServer.java b/src/test/java/org/folio/rest/impl/MockServer.java
index dc4c9220b..4c9f28e6f 100644
--- a/src/test/java/org/folio/rest/impl/MockServer.java
+++ b/src/test/java/org/folio/rest/impl/MockServer.java
@@ -63,6 +63,7 @@
import static org.folio.orders.utils.ResourcePathResolver.FINANCE_EXCHANGE_RATE;
import static org.folio.orders.utils.ResourcePathResolver.FUNDS;
import static org.folio.orders.utils.ResourcePathResolver.LEDGERS;
+import static org.folio.orders.utils.ResourcePathResolver.ROUTING_LISTS;
import static org.folio.orders.utils.ResourcePathResolver.USER_TENANTS_ENDPOINT;
import static org.folio.orders.utils.ResourcePathResolver.LEDGER_FY_ROLLOVERS;
import static org.folio.orders.utils.ResourcePathResolver.LEDGER_FY_ROLLOVER_ERRORS;
@@ -200,6 +201,8 @@
import org.folio.rest.jaxrs.model.PurchaseOrderCollection;
import org.folio.rest.jaxrs.model.ReasonForClosure;
import org.folio.rest.jaxrs.model.ReasonForClosureCollection;
+import org.folio.rest.jaxrs.model.RoutingList;
+import org.folio.rest.jaxrs.model.RoutingListCollection;
import org.folio.rest.jaxrs.model.Suffix;
import org.folio.rest.jaxrs.model.SuffixCollection;
@@ -242,7 +245,7 @@ public class MockServer {
private static final String HOLDINGS_SOURCE_MOCK_DATA_PATH = BASE_MOCK_DATA_PATH + "holdingsSources/";
public static final String PIECE_RECORDS_MOCK_DATA_PATH = BASE_MOCK_DATA_PATH + "pieces/";
public static final String PO_LINES_MOCK_DATA_PATH = BASE_MOCK_DATA_PATH + "lines/";
- public static final String ROUTING_LIST_MOCK_DATA_PATH = BASE_MOCK_DATA_PATH + "routingLists/";
+ public static final String ROUTING_LISTS_MOCK_DATA_PATH = BASE_MOCK_DATA_PATH + "routingLists/";
public static final String USERS_MOCK_DATA_PATH = BASE_MOCK_DATA_PATH + "users/";
public static final String TITLES_MOCK_DATA_PATH = BASE_MOCK_DATA_PATH + "titles/";
private static final String ACQUISITIONS_UNITS_MOCK_DATA_PATH = BASE_MOCK_DATA_PATH + "acquisitionsUnits/units";
@@ -259,6 +262,7 @@ public class MockServer {
static final String ORDER_TEMPLATES_COLLECTION = ORDER_TEMPLATES_MOCK_DATA_PATH + "/orderTemplates.json";
private static final String FUNDS_PATH = BASE_MOCK_DATA_PATH + "funds/funds.json";
private static final String TITLES_PATH = BASE_MOCK_DATA_PATH + "titles/titles.json";
+ private static final String ROUTING_LISTS_PATH = BASE_MOCK_DATA_PATH + "routing-lists/routing-lists.json";
public static final String BUDGETS_PATH = BASE_MOCK_DATA_PATH + "budgets/budgets.json";
public static final String LEDGERS_PATH = BASE_MOCK_DATA_PATH + "ledgers/ledgers.json";
public static final String PATCH_ORDER_LINES_REQUEST_PATCH = BASE_MOCK_DATA_PATH + "patchOrderLines/patch.json";
@@ -546,6 +550,7 @@ private Router defineRoutes() {
router.post(resourcesPath(ORDER_TEMPLATES)).handler(ctx -> handlePostGenericSubObj(ctx, ORDER_TEMPLATES));
router.post(resourcesPath(FINANCE_BATCH_TRANSACTIONS)).handler(this::handleBatchTransactions);
router.post(resourcesPath(TITLES)).handler(ctx -> handlePostGenericSubObj(ctx, TITLES));
+ router.post(resourcesPath(ROUTING_LISTS)).handler(ctx -> handlePostGenericSubObj(ctx, ROUTING_LISTS));
router.post(resourcesPath(ACQUISITIONS_UNITS)).handler(ctx -> handlePostGenericSubObj(ctx, ACQUISITIONS_UNITS));
router.post(resourcesPath(ACQUISITION_METHODS)).handler(ctx -> handlePostGenericSubObj(ctx, ACQUISITION_METHODS));
@@ -599,6 +604,8 @@ private Router defineRoutes() {
router.get(resourcesPath(LEDGERS)).handler(this::handleGetLedgers);
router.get(resourcesPath(TITLES)).handler(this::handleGetTitles);
router.get(resourcePath(TITLES)).handler(this::handleGetOrderTitleById);
+ router.get(resourcesPath(ROUTING_LISTS)).handler(this::handleGetRoutingLists);
+ router.get(resourcePath(ROUTING_LISTS)).handler(this::handleGetRoutingListById);
router.get(resourcesPath(REASONS_FOR_CLOSURE)).handler(ctx -> handleGetGenericSubObjs(ctx, REASONS_FOR_CLOSURE));
router.get(resourcesPath(PREFIXES)).handler(ctx -> handleGetGenericSubObjs(ctx, PREFIXES));
router.get(resourcesPath(SUFFIXES)).handler(ctx -> handleGetGenericSubObjs(ctx, SUFFIXES));
@@ -633,6 +640,7 @@ private Router defineRoutes() {
router.put(resourcePath(ACQUISITIONS_MEMBERSHIPS)).handler(ctx -> handlePutGenericSubObj(ctx, ACQUISITIONS_MEMBERSHIPS));
router.put(resourcePath(ORDER_TEMPLATES)).handler(ctx -> handlePutGenericSubObj(ctx, ORDER_TEMPLATES));
router.put(resourcePath(TITLES)).handler(ctx -> handlePutGenericSubObj(ctx, TITLES));
+ router.put(resourcePath(ROUTING_LISTS)).handler(ctx -> handlePutGenericSubObj(ctx, ROUTING_LISTS));
router.put(resourcePath(REASONS_FOR_CLOSURE)).handler(ctx -> handlePutGenericSubObj(ctx, REASONS_FOR_CLOSURE));
router.put(resourcePath(PREFIXES)).handler(ctx -> handlePutGenericSubObj(ctx, PREFIXES));
router.put(resourcePath(SUFFIXES)).handler(ctx -> handlePutGenericSubObj(ctx, SUFFIXES));
@@ -649,6 +657,7 @@ private Router defineRoutes() {
router.delete(resourcePath(ACQUISITIONS_MEMBERSHIPS)).handler(ctx -> handleDeleteGenericSubObj(ctx, ACQUISITIONS_MEMBERSHIPS));
router.delete(resourcePath(ORDER_TEMPLATES)).handler(ctx -> handleDeleteGenericSubObj(ctx, ORDER_TEMPLATES));
router.delete(resourcePath(TITLES)).handler(ctx -> handleDeleteGenericSubObj(ctx, TITLES));
+ router.delete(resourcePath(ROUTING_LISTS)).handler(ctx -> handleDeleteGenericSubObj(ctx, ROUTING_LISTS));
router.delete(resourcePath(REASONS_FOR_CLOSURE)).handler(ctx -> handleDeleteGenericSubObj(ctx, REASONS_FOR_CLOSURE));
router.delete(resourcePath(PREFIXES)).handler(ctx -> handleDeleteGenericSubObj(ctx, PREFIXES));
router.delete(resourcePath(SUFFIXES)).handler(ctx -> handleDeleteGenericSubObj(ctx, SUFFIXES));
@@ -712,6 +721,25 @@ private JsonObject getTitlesByPoLineIds(List poLineIds) {
return JsonObject.mapFrom(record);
}
+ private JsonObject getRoutingListsByPoLineId(List poLineId) {
+ Supplier> getFromFile = () -> {
+ try {
+ return new JsonObject(getMockData(ROUTING_LISTS_PATH)).mapTo(RoutingListCollection.class).getRoutingLists();
+ } catch (IOException e) {
+ return Collections.emptyList();
+ }
+ };
+
+ List rLists = getMockEntries(ROUTING_LISTS, RoutingList.class).orElseGet(getFromFile);
+
+ if (!poLineId.isEmpty()) {
+ rLists.removeIf(item -> !item.getPoLineId().equals(poLineId.get(0)));
+ }
+
+ Object record = new RoutingListCollection().withRoutingLists(rLists).withTotalRecords(rLists.size());
+ return JsonObject.mapFrom(record);
+ }
+
private void handleGetFunds(RoutingContext ctx) {
String query = StringUtils.trimToEmpty(ctx.request().getParam(QUERY));
addServerRqQuery(FUNDS, query);
@@ -1942,6 +1970,60 @@ private void handleGetTitles(RoutingContext ctx) {
}
}
+ private void handleGetRoutingListById(RoutingContext ctx) {
+ logger.info("got: " + ctx.request().path());
+ String id = ctx.request().getParam(ID);
+ logger.info("id: " + id);
+
+ addServerRqRsData(HttpMethod.GET, ROUTING_LISTS, new JsonObject().put(ID, id));
+
+ if (ID_FOR_INTERNAL_SERVER_ERROR.equals(id)) {
+ serverResponse(ctx, 500, APPLICATION_JSON, INTERNAL_SERVER_ERROR.getReasonPhrase());
+ } else {
+ try {
+
+ // Attempt to find title in mock server memory
+ JsonObject existantTitle = getMockEntry(ROUTING_LISTS, id).orElse(null);
+
+ // If previous step has no result then attempt to find title in stubs
+ if (existantTitle == null) {
+ RoutingList title = new JsonObject(getMockData(String.format("%s%s.json", ROUTING_LISTS_MOCK_DATA_PATH, id))).mapTo(RoutingList.class);
+ existantTitle = JsonObject.mapFrom(title);
+ }
+
+ serverResponse(ctx, 200, APPLICATION_JSON, existantTitle.encodePrettily());
+ } catch (IOException e) {
+ serverResponse(ctx, 404, APPLICATION_JSON, id);
+ }
+ }
+ }
+
+ private void handleGetRoutingLists(RoutingContext ctx) {
+ String query = StringUtils.trimToEmpty(ctx.request().getParam(QUERY));
+ addServerRqQuery(ROUTING_LISTS, query);
+ if (query.contains(ID_FOR_INTERNAL_SERVER_ERROR)) {
+ serverResponse(ctx, 500, APPLICATION_JSON, INTERNAL_SERVER_ERROR.getReasonPhrase());
+ } else {
+ try {
+
+ List ids = Collections.emptyList();
+ if (query.contains("poLineId==")) {
+ ids = extractValuesFromQuery("poLineId", query);
+ }
+
+ JsonObject collection = getRoutingListsByPoLineId(ids);
+ addServerRqRsData(HttpMethod.GET, ROUTING_LISTS, collection);
+
+ ctx.response()
+ .setStatusCode(200)
+ .putHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON)
+ .end(collection.encodePrettily());
+ } catch (Exception e) {
+ serverResponse(ctx, 500, APPLICATION_JSON, INTERNAL_SERVER_ERROR.getReasonPhrase());
+ }
+ }
+ }
+
private List extractIdsFromQuery(String query) {
return extractValuesFromQuery(ID, query);
diff --git a/src/test/java/org/folio/rest/impl/RoutingListsApiTest.java b/src/test/java/org/folio/rest/impl/RoutingListsApiTest.java
index 401d0eb9e..8a9c2ad27 100644
--- a/src/test/java/org/folio/rest/impl/RoutingListsApiTest.java
+++ b/src/test/java/org/folio/rest/impl/RoutingListsApiTest.java
@@ -1,9 +1,15 @@
package org.folio.rest.impl;
+import static io.vertx.core.Future.failedFuture;
import static io.vertx.core.Future.succeededFuture;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static javax.ws.rs.core.MediaType.TEXT_PLAIN;
import static org.folio.RestTestUtils.prepareHeaders;
+import static org.folio.RestTestUtils.verifyDeleteResponse;
import static org.folio.RestTestUtils.verifyGet;
+import static org.folio.RestTestUtils.verifyPostResponse;
+import static org.folio.RestTestUtils.verifyPut;
+import static org.folio.RestTestUtils.verifySuccessGet;
import static org.folio.TestConfig.X_OKAPI_URL;
import static org.folio.TestConfig.autowireDependencies;
import static org.folio.TestConfig.clearServiceInteractions;
@@ -13,30 +19,58 @@
import static org.folio.TestConfig.isVerticleNotDeployed;
import static org.folio.TestConfig.mockPort;
import static org.folio.TestConstants.EMPTY_CONFIG_X_OKAPI_TENANT;
+import static org.folio.TestConstants.EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10;
+import static org.folio.TestConstants.ID_BAD_FORMAT;
+import static org.folio.TestConstants.ID_DOES_NOT_EXIST;
import static org.folio.TestConstants.ROUTING_LIST_ID;
import static org.folio.TestConstants.X_OKAPI_TOKEN;
import static org.folio.TestConstants.X_OKAPI_USER_ID;
+import static org.folio.TestConstants.X_OKAPI_USER_ID_WITH_ACQ_UNITS;
+import static org.folio.TestUtils.getLocationPhysicalCopies;
+import static org.folio.TestUtils.getMinimalContentPoLine;
+import static org.folio.TestUtils.getMockAsJson;
+import static org.folio.orders.utils.ResourcePathResolver.PO_LINES_STORAGE;
+import static org.folio.rest.RestConstants.BAD_REQUEST;
+import static org.folio.rest.RestConstants.NOT_FOUND;
import static org.folio.rest.RestConstants.OKAPI_URL;
+import static org.folio.rest.core.exceptions.ErrorCodes.INVALID_ROUTING_LIST_FOR_PO_LINE_FORMAT;
+import static org.folio.rest.core.exceptions.ErrorCodes.PO_LINE_NOT_FOUND_FOR_ROUTING_LIST;
+import static org.folio.rest.core.exceptions.ErrorCodes.ROUTING_LIST_LIMIT_REACHED_FOR_PO_LINE;
+import static org.folio.rest.impl.MockServer.ROUTING_LISTS_MOCK_DATA_PATH;
+import static org.folio.rest.impl.MockServer.addMockEntry;
import static org.folio.rest.impl.PurchaseOrdersApiTest.X_OKAPI_TENANT;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
-import io.vertx.core.Context;
-import io.vertx.core.json.JsonObject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.folio.ApiTestSuite;
+import org.folio.HttpStatus;
+import org.folio.rest.RestConstants;
+import org.folio.rest.core.exceptions.ErrorCodes;
+import org.folio.rest.core.exceptions.HttpException;
import org.folio.rest.core.models.RequestContext;
-import org.folio.service.RoutingListService;
+import org.folio.rest.jaxrs.model.Error;
+import org.folio.rest.jaxrs.model.Errors;
+import org.folio.rest.jaxrs.model.PoLine;
+import org.folio.rest.jaxrs.model.RoutingList;
+import org.folio.rest.jaxrs.model.RoutingListCollection;
+import org.folio.service.routinglists.RoutingListService;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
@@ -46,18 +80,28 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
-public class RoutingListsApiTest {
+import io.vertx.core.Context;
+import io.vertx.core.json.JsonObject;
+public class RoutingListsApiTest {
private static final Logger logger = LogManager.getLogger();
+
+ public static final String ROUTING_LISTS_ENDPOINT = "/orders/routing-lists";
+ private static final String ROUTING_LISTS_ID_PATH = ROUTING_LISTS_ENDPOINT + "/%s";
+ private static final String ROUTING_LIST_UUID = "c0d13648-347b-4ac9-8c2f-5bc47248b87e";
+ private static final String PO_LINE_UUID = "0009662b-8b80-4001-b704-ca10971f222d";
private static final String TEMPLATE_PROCESSING_REQUEST_ENDPOINT = "orders/routing-lists/" + ROUTING_LIST_ID + "/template";
+
+ private final JsonObject routingListJsonReqData = getMockAsJson(ROUTING_LISTS_MOCK_DATA_PATH + "routing-list.json");
private static boolean runningOnOwn;
+
@Autowired
private RoutingListService routingListService;
private RequestContext requestContext;
private Context ctxMock;
private Map okapiHeadersMock;
private AutoCloseable mockitoMocks;
-
+ private RoutingList sampleRoutingList;
@BeforeAll
static void before() throws InterruptedException, ExecutionException, TimeoutException {
@@ -68,7 +112,6 @@ static void before() throws InterruptedException, ExecutionException, TimeoutExc
initSpringContext(RoutingListsApiTest.ContextConfiguration.class);
}
-
@BeforeEach
void beforeEach() {
mockitoMocks = MockitoAnnotations.openMocks(this);
@@ -80,8 +123,11 @@ void beforeEach() {
okapiHeadersMock.put(X_OKAPI_TENANT.getName(), X_OKAPI_TENANT.getValue());
okapiHeadersMock.put(X_OKAPI_USER_ID.getName(), X_OKAPI_USER_ID.getValue());
requestContext = new RequestContext(ctxMock, okapiHeadersMock);
+ sampleRoutingList = routingListJsonReqData.mapTo(RoutingList.class)
+ .withPoLineId(PO_LINE_UUID);
}
+
@AfterEach
void afterEach() throws Exception {
mockitoMocks.close();
@@ -95,6 +141,137 @@ static void after() {
}
}
+ @Test
+ void testPostRoutingList() {
+ logger.info("=== Test POST Routing List (Create Routing List) ===");
+
+ PoLine poLine = getMinimalContentPoLine()
+ .withId(PO_LINE_UUID)
+ .withOrderFormat(PoLine.OrderFormat.PHYSICAL_RESOURCE)
+ .withLocations(getLocationPhysicalCopies(1));
+ addMockEntry(PO_LINES_STORAGE, JsonObject.mapFrom(poLine));
+
+ doReturn(succeededFuture(sampleRoutingList)).when(routingListService).createRoutingList(eq(sampleRoutingList), any(RequestContext.class));
+
+ verifyPostResponse(ROUTING_LISTS_ENDPOINT, JsonObject.mapFrom(sampleRoutingList).encode(),
+ prepareHeaders(EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10, X_OKAPI_USER_ID_WITH_ACQ_UNITS), APPLICATION_JSON, HttpStatus.HTTP_OK.toInt());
+ }
+
+ @Test
+ void testPostRoutingListShouldFailForInvalidOrderType() {
+ logger.info("=== Test POST Routing List should fail because it's POL has invalid order type ===");
+
+ PoLine poLine = getMinimalContentPoLine()
+ .withId(PO_LINE_UUID)
+ .withOrderFormat(PoLine.OrderFormat.ELECTRONIC_RESOURCE);
+ addMockEntry(PO_LINES_STORAGE, JsonObject.mapFrom(poLine));
+
+ var errorsExpected = getCodesAsErrors(INVALID_ROUTING_LIST_FOR_PO_LINE_FORMAT);
+ doReturn(failedFuture(new HttpException(RestConstants.VALIDATION_ERROR, errorsExpected))).when(routingListService).createRoutingList(eq(sampleRoutingList), any(RequestContext.class));
+
+ List errors = verifyPostResponse(ROUTING_LISTS_ENDPOINT, JsonObject.mapFrom(sampleRoutingList).encode(),
+ prepareHeaders(EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10, X_OKAPI_USER_ID), APPLICATION_JSON, 422)
+ .as(Errors.class)
+ .getErrors();
+
+ assertThat(errors.get(0).getMessage(), equalTo(INVALID_ROUTING_LIST_FOR_PO_LINE_FORMAT.getDescription()));
+ }
+
+ @Test
+ void testPostRoutingListShouldFailForLimitReached() {
+ logger.info("=== Test POST Routing List should fail because it's POL has reached limit of Routing Lists ===");
+
+ PoLine poLine = getMinimalContentPoLine()
+ .withId(PO_LINE_UUID)
+ .withOrderFormat(PoLine.OrderFormat.PHYSICAL_RESOURCE)
+ .withLocations(getLocationPhysicalCopies(0));
+ addMockEntry(PO_LINES_STORAGE, JsonObject.mapFrom(poLine));
+
+ var errorsExpected = getCodesAsErrors(ROUTING_LIST_LIMIT_REACHED_FOR_PO_LINE);
+ doReturn(failedFuture(new HttpException(RestConstants.VALIDATION_ERROR, errorsExpected))).when(routingListService).createRoutingList(eq(sampleRoutingList), any(RequestContext.class));
+
+ List errors = verifyPostResponse(ROUTING_LISTS_ENDPOINT, JsonObject.mapFrom(sampleRoutingList).encode(),
+ prepareHeaders(EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10, X_OKAPI_USER_ID), APPLICATION_JSON, 422
+ )
+ .as(Errors.class)
+ .getErrors();
+
+ assertThat(errors.get(0).getMessage(), equalTo(ROUTING_LIST_LIMIT_REACHED_FOR_PO_LINE.getDescription()));
+ }
+
+ @Test
+ void testPostRoutingListWithInvalidPoLineId() {
+ logger.info("=== Test POST Routing List should fail because it's POL does not exist ===");
+
+ var errorsExpected = getCodesAsErrors(PO_LINE_NOT_FOUND_FOR_ROUTING_LIST);
+ doReturn(failedFuture(new HttpException(RestConstants.VALIDATION_ERROR, errorsExpected))).when(routingListService).createRoutingList(eq(sampleRoutingList), any(RequestContext.class));
+
+ List errors = verifyPostResponse(ROUTING_LISTS_ENDPOINT, JsonObject.mapFrom(sampleRoutingList).encode(),
+ prepareHeaders(EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10, X_OKAPI_USER_ID), APPLICATION_JSON, 422
+ )
+ .as(Errors.class)
+ .getErrors();
+
+ assertThat(errors.get(0).getMessage(), equalTo(PO_LINE_NOT_FOUND_FOR_ROUTING_LIST.getDescription()));
+ }
+
+ @Test
+ void testGetRoutingLists() {
+ logger.info("=== Test Get Routing Lists ===");
+ var collection = new RoutingListCollection()
+ .withRoutingLists(List.of(sampleRoutingList))
+ .withTotalRecords(1);
+ doReturn(succeededFuture(collection)).when(routingListService).getRoutingLists(anyInt(), anyInt(), any(), any(RequestContext.class));
+
+ final RoutingListCollection respCollection = verifySuccessGet(ROUTING_LISTS_ENDPOINT, RoutingListCollection.class);
+ logger.info(JsonObject.mapFrom(respCollection).encodePrettily());
+ assertEquals(1, respCollection.getRoutingLists().size());
+ }
+
+ @Test
+ void testGetRoutingListById() {
+ logger.info("=== Test Get Routing List by id ===");
+ doReturn(succeededFuture(sampleRoutingList)).when(routingListService).getRoutingList(eq(ROUTING_LIST_UUID), any(RequestContext.class));
+
+ final RoutingList resp = verifySuccessGet(String.format(ROUTING_LISTS_ID_PATH, ROUTING_LIST_UUID), RoutingList.class);
+ logger.info(JsonObject.mapFrom(resp).encodePrettily());
+ assertEquals(ROUTING_LIST_UUID, resp.getId());
+ }
+
+ @Test
+ void testPutRoutingList() {
+ logger.info("=== Test update Routing List by id ===");
+ sampleRoutingList.setNotes("new notes");
+ doReturn(succeededFuture()).when(routingListService).updateRoutingList(eq(sampleRoutingList), any(RequestContext.class));
+
+ verifyPut(String.format(ROUTING_LISTS_ID_PATH, ROUTING_LIST_UUID), JsonObject.mapFrom(sampleRoutingList).encode(),
+ prepareHeaders(EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10), "", 204);
+ }
+
+ @Test
+ void testDeleteRoutingListByIdTest() {
+ logger.info("=== Test delete Routing List by id ===");
+ doReturn(succeededFuture()).when(routingListService).deleteRoutingList(eq(ROUTING_LIST_UUID), any(RequestContext.class));
+
+ verifyDeleteResponse(String.format(ROUTING_LISTS_ID_PATH, ROUTING_LIST_UUID), "", 204);
+ }
+
+ @Test
+ void testDeleteRoutingListByIdWithInvalidFormatTest() {
+ logger.info("=== Test delete Routing List by id ===");
+ doReturn(failedFuture(new HttpException(BAD_REQUEST, ErrorCodes.GENERIC_ERROR_CODE))).when(routingListService).deleteRoutingList(eq(ID_BAD_FORMAT), any(RequestContext.class));
+
+ verifyDeleteResponse(String.format(ROUTING_LISTS_ID_PATH, ID_BAD_FORMAT), TEXT_PLAIN, 400);
+ }
+
+ @Test
+ void testDeleteNotExistentRoutingListTest() {
+ logger.info("=== Test delete Routing List by id ===");
+ doReturn(failedFuture(new HttpException(NOT_FOUND, ErrorCodes.GENERIC_ERROR_CODE))).when(routingListService).deleteRoutingList(eq(ID_DOES_NOT_EXIST), any(RequestContext.class));
+
+ verifyDeleteResponse(String.format(ROUTING_LISTS_ID_PATH, ID_DOES_NOT_EXIST), APPLICATION_JSON, 404);
+ }
+
@Test
void testProcessTemplateRequest() {
logger.info("=== Test Execute template processing request ===");
@@ -107,10 +284,19 @@ void testProcessTemplateRequest() {
verify(routingListService, times(1)).processTemplateRequest(eq(ROUTING_LIST_ID), any(RequestContext.class));
}
+ private Errors getCodesAsErrors(ErrorCodes... codes) {
+ return new Errors()
+ .withErrors(Arrays.stream(codes).map(ErrorCodes::toError).toList())
+ .withTotalRecords(codes.length);
+ }
+
static class ContextConfiguration {
+
@Bean
public RoutingListService routingListService() {
return mock(RoutingListService.class);
}
+
}
+
}
diff --git a/src/test/java/org/folio/service/RoutingListServiceTest.java b/src/test/java/org/folio/service/RoutingListServiceTest.java
deleted file mode 100644
index fe9651a93..000000000
--- a/src/test/java/org/folio/service/RoutingListServiceTest.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package org.folio.service;
-
-import static io.vertx.core.Future.succeededFuture;
-import static org.folio.TestConstants.ROUTING_LIST_ID;
-import static org.folio.TestUtils.getMockData;
-import static org.folio.rest.impl.MockServer.ROUTING_LIST_MOCK_DATA_PATH;
-import static org.folio.rest.impl.MockServer.USERS_MOCK_DATA_PATH;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-
-import io.vertx.core.Future;
-import io.vertx.core.json.JsonObject;
-import io.vertx.junit5.VertxExtension;
-import io.vertx.junit5.VertxTestContext;
-import org.folio.models.UserCollection;
-import org.folio.rest.acq.model.Setting;
-import org.folio.rest.acq.model.SettingCollection;
-import org.folio.rest.core.RestClient;
-import org.folio.rest.core.models.RequestContext;
-import org.folio.rest.core.models.RequestEntry;
-import org.folio.rest.jaxrs.model.RoutingList;
-import org.junit.jupiter.api.AfterEach;
-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.MockitoAnnotations;
-
-@ExtendWith(VertxExtension.class)
-public class RoutingListServiceTest {
-
- @InjectMocks
- RoutingListService routingListService;
- @Mock
- private RestClient restClient;
- @Mock
- private UserService userService;
- @Mock
- private RequestContext requestContextMock;
- private AutoCloseable mockitoMocks;
-
- @BeforeEach
- public void initMocks() throws Exception {
- mockitoMocks = MockitoAnnotations.openMocks(this);
- }
-
- @AfterEach
- void afterEach() throws Exception {
- mockitoMocks.close();
- }
-
- @Test
- void processTemplate(VertxTestContext vertxTestContext) throws IOException {
- var routingList = new JsonObject(getMockData(ROUTING_LIST_MOCK_DATA_PATH + ROUTING_LIST_ID + ".json")).mapTo(RoutingList.class);
- var users = new JsonObject(getMockData(USERS_MOCK_DATA_PATH + "user_collection.json")).mapTo(UserCollection.class);
- var expectedTemplateRequest = new JsonObject(getMockData(ROUTING_LIST_MOCK_DATA_PATH + ROUTING_LIST_ID + "-expected-template-request.json"));
- var setting = new Setting().withId(UUID.randomUUID().toString())
- .withKey("routing-list")
- .withValue("1c4b225f-f669-4e9b-afcd-ebc0e273a34e");
- var settingCollection = new SettingCollection().withSettings(List.of(setting));
-
- doReturn(succeededFuture(routingList)).when(restClient).get(any(RequestEntry.class), eq(RoutingList.class), any());
- doReturn(succeededFuture(users)).when(userService).getUsersByIds(eq(routingList.getUserIds()), any());
- doReturn(succeededFuture(settingCollection)).when(restClient).get(any(RequestEntry.class), eq(SettingCollection.class), any());
- doReturn(succeededFuture(new JsonObject())).when(restClient).postJsonObject(any(RequestEntry.class), eq(expectedTemplateRequest), any());
-
- Future future = routingListService.processTemplateRequest(ROUTING_LIST_ID, requestContextMock);
-
- vertxTestContext.assertComplete(future).onComplete(result -> {
- assertTrue(result.succeeded());
- vertxTestContext.completeNow();
- });
- }
-
- @Test
- void throwErrorWhenSettingNotFound(VertxTestContext vertxTestContext) throws IOException {
- var routingList = new JsonObject(getMockData(ROUTING_LIST_MOCK_DATA_PATH + ROUTING_LIST_ID + ".json")).mapTo(RoutingList.class);
- var users = new JsonObject(getMockData(USERS_MOCK_DATA_PATH + "user_collection.json")).mapTo(UserCollection.class);
-
- doReturn(succeededFuture(routingList)).when(restClient).get(any(RequestEntry.class), eq(RoutingList.class), any());
- doReturn(succeededFuture(users)).when(userService).getUsersByIds(eq(routingList.getUserIds()), any());
- doReturn(succeededFuture(new SettingCollection().withSettings(new ArrayList<>())))
- .when(restClient).get(any(RequestEntry.class), eq(SettingCollection.class), any());
-
- Future future = routingListService.processTemplateRequest(ROUTING_LIST_ID, requestContextMock);
-
- vertxTestContext.assertFailure(future).onComplete(result -> {
- assertTrue(result.failed());
- var exception = result.cause().getMessage();
- assertTrue(exception.contains("Setting is not found with key=ROUTING_USER_ADDRESS_TYPE_ID"));
- vertxTestContext.completeNow();
- });
- }
-}
diff --git a/src/test/java/org/folio/service/routinglists/RoutingListServiceTest.java b/src/test/java/org/folio/service/routinglists/RoutingListServiceTest.java
new file mode 100644
index 000000000..30f4b6a85
--- /dev/null
+++ b/src/test/java/org/folio/service/routinglists/RoutingListServiceTest.java
@@ -0,0 +1,187 @@
+package org.folio.service.routinglists;
+
+import static io.vertx.core.Future.succeededFuture;
+import static org.folio.TestConstants.ROUTING_LIST_ID;
+import static org.folio.TestUtils.getLocationPhysicalCopies;
+import static org.folio.TestUtils.getMinimalContentPoLine;
+import static org.folio.TestUtils.getMockAsJson;
+import static org.folio.TestUtils.getMockData;
+import static org.folio.rest.core.exceptions.ErrorCodes.INVALID_ROUTING_LIST_FOR_PO_LINE_FORMAT;
+import static org.folio.rest.core.exceptions.ErrorCodes.ROUTING_LIST_LIMIT_REACHED_FOR_PO_LINE;
+import static org.folio.rest.impl.MockServer.ROUTING_LISTS_MOCK_DATA_PATH;
+import static org.folio.rest.impl.MockServer.USERS_MOCK_DATA_PATH;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.folio.models.UserCollection;
+import org.folio.rest.acq.model.Setting;
+import org.folio.rest.acq.model.SettingCollection;
+import org.folio.rest.core.RestClient;
+import org.folio.rest.core.models.RequestContext;
+import org.folio.rest.core.models.RequestEntry;
+import org.folio.rest.jaxrs.model.PoLine;
+import org.folio.rest.jaxrs.model.RoutingList;
+import org.folio.rest.jaxrs.model.RoutingListCollection;
+import org.folio.service.UserService;
+import org.folio.service.orders.PurchaseOrderLineService;
+import org.junit.jupiter.api.AfterEach;
+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.MockitoAnnotations;
+
+import io.vertx.core.Future;
+import io.vertx.core.json.JsonObject;
+import io.vertx.junit5.VertxExtension;
+import io.vertx.junit5.VertxTestContext;
+
+@ExtendWith(VertxExtension.class)
+public class RoutingListServiceTest {
+
+ private static final String ROUTING_LIST_SAMPLE = ROUTING_LISTS_MOCK_DATA_PATH + "routing-list.json";
+
+ private static final String PO_LINE_UUID = "0009662b-8b80-4001-b704-ca10971f222d";
+
+ private PoLine samplePoLine;
+ private RoutingList sampleRoutingList;
+
+ @Mock
+ private RestClient restClient;
+
+ @Mock
+ private PurchaseOrderLineService poLineService;
+
+ @Mock
+ private UserService userService;
+
+ @Mock
+ private RequestContext requestContextMock;
+
+ @InjectMocks
+ private RoutingListService routingListService;
+
+ private AutoCloseable mockitoMocks;
+
+ @BeforeEach
+ void before() {
+ mockitoMocks = MockitoAnnotations.openMocks(this);
+ sampleRoutingList = getMockAsJson(ROUTING_LIST_SAMPLE).mapTo(RoutingList.class);
+ samplePoLine = getMinimalContentPoLine()
+ .withId(PO_LINE_UUID)
+ .withOrderFormat(PoLine.OrderFormat.PHYSICAL_RESOURCE)
+ .withLocations(getLocationPhysicalCopies(1));
+ }
+
+ @AfterEach
+ void afterEach() throws Exception {
+ mockitoMocks.close();
+ }
+
+ @Test
+ void testCreateRoutingList(VertxTestContext vertxTestContext) {
+ doReturn(succeededFuture(getRoutingListCollection(0))).when(restClient).get(any(RequestEntry.class), eq(RoutingListCollection.class), any());
+ doReturn(succeededFuture(sampleRoutingList)).when(restClient).post(any(RequestEntry.class), any(RoutingList.class), eq(RoutingList.class), any());
+ doReturn(succeededFuture(samplePoLine)).when(poLineService).getOrderLineById(any(), any());
+
+ Future future = routingListService.createRoutingList(sampleRoutingList, requestContextMock);
+ vertxTestContext.assertComplete(future).onComplete(result -> {
+ assertTrue(result.succeeded());
+ assertEquals(sampleRoutingList.getId(), result.result().getId());
+ vertxTestContext.completeNow();
+ });
+ }
+
+ @Test
+ void testCreateRoutingListWithPOLineLimitReached(VertxTestContext vertxTestContext) {
+ doReturn(succeededFuture(getRoutingListCollection(1))).when(restClient).get(any(RequestEntry.class), eq(RoutingListCollection.class), any());
+ doReturn(succeededFuture(samplePoLine)).when(poLineService).getOrderLineById(any(), any());
+
+ Future future = routingListService.createRoutingList(sampleRoutingList, requestContextMock);
+ vertxTestContext.assertFailure(future).onComplete(result -> {
+ assertTrue(result.failed());
+ var exception = result.cause().getMessage();
+ assertTrue(exception.contains(ROUTING_LIST_LIMIT_REACHED_FOR_PO_LINE.getDescription()));
+ vertxTestContext.completeNow();
+ });
+
+ }
+
+ @Test
+ void testCreateRoutingListWithPOLineInvalidOrderFormat(VertxTestContext vertxTestContext) {
+ samplePoLine.setOrderFormat(PoLine.OrderFormat.ELECTRONIC_RESOURCE);
+ doReturn(succeededFuture(getRoutingListCollection(0))).when(restClient).get(any(RequestEntry.class), eq(RoutingListCollection.class), any());
+ doReturn(succeededFuture(samplePoLine)).when(poLineService).getOrderLineById(any(), any());
+
+ Future future = routingListService.createRoutingList(sampleRoutingList, requestContextMock);
+ vertxTestContext.assertFailure(future).onComplete(result -> {
+ assertTrue(result.failed());
+ var exception = result.cause().getMessage();
+ assertTrue(exception.contains(INVALID_ROUTING_LIST_FOR_PO_LINE_FORMAT.getDescription()));
+ vertxTestContext.completeNow();
+ });
+ }
+
+ @Test
+ void processTemplate(VertxTestContext vertxTestContext) throws IOException {
+ var routingList = new JsonObject(getMockData(ROUTING_LISTS_MOCK_DATA_PATH + ROUTING_LIST_ID + ".json")).mapTo(RoutingList.class);
+ var users = new JsonObject(getMockData(USERS_MOCK_DATA_PATH + "user_collection.json")).mapTo(UserCollection.class);
+ var expectedTemplateRequest = new JsonObject(getMockData(ROUTING_LISTS_MOCK_DATA_PATH + ROUTING_LIST_ID + "-expected-template-request.json"));
+ var setting = new Setting().withId(UUID.randomUUID().toString())
+ .withKey("routing-list")
+ .withValue("1c4b225f-f669-4e9b-afcd-ebc0e273a34e");
+ var settingCollection = new SettingCollection().withSettings(List.of(setting));
+
+ doReturn(succeededFuture(routingList)).when(restClient).get(any(RequestEntry.class), eq(RoutingList.class), any());
+ doReturn(succeededFuture(users)).when(userService).getUsersByIds(eq(routingList.getUserIds()), any());
+ doReturn(succeededFuture(settingCollection)).when(restClient).get(any(RequestEntry.class), eq(SettingCollection.class), any());
+ doReturn(succeededFuture(new JsonObject())).when(restClient).postJsonObject(any(RequestEntry.class), eq(expectedTemplateRequest), any());
+
+ Future future = routingListService.processTemplateRequest(ROUTING_LIST_ID, requestContextMock);
+
+ vertxTestContext.assertComplete(future).onComplete(result -> {
+ assertTrue(result.succeeded());
+ vertxTestContext.completeNow();
+ });
+ }
+
+ @Test
+ void throwErrorWhenSettingNotFound(VertxTestContext vertxTestContext) throws IOException {
+ var routingList = new JsonObject(getMockData(ROUTING_LISTS_MOCK_DATA_PATH + ROUTING_LIST_ID + ".json")).mapTo(RoutingList.class);
+ var users = new JsonObject(getMockData(USERS_MOCK_DATA_PATH + "user_collection.json")).mapTo(UserCollection.class);
+
+ doReturn(succeededFuture(routingList)).when(restClient).get(any(RequestEntry.class), eq(RoutingList.class), any());
+ doReturn(succeededFuture(users)).when(userService).getUsersByIds(eq(routingList.getUserIds()), any());
+ doReturn(succeededFuture(new SettingCollection().withSettings(new ArrayList<>())))
+ .when(restClient).get(any(RequestEntry.class), eq(SettingCollection.class), any());
+
+ Future future = routingListService.processTemplateRequest(ROUTING_LIST_ID, requestContextMock);
+
+ vertxTestContext.assertFailure(future).onComplete(result -> {
+ assertTrue(result.failed());
+ var exception = result.cause().getMessage();
+ assertTrue(exception.contains("Setting is not found with key=ROUTING_USER_ADDRESS_TYPE_ID"));
+ vertxTestContext.completeNow();
+ });
+ }
+
+ private RoutingListCollection getRoutingListCollection(int n) {
+ List lists = new ArrayList<>();
+ for (int i = 0; i < n; i++) {
+ lists.add(sampleRoutingList);
+ }
+ return new RoutingListCollection()
+ .withRoutingLists(lists)
+ .withTotalRecords(n);
+ }
+
+}
diff --git a/src/test/java/org/folio/service/routinglists/validators/RoutingListValidatorTest.java b/src/test/java/org/folio/service/routinglists/validators/RoutingListValidatorTest.java
new file mode 100644
index 000000000..333d3bde5
--- /dev/null
+++ b/src/test/java/org/folio/service/routinglists/validators/RoutingListValidatorTest.java
@@ -0,0 +1,73 @@
+package org.folio.service.routinglists.validators;
+
+import static org.folio.TestUtils.getLocationPhysicalCopies;
+import static org.folio.TestUtils.getMinimalContentPoLine;
+import static org.folio.TestUtils.getMockAsJson;
+import static org.folio.rest.core.exceptions.ErrorCodes.INVALID_ROUTING_LIST_FOR_PO_LINE_FORMAT;
+import static org.folio.rest.core.exceptions.ErrorCodes.ROUTING_LIST_LIMIT_REACHED_FOR_PO_LINE;
+import static org.folio.rest.impl.MockServer.ROUTING_LISTS_MOCK_DATA_PATH;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.folio.rest.jaxrs.model.Error;
+import org.folio.rest.jaxrs.model.PoLine;
+import org.folio.rest.jaxrs.model.RoutingList;
+import org.folio.rest.jaxrs.model.RoutingListCollection;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class RoutingListValidatorTest {
+
+ private static final String ROUTING_LIST_SAMPLE = ROUTING_LISTS_MOCK_DATA_PATH + "routing-list.json";
+
+ private static final String PO_LINE_UUID = "0009662b-8b80-4001-b704-ca10971f222d";
+
+ private PoLine samplePoLine;
+ private RoutingList sampleRoutingList;
+
+ @BeforeEach
+ void before() {
+ sampleRoutingList = getMockAsJson(ROUTING_LIST_SAMPLE).mapTo(RoutingList.class);
+ samplePoLine = getMinimalContentPoLine()
+ .withId(PO_LINE_UUID)
+ .withOrderFormat(PoLine.OrderFormat.PHYSICAL_RESOURCE)
+ .withLocations(getLocationPhysicalCopies(1));
+ }
+
+ @Test
+ void testValidateRoutingList() {
+ RoutingListCollection collection = getRoutingListCollection(0);
+ List errorList = RoutingListValidatorUtil.validateRoutingList(collection, samplePoLine);
+ assertEquals(errorList.size(), 0);
+ }
+
+ @Test
+ void testValidateRoutingListWithPOLineLimitReached() {
+ RoutingListCollection collection = getRoutingListCollection(1);
+ List errors = RoutingListValidatorUtil.validateRoutingList(collection, samplePoLine);
+ assertEquals(errors.size(), 1);
+ assertEquals(errors.get(0).getMessage(), ROUTING_LIST_LIMIT_REACHED_FOR_PO_LINE.getDescription());
+ }
+
+ @Test
+ void testValidateRoutingListWithPOLineInvalidOrderFormat() {
+ samplePoLine.setOrderFormat(PoLine.OrderFormat.ELECTRONIC_RESOURCE);
+ RoutingListCollection collection = getRoutingListCollection(1);
+ List errors = RoutingListValidatorUtil.validateRoutingList(collection, samplePoLine);
+ assertEquals(errors.size(), 1);
+ assertEquals(errors.get(0).getMessage(), INVALID_ROUTING_LIST_FOR_PO_LINE_FORMAT.getDescription());
+ }
+
+ private RoutingListCollection getRoutingListCollection(int n) {
+ List lists = new ArrayList<>();
+ for (int i = 0; i < n; i++) {
+ lists.add(sampleRoutingList);
+ }
+ return new RoutingListCollection()
+ .withRoutingLists(lists)
+ .withTotalRecords(n);
+ }
+
+}
diff --git a/src/test/resources/mockdata/routingLists/c0d13648-347b-4ac9-8c2f-5bc47248b87e.json b/src/test/resources/mockdata/routingLists/c0d13648-347b-4ac9-8c2f-5bc47248b87e.json
new file mode 100644
index 000000000..cce9d1272
--- /dev/null
+++ b/src/test/resources/mockdata/routingLists/c0d13648-347b-4ac9-8c2f-5bc47248b87e.json
@@ -0,0 +1,11 @@
+{
+ "id": "c0d13648-347b-4ac9-8c2f-5bc47248b87e",
+ "_version": 1,
+ "name": "List name",
+ "notes": "Some note",
+ "userIds": [
+ "d926d900-e27d-46d6-bba8-31e9d5c2cf44",
+ "077274ad-6b4f-4c28-9779-6d381e7a1ca1"
+ ],
+ "poLineId": "0009662b-8b80-4001-b704-ca10971f222d"
+}
diff --git a/src/test/resources/mockdata/routingLists/routing-list.json b/src/test/resources/mockdata/routingLists/routing-list.json
new file mode 100644
index 000000000..cce9d1272
--- /dev/null
+++ b/src/test/resources/mockdata/routingLists/routing-list.json
@@ -0,0 +1,11 @@
+{
+ "id": "c0d13648-347b-4ac9-8c2f-5bc47248b87e",
+ "_version": 1,
+ "name": "List name",
+ "notes": "Some note",
+ "userIds": [
+ "d926d900-e27d-46d6-bba8-31e9d5c2cf44",
+ "077274ad-6b4f-4c28-9779-6d381e7a1ca1"
+ ],
+ "poLineId": "0009662b-8b80-4001-b704-ca10971f222d"
+}
diff --git a/src/test/resources/mockdata/routingLists/routing-lists.json b/src/test/resources/mockdata/routingLists/routing-lists.json
new file mode 100644
index 000000000..2bb58ff0b
--- /dev/null
+++ b/src/test/resources/mockdata/routingLists/routing-lists.json
@@ -0,0 +1,17 @@
+{
+ "routingLists":
+ [
+ {
+ "id": "c0d13648-347b-4ac9-8c2f-5bc47248b87e",
+ "_version": 1,
+ "name": "List name",
+ "notes": "Some note",
+ "userIds": [
+ "d926d900-e27d-46d6-bba8-31e9d5c2cf44",
+ "077274ad-6b4f-4c28-9779-6d381e7a1ca1"
+ ],
+ "poLineId": "0009662b-8b80-4001-b704-ca10971f222d"
+ }
+ ],
+ "totalRecords": 1
+}