diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index a98053c7..a5ec2216 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -46,6 +46,32 @@ } ] }, + { + "id": "ecs-request-external", + "version": "1.0", + "handlers": [ + { + "methods": ["POST"], + "pathPattern": "/tlr/create-ecs-request-external", + "permissionsRequired": ["tlr.ecs-request-external.post"], + "modulePermissions": [ + "circulation.requests.instances.item.post", + "circulation.requests.item.post", + "circulation-item.item.get", + "circulation-item.collection.get", + "circulation-item.item.post", + "circulation-item.item.put", + "search.instances.collection.get", + "users.item.get", + "users.collection.get", + "users.item.post", + "inventory-storage.service-points.item.get", + "inventory-storage.service-points.collection.get", + "inventory-storage.service-points.item.post" + ] + } + ] + }, { "id": "ecs-tlr-allowed-service-points", "version": "1.0", @@ -205,6 +231,11 @@ "permissionName": "tlr.staff-slips.pick-slips.get", "displayName": "ecs-tlr - pick slips", "description": "Get pick slips" + }, + { + "permissionName": "tlr.ecs-request-external.post", + "displayName": "ecs-request-external - create ECS request external", + "description": "Create ECS request external" } ], "requires": [ diff --git a/pom.xml b/pom.xml index 69e72198..191faebb 100644 --- a/pom.xml +++ b/pom.xml @@ -405,6 +405,33 @@ + + ecs-request-external + + generate + + + ${project.basedir}/src/main/resources/swagger.api/ecs-request-external.yaml + ${project.build.directory}/generated-sources + spring + ${project.groupId}.domain.dto + ${project.groupId}.rest.resource + true + true + true + true + false + true + ApiUtil.java + true + + java + true + true + true + + + diff --git a/src/main/java/org/folio/client/feign/CirculationClient.java b/src/main/java/org/folio/client/feign/CirculationClient.java index 061596b4..3afd10d2 100644 --- a/src/main/java/org/folio/client/feign/CirculationClient.java +++ b/src/main/java/org/folio/client/feign/CirculationClient.java @@ -18,30 +18,19 @@ public interface CirculationClient { Request createRequest(Request request); @GetMapping("/requests/allowed-service-points") - AllowedServicePointsResponse allowedServicePointsWithStubItem( - @RequestParam("patronGroupId") String patronGroupId, @RequestParam("instanceId") String instanceId, - @RequestParam("operation") String operation, @RequestParam("useStubItem") boolean useStubItem); - - @GetMapping("/requests/allowed-service-points") - AllowedServicePointsResponse allowedServicePointsWithStubItem( - @RequestParam("operation") String operation, @RequestParam("requestId") String requestId, - @RequestParam("useStubItem") boolean useStubItem); - - @GetMapping("/requests/allowed-service-points") - AllowedServicePointsResponse allowedRoutingServicePoints( - @RequestParam("patronGroupId") String patronGroupId, @RequestParam("instanceId") String instanceId, + AllowedServicePointsResponse allowedServicePointsByInstance( + @RequestParam("patronGroupId") String patronGroupId, @RequestParam("operation") String operation, - @RequestParam("ecsRequestRouting") boolean ecsRequestRouting); + @RequestParam("instanceId") String instanceId); @GetMapping("/requests/allowed-service-points") - AllowedServicePointsResponse allowedRoutingServicePoints( - @RequestParam("operation") String operation, @RequestParam("requestId") String requestId, - @RequestParam("ecsRequestRouting") boolean ecsRequestRouting); - - @GetMapping("/requests/allowed-service-points") - AllowedServicePointsResponse allowedRoutingServicePoints( + AllowedServicePointsResponse allowedServicePointsByItem( @RequestParam("patronGroupId") String patronGroupId, @RequestParam("operation") String operation, - @RequestParam("ecsRequestRouting") boolean ecsRequestRouting, @RequestParam("itemId") String itemId); + + @GetMapping("/requests/allowed-service-points") + AllowedServicePointsResponse allowedServicePoints( + @RequestParam("operation") String operation, + @RequestParam("requestId") String requestId); } diff --git a/src/main/java/org/folio/controller/AllowedServicePointsController.java b/src/main/java/org/folio/controller/AllowedServicePointsController.java index d7e9750d..bcdb027c 100644 --- a/src/main/java/org/folio/controller/AllowedServicePointsController.java +++ b/src/main/java/org/folio/controller/AllowedServicePointsController.java @@ -5,8 +5,6 @@ import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; -import java.util.ArrayList; -import java.util.List; import java.util.UUID; import org.folio.domain.dto.AllowedServicePointsRequest; @@ -38,20 +36,15 @@ public ResponseEntity getAllowedServicePoints(Stri AllowedServicePointsRequest request = new AllowedServicePointsRequest( operation, requesterId, instanceId, requestId, itemId); - if (validateAllowedServicePointsRequest(request)) { - var allowedServicePointsService = getAllowedServicePointsService(request); - var response = allowedServicePointsService.getAllowedServicePoints(request); - return ResponseEntity.status(OK).body(response); - } else { + if (!validateAllowedServicePointsRequest(request)) { return ResponseEntity.status(UNPROCESSABLE_ENTITY).build(); } - } - - private AllowedServicePointsService getAllowedServicePointsService( - AllowedServicePointsRequest request) { - return request.isForTitleLevelRequest() + var allowedServicePointsService = request.isForTitleLevelRequest() ? allowedServicePointsForTitleLevelRequestService : allowedServicePointsForItemLevelRequestService; + + return ResponseEntity.status(OK).body(allowedServicePointsService + .getAllowedServicePoints(request)); } private static boolean validateAllowedServicePointsRequest(AllowedServicePointsRequest request) { @@ -63,8 +56,6 @@ private static boolean validateAllowedServicePointsRequest(AllowedServicePointsR boolean allowedCombinationOfParametersDetected = false; - List errors = new ArrayList<>(); - if (operation == CREATE && requesterId != null && instanceId != null && itemId == null && requestId == null) { @@ -87,13 +78,8 @@ private static boolean validateAllowedServicePointsRequest(AllowedServicePointsR } if (!allowedCombinationOfParametersDetected) { - String errorMessage = "Invalid combination of query parameters"; - errors.add(errorMessage); - } - - if (!errors.isEmpty()) { - String errorMessage = String.join(" ", errors); - log.error("validateRequest:: allowed service points request failed: {}", errorMessage); + log.error("validateRequest:: allowed service points request failed: " + + "Invalid combination of query parameters"); return false; } diff --git a/src/main/java/org/folio/controller/EcsRequestExternalController.java b/src/main/java/org/folio/controller/EcsRequestExternalController.java new file mode 100644 index 00000000..e71cbd31 --- /dev/null +++ b/src/main/java/org/folio/controller/EcsRequestExternalController.java @@ -0,0 +1,60 @@ +package org.folio.controller; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.CREATED; + +import org.folio.domain.dto.EcsRequestExternal; +import org.folio.domain.dto.EcsTlr; +import org.folio.domain.mapper.ExternalEcsRequestMapper; +import org.folio.exception.RequestCreatingException; +import org.folio.rest.resource.EcsRequestExternalApi; +import org.folio.service.EcsTlrService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +import lombok.AllArgsConstructor; +import lombok.extern.log4j.Log4j2; + +@RestController +@Log4j2 +@AllArgsConstructor +public class EcsRequestExternalController implements EcsRequestExternalApi { + + private static final EcsTlr.RequestTypeEnum[] ORDERED_REQUEST_TYPES = { + EcsTlr.RequestTypeEnum.PAGE, + EcsTlr.RequestTypeEnum.RECALL, + EcsTlr.RequestTypeEnum.HOLD + }; + + private final EcsTlrService ecsTlrService; + private final ExternalEcsRequestMapper externalEcsRequestMapper; + + @Override + public ResponseEntity postEcsRequestExternal(EcsRequestExternal ecsRequestExternal) { + log.info("postEcsRequestExternal:: creating external ECS request, instance {}, " + + "item {}, requester {}", ecsRequestExternal.getInstanceId(), + ecsRequestExternal.getItemId(), ecsRequestExternal.getRequesterId()); + + EcsTlr ecsTlrDto = externalEcsRequestMapper.mapEcsRequestExternalToEcsTlr(ecsRequestExternal); + + for (EcsTlr.RequestTypeEnum requestType: ORDERED_REQUEST_TYPES) { + EcsTlr ecsTlr; + try { + ecsTlr = ecsTlrService.create(ecsTlrDto.requestType(requestType)); + } catch (RequestCreatingException e) { + log.warn("postEcsRequestExternal:: failed to create ECS request, message: {}, cause: {}", + e.getMessage(), e.getCause()); + ecsTlr = null; + } + + if (ecsTlr != null) { + log.info("postEcsRequestExternal:: created ECS request {}, request type is {}", + ecsTlr.getId(), requestType); + return ResponseEntity.status(CREATED).body(ecsTlr); + } + } + + log.warn("postEcsRequestExternal:: failed to create external ECS request"); + return ResponseEntity.status(BAD_REQUEST).build(); + } +} diff --git a/src/main/java/org/folio/domain/mapper/ExternalEcsRequestMapper.java b/src/main/java/org/folio/domain/mapper/ExternalEcsRequestMapper.java new file mode 100644 index 00000000..70486877 --- /dev/null +++ b/src/main/java/org/folio/domain/mapper/ExternalEcsRequestMapper.java @@ -0,0 +1,34 @@ +package org.folio.domain.mapper; + +import org.folio.domain.dto.EcsRequestExternal; +import org.folio.domain.dto.EcsTlr; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.NullValueCheckStrategy; + +@Mapper(componentModel = "spring", nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) +public interface ExternalEcsRequestMapper { + + @Mapping(target = "requestLevel", qualifiedByName = "ExternalEcsRequestToEcsTlrRequestLevel") + @Mapping(target = "fulfillmentPreference", qualifiedByName = "ExternalEcsRequestToEcsTlrFulfillmentPreference") + EcsTlr mapEcsRequestExternalToEcsTlr(EcsRequestExternal ecsRequestExternal); + + @Named("ExternalEcsRequestToEcsTlrRequestLevel") + default EcsTlr.RequestLevelEnum mapExternalEcsRequestToEcsTlrRequestLevel( + EcsRequestExternal.RequestLevelEnum ecsRequestExternalRequestLevel) { + + return ecsRequestExternalRequestLevel != null + ? EcsTlr.RequestLevelEnum.fromValue(ecsRequestExternalRequestLevel.getValue()) + : null; + } + + @Named("ExternalEcsRequestToEcsTlrFulfillmentPreference") + default EcsTlr.FulfillmentPreferenceEnum mapExternalEcsRequestToEcsTlrFulfillmentPreference( + EcsRequestExternal.FulfillmentPreferenceEnum fulfillmentPreference) { + return fulfillmentPreference != null + ? EcsTlr.FulfillmentPreferenceEnum.fromValue(fulfillmentPreference.getValue()) + : null; + } + +} diff --git a/src/main/java/org/folio/service/impl/AllowedServicePointsForItemLevelRequestService.java b/src/main/java/org/folio/service/impl/AllowedServicePointsForItemLevelRequestService.java index 1e9ac8e1..4e11b8e7 100644 --- a/src/main/java/org/folio/service/impl/AllowedServicePointsForItemLevelRequestService.java +++ b/src/main/java/org/folio/service/impl/AllowedServicePointsForItemLevelRequestService.java @@ -41,15 +41,15 @@ protected Collection getLendingTenants(AllowedServicePointsRequest reque } @Override - protected AllowedServicePointsResponse getAllowedServicePointsFromLendingTenant( + protected AllowedServicePointsResponse getAllowedServicePointsFromTenant( AllowedServicePointsRequest request, String patronGroupId, String tenantId) { - log.info("getAllowedServicePointsFromLendingTenant:: parameters: request: {}, " + + log.info("getAllowedServicePointsFromTenant:: parameters: request: {}, " + "patronGroupId: {}, tenantId: {}", request, patronGroupId, tenantId); return executionService.executeSystemUserScoped(tenantId, - () -> circulationClient.allowedRoutingServicePoints(patronGroupId, - request.getOperation().getValue(), true, request.getItemId())); + () -> circulationClient.allowedServicePointsByItem(patronGroupId, + request.getOperation().getValue(), request.getItemId())); } } diff --git a/src/main/java/org/folio/service/impl/AllowedServicePointsForTitleLevelRequestService.java b/src/main/java/org/folio/service/impl/AllowedServicePointsForTitleLevelRequestService.java index a219e067..4313d18c 100644 --- a/src/main/java/org/folio/service/impl/AllowedServicePointsForTitleLevelRequestService.java +++ b/src/main/java/org/folio/service/impl/AllowedServicePointsForTitleLevelRequestService.java @@ -48,12 +48,12 @@ protected Collection getLendingTenants(AllowedServicePointsRequest reque } @Override - protected AllowedServicePointsResponse getAllowedServicePointsFromLendingTenant( + protected AllowedServicePointsResponse getAllowedServicePointsFromTenant( AllowedServicePointsRequest request, String patronGroupId, String tenantId) { return executionService.executeSystemUserScoped(tenantId, - () -> circulationClient.allowedRoutingServicePoints(patronGroupId, request.getInstanceId(), - request.getOperation().getValue(), true)); + () -> circulationClient.allowedServicePointsByInstance(patronGroupId, + request.getOperation().getValue(), request.getInstanceId())); } } diff --git a/src/main/java/org/folio/service/impl/AllowedServicePointsServiceImpl.java b/src/main/java/org/folio/service/impl/AllowedServicePointsServiceImpl.java index c50f1b89..1aa0d983 100644 --- a/src/main/java/org/folio/service/impl/AllowedServicePointsServiceImpl.java +++ b/src/main/java/org/folio/service/impl/AllowedServicePointsServiceImpl.java @@ -3,13 +3,15 @@ import static org.folio.domain.dto.RequestOperation.REPLACE; import java.util.Collection; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.UUID; -import java.util.stream.Stream; import org.folio.client.feign.CirculationClient; import org.folio.client.feign.SearchClient; -import org.folio.domain.Constants; +import org.folio.domain.dto.AllowedServicePointsInner; import org.folio.domain.dto.AllowedServicePointsRequest; import org.folio.domain.dto.AllowedServicePointsResponse; import org.folio.domain.dto.Request; @@ -20,6 +22,7 @@ import org.folio.service.UserService; import org.folio.spring.service.SystemUserScopedExecutionService; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; @@ -49,58 +52,62 @@ private AllowedServicePointsResponse getForCreate(AllowedServicePointsRequest re String patronGroupId = userService.find(request.getRequesterId()).getPatronGroup(); log.info("getForCreate:: patronGroupId={}", patronGroupId); - boolean isAvailableInLendingTenants = getLendingTenants(request) - .stream() - .anyMatch(tenant -> isAvailableInLendingTenant(request, patronGroupId, tenant)); + Map page = new HashMap<>(); + Map hold = new HashMap<>(); + Map recall = new HashMap<>(); + for (String tenantId : getLendingTenants(request)) { + var servicePoints = getAllowedServicePointsFromTenant(request, patronGroupId, tenantId); + log.info("getForCreate:: service points from {}: {}", tenantId, servicePoints); - if (!isAvailableInLendingTenants) { - log.info("getForCreate:: Not available for requesting, returning empty result"); - return new AllowedServicePointsResponse(); + combineAndFilterDuplicates(page, servicePoints.getPage()); + combineAndFilterDuplicates(hold, servicePoints.getHold()); + combineAndFilterDuplicates(recall, servicePoints.getRecall()); } - log.info("getForCreate:: Available for requesting, proxying call"); - return circulationClient.allowedServicePointsWithStubItem(patronGroupId, request.getInstanceId(), - request.getOperation().getValue(), true); + return new AllowedServicePointsResponse() + .page(Set.copyOf(page.values())) + .hold(Set.copyOf(hold.values())) + .recall(Set.copyOf(recall.values())); } - protected abstract Collection getLendingTenants(AllowedServicePointsRequest request); - - private boolean isAvailableInLendingTenant(AllowedServicePointsRequest request, String patronGroupId, - String tenantId) { - - var allowedServicePointsResponse = getAllowedServicePointsFromLendingTenant(request, - patronGroupId, tenantId); - log.info("isAvailableInLendingTenant:: allowedServicePointsResponse: {}", - allowedServicePointsResponse); + private void combineAndFilterDuplicates( + Map servicePoints, Set toAdd) { - var availabilityCheckResult = Stream.of(allowedServicePointsResponse.getHold(), - allowedServicePointsResponse.getPage(), allowedServicePointsResponse.getRecall()) + if (CollectionUtils.isEmpty(toAdd)) { + return; + } + toAdd.stream() .filter(Objects::nonNull) - .flatMap(Collection::stream) - .anyMatch(Objects::nonNull); - - log.info("isAvailableInLendingTenant:: result: {}", availabilityCheckResult); - return availabilityCheckResult; + .forEach(allowedSp -> servicePoints.put(allowedSp.getId(), allowedSp)); } - protected abstract AllowedServicePointsResponse getAllowedServicePointsFromLendingTenant( + protected abstract Collection getLendingTenants(AllowedServicePointsRequest request); + + protected abstract AllowedServicePointsResponse getAllowedServicePointsFromTenant( AllowedServicePointsRequest request, String patronGroupId, String tenantId); private AllowedServicePointsResponse getForReplace(AllowedServicePointsRequest request) { EcsTlrEntity ecsTlr = findEcsTlr(request); - final boolean requestIsLinkedToItem = ecsTlr.getItemId() != null; - log.info("getForReplace:: request is linked to an item: {}", requestIsLinkedToItem); - if (!requestIsLinkedToItem && isRequestingNotAllowedInLendingTenant(ecsTlr)) { - log.info("getForReplace:: no service points are allowed in lending tenant"); - return new AllowedServicePointsResponse(); - } + log.info("getForReplace:: fetching allowed service points from secondary request tenant"); + var allowedServicePoints = executionService.executeSystemUserScoped( + ecsTlr.getSecondaryRequestTenantId(), () -> circulationClient.allowedServicePoints( + REPLACE.getValue(), ecsTlr.getSecondaryRequestId().toString())); + + Request secondaryRequest = requestService.getRequestFromStorage( + ecsTlr.getSecondaryRequestId().toString(), ecsTlr.getSecondaryRequestTenantId()); + Request.RequestTypeEnum secondaryRequestType = secondaryRequest.getRequestType(); + log.info("getForReplace:: secondary request type: {}", secondaryRequestType.getValue()); - return getAllowedServicePointsFromBorrowingTenant(request); + return switch (secondaryRequestType) { + case PAGE -> new AllowedServicePointsResponse().page(allowedServicePoints.getPage()); + case HOLD -> new AllowedServicePointsResponse().hold(allowedServicePoints.getHold()); + case RECALL -> new AllowedServicePointsResponse().recall(allowedServicePoints.getRecall()); + }; } private EcsTlrEntity findEcsTlr(AllowedServicePointsRequest request) { - final String primaryRequestId = request.getRequestId(); + String primaryRequestId = request.getRequestId(); log.info("findEcsTlr:: looking for ECS TLR with primary request {}", primaryRequestId); EcsTlrEntity ecsTlr = ecsTlrRepository.findByPrimaryRequestId(UUID.fromString(primaryRequestId)) .orElseThrow(() -> new EntityNotFoundException(String.format( @@ -110,46 +117,4 @@ private EcsTlrEntity findEcsTlr(AllowedServicePointsRequest request) { return ecsTlr; } - private AllowedServicePointsResponse getAllowedServicePointsFromBorrowingTenant( - AllowedServicePointsRequest request) { - - log.info("getForReplace:: fetching allowed service points from borrowing tenant"); - var allowedServicePoints = circulationClient.allowedServicePointsWithStubItem( - REPLACE.getValue(), request.getRequestId(), true); - - Request.RequestTypeEnum primaryRequestType = Constants.PRIMARY_REQUEST_TYPE; - log.info("getAllowedServicePointsFromBorrowingTenant:: primary request type: {}", - primaryRequestType.getValue()); - - return switch (primaryRequestType) { - case PAGE -> new AllowedServicePointsResponse().page(allowedServicePoints.getPage()); - case HOLD -> new AllowedServicePointsResponse().hold(allowedServicePoints.getHold()); - case RECALL -> new AllowedServicePointsResponse().recall(allowedServicePoints.getRecall()); - }; - } - - private boolean isRequestingNotAllowedInLendingTenant(EcsTlrEntity ecsTlr) { - log.info("isRequestingNotAllowedInLendingTenant:: checking if requesting is allowed in lending tenant"); - var allowedServicePointsInLendingTenant = executionService.executeSystemUserScoped( - ecsTlr.getSecondaryRequestTenantId(), () -> circulationClient.allowedRoutingServicePoints( - REPLACE.getValue(), ecsTlr.getSecondaryRequestId().toString(), true)); - - Request secondaryRequest = requestService.getRequestFromStorage( - ecsTlr.getSecondaryRequestId().toString(), ecsTlr.getSecondaryRequestTenantId()); - Request.RequestTypeEnum secondaryRequestType = secondaryRequest.getRequestType(); - log.info("isRequestingNotAllowedInLendingTenant:: secondary request type: {}", - secondaryRequestType.getValue()); - - var allowedServicePointsForRequestType = switch (secondaryRequestType) { - case PAGE -> allowedServicePointsInLendingTenant.getPage(); - case HOLD -> allowedServicePointsInLendingTenant.getHold(); - case RECALL -> allowedServicePointsInLendingTenant.getRecall(); - }; - - log.debug("isRequestingNotAllowedInLendingTenant:: allowed service points for {}: {}", - secondaryRequestType.getValue(), allowedServicePointsForRequestType); - - return allowedServicePointsForRequestType == null || allowedServicePointsForRequestType.isEmpty(); - } - } diff --git a/src/main/resources/swagger.api/ecs-request-external.yaml b/src/main/resources/swagger.api/ecs-request-external.yaml new file mode 100644 index 00000000..bda0f34b --- /dev/null +++ b/src/main/resources/swagger.api/ecs-request-external.yaml @@ -0,0 +1,59 @@ +openapi: 3.0.0 +info: + title: ECS Request External API + version: v1 +tags: + - name: ecsRequestExternal +paths: + /tlr/create-ecs-request-external: + post: + description: Create ECS request external + operationId: postEcsRequestExternal + tags: + - ecsRequestExternal + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/ecs-request-external" + required: true + responses: + '201': + $ref: "#/components/responses/ecs-tlr" + '400': + $ref: '#/components/responses/badRequestResponse' + '500': + $ref: '#/components/responses/internalServerErrorResponse' +components: + schemas: + ecs-request-external: + $ref: 'schemas/EcsRequestExternal.yaml#/EcsRequestExternal' + errorResponse: + $ref: 'schemas/errors.json' + responses: + ecs-tlr: + description: ECS TLR object + content: + application/json: + schema: + $ref: 'schemas/EcsTlr.yaml#/EcsTlr' + badRequestResponse: + description: Validation errors + content: + application/json: + example: + errors: + - message: Request is invalid + total_records: 1 + schema: + $ref: "#/components/schemas/errorResponse" + internalServerErrorResponse: + description: When unhandled exception occurred during code execution, e.g. NullPointerException + content: + application/json: + example: + errors: + - message: Unexpected error + total_records: 1 + schema: + $ref: "#/components/schemas/errorResponse" diff --git a/src/main/resources/swagger.api/schemas/EcsRequestExternal.yaml b/src/main/resources/swagger.api/schemas/EcsRequestExternal.yaml new file mode 100644 index 00000000..bd785659 --- /dev/null +++ b/src/main/resources/swagger.api/schemas/EcsRequestExternal.yaml @@ -0,0 +1,66 @@ +EcsRequestExternal: + description: ECS Request External - title level requests in a multi-tenant environment with Сonsortia support enabled + type: "object" + properties: + id: + description: "ID of the ECS TLR" + $ref: "uuid.yaml" + instanceId: + description: "ID of the instance being requested" + $ref: "uuid.yaml" + requesterId: + description: "ID of the requesting patron (user)" + $ref: "uuid.yaml" + requestLevel: + description: "Level of the request - Item or Title" + type: string + enum: [ "Item", "Title" ] + requestExpirationDate: + description: "Date when the request expires" + type: string + format: date-time + requestDate: + description: "Date when the request was placed" + type: string + format: date-time + patronComments: + description: "Comments made by the patron" + type: string + fulfillmentPreference: + description: "How should the request be fulfilled (whether the item should be kept on the hold shelf for collection or delivered to the requester)" + type: string + enum: ["Hold Shelf", "Delivery"] + pickupServicePointId: + description: "The ID of the Service Point where this request can be picked up" + $ref: "uuid.yaml" + itemId: + description: "ID of the item being requested" + $ref: "uuid.yaml" + holdingsRecordId: + description: "ID of the holdings record being requested" + $ref: "uuid.yaml" + primaryRequestId: + description: "Primary request ID" + $ref: "uuid.yaml" + primaryRequestDcbTransactionId: + description: "ID of DCB transaction created for primary request" + $ref: "uuid.yaml" + primaryRequestTenantId: + description: "ID of the tenant primary request was created in" + type: string + secondaryRequestId: + description: "Secondary request ID" + $ref: "uuid.yaml" + secondaryRequestDcbTransactionId: + description: "ID of DCB transaction created for secondary request" + $ref: "uuid.yaml" + secondaryRequestTenantId: + description: "ID of the tenant secondary request was created in" + type: string + + required: + - instanceId + - requesterId + - requestLevel + - fulfillmentPreference + - requestDate diff --git a/src/test/java/org/folio/api/AllowedServicePointsApiTest.java b/src/test/java/org/folio/api/AllowedServicePointsApiTest.java index d498d7fd..d468fe2c 100644 --- a/src/test/java/org/folio/api/AllowedServicePointsApiTest.java +++ b/src/test/java/org/folio/api/AllowedServicePointsApiTest.java @@ -7,8 +7,6 @@ import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; import static java.lang.String.format; import static org.apache.http.HttpStatus.SC_OK; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.is; import java.util.List; import java.util.Set; @@ -26,6 +24,8 @@ import org.folio.repository.EcsTlrRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import org.springframework.beans.factory.annotation.Autowired; class AllowedServicePointsApiTest extends BaseIT { @@ -43,7 +43,7 @@ class AllowedServicePointsApiTest extends BaseIT { ALLOWED_SERVICE_POINTS_URL + "?operation=replace&requestId=" + PRIMARY_REQUEST_ID; private static final String ALLOWED_SERVICE_POINTS_MOD_CIRCULATION_URL = "/circulation/requests/allowed-service-points"; - private static final String ALLOWED_SERVICE_POINTS_MOD_CIRCULATION_URL_PATTERN = + private static final String ALLOWED_SPS_MOD_CIRCULATION_URL_PATTERN = ALLOWED_SERVICE_POINTS_MOD_CIRCULATION_URL + ".*"; private static final String SEARCH_INSTANCES_URL = "/search/instances.*"; private static final String USER_URL = "/users/" + REQUESTER_ID; @@ -60,13 +60,21 @@ public void beforeEach() { } @Test - void allowedServicePointReturnsEmptyResultWhenNoRoutingSpInResponsesFromDataTenants() { - var item1 = new SearchItem(); - item1.setTenantId(TENANT_ID_UNIVERSITY); + void allowedServicePointsShouldReturn422WhenParametersAreInvalid() { + doGet(ALLOWED_SERVICE_POINTS_URL + format("?operation=create&requesterId=%s", randomId())) + .expectStatus().isEqualTo(422); + } - var item2 = new SearchItem(); - item2.setTenantId(TENANT_ID_COLLEGE); + @Test + void titleLevelCreateWhenNoSpInDataTenants() { + // given + User requester = new User().patronGroup(PATRON_GROUP_ID); + wireMockServer.stubFor(get(urlMatching(USER_URL)) + .withHeader(HEADER_TENANT, equalTo(TENANT_ID_CONSORTIUM)) + .willReturn(jsonResponse(asJsonString(requester), SC_OK))); + var item1 = new SearchItem().tenantId(TENANT_ID_UNIVERSITY); + var item2 = new SearchItem().tenantId(TENANT_ID_COLLEGE); var searchInstancesResponse = new SearchInstancesResponse(); searchInstancesResponse.setTotalRecords(1); searchInstancesResponse.setInstances(List.of(new SearchInstance().items(List.of(item1, item2)))); @@ -75,330 +83,188 @@ void allowedServicePointReturnsEmptyResultWhenNoRoutingSpInResponsesFromDataTena .withHeader(HEADER_TENANT, equalTo(TENANT_ID_CONSORTIUM)) .willReturn(jsonResponse(asJsonString(searchInstancesResponse), SC_OK))); - var allowedSpResponseConsortium = new AllowedServicePointsResponse(); - allowedSpResponseConsortium.setHold(Set.of( - buildAllowedServicePoint("SP_consortium_1"), - buildAllowedServicePoint("SP_consortium_2"))); - allowedSpResponseConsortium.setPage(null); - allowedSpResponseConsortium.setRecall(Set.of( - buildAllowedServicePoint("SP_consortium_3"))); - var allowedSpResponseUniversity = new AllowedServicePointsResponse(); allowedSpResponseUniversity.setHold(null); allowedSpResponseUniversity.setPage(null); allowedSpResponseUniversity.setRecall(null); + wireMockServer.stubFor(get(urlMatching(ALLOWED_SPS_MOD_CIRCULATION_URL_PATTERN)) + .withHeader(HEADER_TENANT, equalTo(TENANT_ID_UNIVERSITY)) + .willReturn(jsonResponse(asJsonString(allowedSpResponseUniversity), SC_OK))); var allowedSpResponseCollege = new AllowedServicePointsResponse(); - allowedSpResponseCollege.setHold(null); - allowedSpResponseCollege.setPage(null); - allowedSpResponseCollege.setRecall(null); + allowedSpResponseCollege.setHold(Set.of()); + allowedSpResponseCollege.setPage(Set.of()); + allowedSpResponseCollege.setRecall(Set.of()); + wireMockServer.stubFor(get(urlMatching(ALLOWED_SPS_MOD_CIRCULATION_URL_PATTERN)) + .withHeader(HEADER_TENANT, equalTo(TENANT_ID_COLLEGE)) + .willReturn(jsonResponse(asJsonString(allowedSpResponseCollege), SC_OK))); - var allowedSpResponseCollegeWithRouting = new AllowedServicePointsResponse(); - allowedSpResponseCollegeWithRouting.setHold(null); - allowedSpResponseCollegeWithRouting.setPage(Set.of( - buildAllowedServicePoint("SP_college_1"))); - allowedSpResponseCollegeWithRouting.setRecall(null); + // when - then + doGet( + ALLOWED_SERVICE_POINTS_URL + format("?operation=create&requesterId=%s&instanceId=%s", + REQUESTER_ID, INSTANCE_ID)) + .expectStatus().isEqualTo(200) + .expectBody().json("{}"); + + wireMockServer.verify(getRequestedFor(urlMatching( + ALLOWED_SPS_MOD_CIRCULATION_URL_PATTERN)) + .withQueryParam("patronGroupId", equalTo(PATRON_GROUP_ID)) + .withQueryParam("operation", equalTo("create")) + .withQueryParam("instanceId", equalTo(INSTANCE_ID))); + } + @Test + void titleLevelCreateReturnsResponsesFromDataTenants() { + // given User requester = new User().patronGroup(PATRON_GROUP_ID); wireMockServer.stubFor(get(urlMatching(USER_URL)) .withHeader(HEADER_TENANT, equalTo(TENANT_ID_CONSORTIUM)) .willReturn(jsonResponse(asJsonString(requester), SC_OK))); - wireMockServer.stubFor(get(urlMatching(ALLOWED_SERVICE_POINTS_MOD_CIRCULATION_URL_PATTERN)) + var item1 = new SearchItem().tenantId(TENANT_ID_UNIVERSITY); + var item2 = new SearchItem().tenantId(TENANT_ID_COLLEGE); + var searchInstancesResponse = new SearchInstancesResponse(); + searchInstancesResponse.setTotalRecords(1); + searchInstancesResponse.setInstances(List.of(new SearchInstance().items(List.of(item1, item2)))); + + wireMockServer.stubFor(get(urlMatching(SEARCH_INSTANCES_URL)) .withHeader(HEADER_TENANT, equalTo(TENANT_ID_CONSORTIUM)) - .willReturn(jsonResponse(asJsonString(allowedSpResponseConsortium), SC_OK))); + .willReturn(jsonResponse(asJsonString(searchInstancesResponse), SC_OK))); - wireMockServer.stubFor(get(urlMatching(ALLOWED_SERVICE_POINTS_MOD_CIRCULATION_URL_PATTERN)) + AllowedServicePointsInner sp1 = buildAllowedServicePoint("SP_college_1"); + AllowedServicePointsInner sp2 = buildAllowedServicePoint("SP_college_2"); + AllowedServicePointsInner sp3 = buildAllowedServicePoint("SP_college_3"); + var allowedSpResponseUniversity = new AllowedServicePointsResponse() + .hold(Set.of(sp1)) + .page(Set.of(sp1)) + .recall(Set.of(sp2, sp3)); + wireMockServer.stubFor(get(urlMatching(ALLOWED_SPS_MOD_CIRCULATION_URL_PATTERN)) .withHeader(HEADER_TENANT, equalTo(TENANT_ID_UNIVERSITY)) .willReturn(jsonResponse(asJsonString(allowedSpResponseUniversity), SC_OK))); - var collegeStubMapping = wireMockServer.stubFor( - get(urlMatching(ALLOWED_SERVICE_POINTS_MOD_CIRCULATION_URL_PATTERN)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_COLLEGE)) - .willReturn(jsonResponse(asJsonString(allowedSpResponseCollege), SC_OK))); - - doGet( - ALLOWED_SERVICE_POINTS_URL + format("?operation=create&requesterId=%s&instanceId=%s", - REQUESTER_ID, INSTANCE_ID)) - .expectStatus().isEqualTo(200) - .expectBody().json("{}"); - - wireMockServer.removeStub(collegeStubMapping); - wireMockServer.stubFor(get(urlMatching(ALLOWED_SERVICE_POINTS_MOD_CIRCULATION_URL_PATTERN)) + var allowedSpResponseCollege = new AllowedServicePointsResponse() + .hold(Set.of(sp2)) + .page(Set.of(sp1)) + .recall(null); + wireMockServer.stubFor(get(urlMatching(ALLOWED_SPS_MOD_CIRCULATION_URL_PATTERN)) .withHeader(HEADER_TENANT, equalTo(TENANT_ID_COLLEGE)) - .willReturn(jsonResponse(asJsonString(allowedSpResponseCollegeWithRouting), - SC_OK))); + .willReturn(jsonResponse(asJsonString(allowedSpResponseCollege), SC_OK))); + // when - then + var allowedSpResponseCombined = new AllowedServicePointsResponse() + .hold(Set.of(sp1, sp2)) + .page(Set.of(sp1)) + .recall(Set.of(sp2, sp3)); doGet( ALLOWED_SERVICE_POINTS_URL + format("?operation=create&requesterId=%s&instanceId=%s", REQUESTER_ID, INSTANCE_ID)) .expectStatus().isEqualTo(200) - .expectBody().json(asJsonString(allowedSpResponseConsortium)); + .expectBody().json(asJsonString(allowedSpResponseCombined)); wireMockServer.verify(getRequestedFor(urlMatching( - ALLOWED_SERVICE_POINTS_MOD_CIRCULATION_URL_PATTERN)) + ALLOWED_SPS_MOD_CIRCULATION_URL_PATTERN)) .withQueryParam("patronGroupId", equalTo(PATRON_GROUP_ID)) - .withQueryParam("instanceId", equalTo(INSTANCE_ID)) .withQueryParam("operation", equalTo("create")) - .withQueryParam("useStubItem", equalTo("true"))); - } - - @Test - void allowedServicePointsShouldReturn422WhenParametersAreInvalid() { - doGet(ALLOWED_SERVICE_POINTS_URL + format("?operation=create&requesterId=%s", randomId())) - .expectStatus().isEqualTo(422); + .withQueryParam("instanceId", equalTo(INSTANCE_ID))); } @Test - void replaceForRequestLinkedToItemWhenPrimaryRequestTypeIsAllowedInBorrowingTenant() { - createEcsTlr(true); + void itemLevelCreateReturnsResponsesFromDataTenants() { + // given + User requester = new User().patronGroup(PATRON_GROUP_ID); + wireMockServer.stubFor(get(urlMatching(USER_URL)) + .withHeader(HEADER_TENANT, equalTo(TENANT_ID_CONSORTIUM)) + .willReturn(jsonResponse(asJsonString(requester), SC_OK))); - var mockAllowedSpResponseFromBorrowingTenant = new AllowedServicePointsResponse() - .page(Set.of(buildAllowedServicePoint("borrowing-page"))) - .hold(Set.of(buildAllowedServicePoint("borrowing-hold"))) - .recall(Set.of(buildAllowedServicePoint("borrowing-recall"))); + var searchItemResponse = new SearchItemResponse(); + searchItemResponse.setTenantId(TENANT_ID_COLLEGE); + searchItemResponse.setInstanceId(INSTANCE_ID); + searchItemResponse.setId(ITEM_ID); + wireMockServer.stubFor(get(urlMatching(SEARCH_ITEM_URL)) + .withHeader(HEADER_TENANT, equalTo(TENANT_ID_CONSORTIUM)) + .willReturn(jsonResponse(asJsonString(searchItemResponse), SC_OK))); - wireMockServer.stubFor(get(urlMatching(ALLOWED_SERVICE_POINTS_MOD_CIRCULATION_URL + - String.format("\\?operation=replace&requestId=%s&useStubItem=true", PRIMARY_REQUEST_ID))) - .withHeader(HEADER_TENANT, equalTo(BORROWING_TENANT_ID)) - .willReturn(jsonResponse(asJsonString(mockAllowedSpResponseFromBorrowingTenant), SC_OK))); + AllowedServicePointsInner sp1 = buildAllowedServicePoint("SP_college_1"); + AllowedServicePointsInner sp2 = buildAllowedServicePoint("SP_college_2"); + var allowedSpResponseCollege = new AllowedServicePointsResponse() + .hold(Set.of(sp1)) + .page(Set.of(sp1)) + .recall(Set.of(sp2)); + wireMockServer.stubFor(get(urlMatching(ALLOWED_SPS_MOD_CIRCULATION_URL_PATTERN)) + .withHeader(HEADER_TENANT, equalTo(TENANT_ID_COLLEGE)) + .willReturn(jsonResponse(asJsonString(allowedSpResponseCollege), + SC_OK))); - doGet(ALLOWED_SERVICE_POINTS_FOR_REPLACE_URL) + // when - then + doGet( + ALLOWED_SERVICE_POINTS_URL + format("?operation=create&requesterId=%s&itemId=%s", + REQUESTER_ID, ITEM_ID)) .expectStatus().isEqualTo(200) - .expectBody() - .jsonPath("Page").doesNotExist() - .jsonPath("Recall").doesNotExist() - .jsonPath("Hold").value(hasSize(1)) - .jsonPath("Hold[0].name").value(is("borrowing-hold")); + .expectBody().json(asJsonString(allowedSpResponseCollege)); - wireMockServer.verify(0, getRequestedFor(urlMatching(REQUEST_STORAGE_URL + ".*"))); - wireMockServer.verify(0, getRequestedFor(urlMatching( - ALLOWED_SERVICE_POINTS_MOD_CIRCULATION_URL_PATTERN)) - .withHeader(HEADER_TENANT, equalTo(LENDING_TENANT_ID))); + wireMockServer.verify(getRequestedFor(urlMatching( + ALLOWED_SPS_MOD_CIRCULATION_URL_PATTERN)) + .withHeader(HEADER_TENANT, equalTo(TENANT_ID_COLLEGE)) + .withQueryParam("patronGroupId", equalTo(PATRON_GROUP_ID)) + .withQueryParam("operation", equalTo("create")) + .withQueryParam("itemId", equalTo(ITEM_ID))); } @Test - void replaceForRequestLinkedToItemWhenPrimaryRequestTypeIsNotAllowedInBorrowingTenant() { - createEcsTlr(true); - + void replaceFailsWhenEcsTlrIsNotFound() { var mockAllowedSpResponseFromBorrowingTenant = new AllowedServicePointsResponse() .page(Set.of(buildAllowedServicePoint("borrowing-page"))) - .hold(null) + .hold(Set.of(buildAllowedServicePoint("borrowing-hold"))) .recall(Set.of(buildAllowedServicePoint("borrowing-recall"))); wireMockServer.stubFor(get(urlMatching(ALLOWED_SERVICE_POINTS_MOD_CIRCULATION_URL + - String.format("\\?operation=replace&requestId=%s&useStubItem=true", PRIMARY_REQUEST_ID))) + String.format("\\?operation=replace&requestId=%s", PRIMARY_REQUEST_ID))) .withHeader(HEADER_TENANT, equalTo(BORROWING_TENANT_ID)) .willReturn(jsonResponse(asJsonString(mockAllowedSpResponseFromBorrowingTenant), SC_OK))); doGet(ALLOWED_SERVICE_POINTS_FOR_REPLACE_URL) - .expectStatus().isEqualTo(200) - .expectBody() - .jsonPath("Page").doesNotExist() - .jsonPath("Hold").doesNotExist() - .jsonPath("Recall").doesNotExist(); - - wireMockServer.verify(0, getRequestedFor(urlMatching(REQUEST_STORAGE_URL + ".*"))); - wireMockServer.verify(0, getRequestedFor(urlMatching( - ALLOWED_SERVICE_POINTS_MOD_CIRCULATION_URL_PATTERN)) - .withHeader(HEADER_TENANT, equalTo(LENDING_TENANT_ID))); + .expectStatus().isEqualTo(500); } - @Test - void replaceForRequestNotLinkedToItemWhenSecondaryRequestTypeIsNoLongerAllowedInLendingTenant() { + @ParameterizedTest + @EnumSource(Request.RequestTypeEnum.class) + void replaceForRequestNotLinkedToItem(Request.RequestTypeEnum secondaryRequestType) { + // given createEcsTlr(false); Request secondaryRequest = new Request().id(SECONDARY_REQUEST_ID) - .requestType(Request.RequestTypeEnum.PAGE); + .requestType(secondaryRequestType); wireMockServer.stubFor(get(urlMatching(REQUEST_STORAGE_URL + "/" + SECONDARY_REQUEST_ID)) .withHeader(HEADER_TENANT, equalTo(LENDING_TENANT_ID)) .willReturn(jsonResponse(asJsonString(secondaryRequest), SC_OK))); - var mockAllowedSpResponseFromLendingTenant = new AllowedServicePointsResponse() - .page(null) - .hold(Set.of(buildAllowedServicePoint("lending-hold"))) - .recall(Set.of(buildAllowedServicePoint("lending-recall"))); + Set servicePoints = Set.of( + buildAllowedServicePoint("SP1"), + buildAllowedServicePoint("SP2") + ); + var allowedSpResponseSecondaryRequestTenant = new AllowedServicePointsResponse(); + switch (secondaryRequestType) { + case PAGE -> allowedSpResponseSecondaryRequestTenant.page(servicePoints); + case HOLD -> allowedSpResponseSecondaryRequestTenant.hold(servicePoints); + case RECALL -> allowedSpResponseSecondaryRequestTenant.recall(servicePoints); + } wireMockServer.stubFor(get(urlMatching(ALLOWED_SERVICE_POINTS_MOD_CIRCULATION_URL + - String.format("\\?operation=replace&requestId=%s&ecsRequestRouting=true", SECONDARY_REQUEST_ID))) + String.format("\\?operation=replace&requestId=%s", SECONDARY_REQUEST_ID))) .withHeader(HEADER_TENANT, equalTo(LENDING_TENANT_ID)) - .willReturn(jsonResponse(asJsonString(mockAllowedSpResponseFromLendingTenant), SC_OK))); + .willReturn(jsonResponse(asJsonString(allowedSpResponseSecondaryRequestTenant), SC_OK))); + // then - then doGet(ALLOWED_SERVICE_POINTS_FOR_REPLACE_URL) .expectStatus().isEqualTo(200) - .expectBody() - .jsonPath("Page").doesNotExist() - .jsonPath("Recall").doesNotExist() - .jsonPath("Hold").doesNotExist(); + .expectBody().json(asJsonString(allowedSpResponseSecondaryRequestTenant)); wireMockServer.verify(0, getRequestedFor(urlMatching( - ALLOWED_SERVICE_POINTS_MOD_CIRCULATION_URL_PATTERN)) - .withHeader(HEADER_TENANT, equalTo(BORROWING_TENANT_ID))); - } - - @Test - void replaceForRequestNotLinkedToItemWhenSecondaryRequestTypeIsAllowedInLendingTenant() { - createEcsTlr(false); - - Request secondaryRequest = new Request().id(SECONDARY_REQUEST_ID) - .requestType(Request.RequestTypeEnum.PAGE); - - wireMockServer.stubFor(get(urlMatching(REQUEST_STORAGE_URL + "/" + SECONDARY_REQUEST_ID)) - .withHeader(HEADER_TENANT, equalTo(LENDING_TENANT_ID)) - .willReturn(jsonResponse(asJsonString(secondaryRequest), SC_OK))); - - var mockAllowedSpResponseFromLendingTenant = new AllowedServicePointsResponse() - .page(Set.of(buildAllowedServicePoint("lending-page"))) - .hold(null) - .recall(null); - - wireMockServer.stubFor(get(urlMatching(ALLOWED_SERVICE_POINTS_MOD_CIRCULATION_URL + - String.format("\\?operation=replace&requestId=%s&ecsRequestRouting=true", SECONDARY_REQUEST_ID))) - .withHeader(HEADER_TENANT, equalTo(LENDING_TENANT_ID)) - .willReturn(jsonResponse(asJsonString(mockAllowedSpResponseFromLendingTenant), SC_OK))); - - var mockAllowedSpResponseFromBorrowingTenant = new AllowedServicePointsResponse() - .page(Set.of(buildAllowedServicePoint("borrowing-page"))) - .hold(Set.of(buildAllowedServicePoint("borrowing-hold"))) - .recall(Set.of(buildAllowedServicePoint("borrowing-recall"))); - - wireMockServer.stubFor( - get(urlMatching(ALLOWED_SERVICE_POINTS_MOD_CIRCULATION_URL + - format("\\?operation=replace&requestId=%s&useStubItem=true", PRIMARY_REQUEST_ID))) - .withHeader(HEADER_TENANT, equalTo(BORROWING_TENANT_ID)) - .willReturn(jsonResponse(asJsonString(mockAllowedSpResponseFromBorrowingTenant), SC_OK))); - - doGet(ALLOWED_SERVICE_POINTS_FOR_REPLACE_URL) - .expectStatus().isEqualTo(200) - .expectBody() - .jsonPath("Page").doesNotExist() - .jsonPath("Recall").doesNotExist() - .jsonPath("Hold[0].name").value(is("borrowing-hold")); - - wireMockServer.verify(getRequestedFor(urlMatching( - ALLOWED_SERVICE_POINTS_MOD_CIRCULATION_URL_PATTERN)) + ALLOWED_SPS_MOD_CIRCULATION_URL_PATTERN)) .withHeader(HEADER_TENANT, equalTo(BORROWING_TENANT_ID))); } - @Test - void replaceForRequestNotLinkedToItemWhenPrimaryRequestTypeIsNotAllowedInBorrowingTenant() { - createEcsTlr(false); - - Request secondaryRequest = new Request().id(SECONDARY_REQUEST_ID) - .requestType(Request.RequestTypeEnum.PAGE); - - wireMockServer.stubFor(get(urlMatching(REQUEST_STORAGE_URL + "/" + SECONDARY_REQUEST_ID)) - .withHeader(HEADER_TENANT, equalTo(LENDING_TENANT_ID)) - .willReturn(jsonResponse(asJsonString(secondaryRequest), SC_OK))); - - var mockAllowedSpResponseFromLendingTenant = new AllowedServicePointsResponse() - .page(Set.of(buildAllowedServicePoint("lending-page"))) - .hold(null) - .recall(null); - - wireMockServer.stubFor(get(urlMatching(ALLOWED_SERVICE_POINTS_MOD_CIRCULATION_URL + - String.format("\\?operation=replace&requestId=%s&ecsRequestRouting=true", SECONDARY_REQUEST_ID))) - .withHeader(HEADER_TENANT, equalTo(LENDING_TENANT_ID)) - .willReturn(jsonResponse(asJsonString(mockAllowedSpResponseFromLendingTenant), SC_OK))); - - var mockAllowedSpResponseFromBorrowingTenant = new AllowedServicePointsResponse() - .page(Set.of(buildAllowedServicePoint("borrowing-page"))) - .hold(null) - .recall(Set.of(buildAllowedServicePoint("borrowing-recall"))); - - wireMockServer.stubFor(get(urlMatching(ALLOWED_SERVICE_POINTS_MOD_CIRCULATION_URL + - String.format("\\?operation=replace&requestId=%s&useStubItem=true", PRIMARY_REQUEST_ID))) - .withHeader(HEADER_TENANT, equalTo(BORROWING_TENANT_ID)) - .willReturn(jsonResponse(asJsonString(mockAllowedSpResponseFromBorrowingTenant), SC_OK))); - - doGet(ALLOWED_SERVICE_POINTS_FOR_REPLACE_URL) - .expectStatus().isEqualTo(200) - .expectBody() - .jsonPath("Page").doesNotExist() - .jsonPath("Recall").doesNotExist() - .jsonPath("Hold").doesNotExist(); - } - - @Test - void replaceFailsWhenEcsTlrIsNotFound() { - var mockAllowedSpResponseFromBorrowingTenant = new AllowedServicePointsResponse() - .page(Set.of(buildAllowedServicePoint("borrowing-page"))) - .hold(Set.of(buildAllowedServicePoint("borrowing-hold"))) - .recall(Set.of(buildAllowedServicePoint("borrowing-recall"))); - - wireMockServer.stubFor(get(urlMatching(ALLOWED_SERVICE_POINTS_MOD_CIRCULATION_URL + - String.format("\\?operation=replace&requestId=%s&useStubItem=false", PRIMARY_REQUEST_ID))) - .withHeader(HEADER_TENANT, equalTo(BORROWING_TENANT_ID)) - .willReturn(jsonResponse(asJsonString(mockAllowedSpResponseFromBorrowingTenant), SC_OK))); - - doGet(ALLOWED_SERVICE_POINTS_FOR_REPLACE_URL) - .expectStatus().isEqualTo(500); - } - - @Test - void allowedSpWithItemLevelReturnsResultSpInResponsesFromDataTenant() { - var searchItemResponse = new SearchItemResponse(); - searchItemResponse.setTenantId(TENANT_ID_COLLEGE); - searchItemResponse.setInstanceId(INSTANCE_ID); - searchItemResponse.setId(ITEM_ID); - - wireMockServer.stubFor(get(urlMatching(SEARCH_ITEM_URL)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_CONSORTIUM)) - .willReturn(jsonResponse(asJsonString(searchItemResponse), SC_OK))); - - var allowedSpResponseConsortium = new AllowedServicePointsResponse(); - allowedSpResponseConsortium.setHold(Set.of( - buildAllowedServicePoint("SP_consortium_1"), - buildAllowedServicePoint("SP_consortium_2"))); - allowedSpResponseConsortium.setPage(null); - allowedSpResponseConsortium.setRecall(Set.of( - buildAllowedServicePoint("SP_consortium_3"))); - - var allowedSpResponseCollege = new AllowedServicePointsResponse(); - allowedSpResponseCollege.setHold(Set.of( - buildAllowedServicePoint("SP_college_1"))); - allowedSpResponseCollege.setPage(null); - allowedSpResponseCollege.setRecall(Set.of( - buildAllowedServicePoint("SP_college_2"))); - - User requester = new User().patronGroup(PATRON_GROUP_ID); - wireMockServer.stubFor(get(urlMatching(USER_URL)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_CONSORTIUM)) - .willReturn(jsonResponse(asJsonString(requester), SC_OK))); - - wireMockServer.stubFor(get(urlMatching(ALLOWED_SERVICE_POINTS_MOD_CIRCULATION_URL_PATTERN)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_CONSORTIUM)) - .willReturn(jsonResponse(asJsonString(allowedSpResponseConsortium), SC_OK))); - - wireMockServer.stubFor(get(urlMatching(ALLOWED_SERVICE_POINTS_MOD_CIRCULATION_URL_PATTERN)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_COLLEGE)) - .willReturn(jsonResponse(asJsonString(allowedSpResponseCollege), - SC_OK))); - - doGet( - ALLOWED_SERVICE_POINTS_URL + format("?operation=create&requesterId=%s&itemId=%s", - REQUESTER_ID, ITEM_ID)) - .expectStatus().isEqualTo(200) - .expectBody().json(asJsonString(allowedSpResponseConsortium)); - - wireMockServer.verify(getRequestedFor(urlMatching( - ALLOWED_SERVICE_POINTS_MOD_CIRCULATION_URL_PATTERN)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_COLLEGE)) - .withQueryParam("patronGroupId", equalTo(PATRON_GROUP_ID)) - .withQueryParam("operation", equalTo("create")) - .withQueryParam("ecsRequestRouting", equalTo("true")) - .withQueryParam("itemId", equalTo(ITEM_ID))); - - wireMockServer.verify(getRequestedFor(urlMatching( - ALLOWED_SERVICE_POINTS_MOD_CIRCULATION_URL_PATTERN)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_CONSORTIUM)) - .withQueryParam("patronGroupId", equalTo(PATRON_GROUP_ID)) - .withQueryParam("instanceId", equalTo(INSTANCE_ID)) - .withQueryParam("operation", equalTo("create")) - .withQueryParam("useStubItem", equalTo("true"))); - } - private AllowedServicePointsInner buildAllowedServicePoint(String name) { return new AllowedServicePointsInner() .id(randomId()) diff --git a/src/test/java/org/folio/controller/EcsRequestExternalControllerTest.java b/src/test/java/org/folio/controller/EcsRequestExternalControllerTest.java new file mode 100644 index 00000000..f881d801 --- /dev/null +++ b/src/test/java/org/folio/controller/EcsRequestExternalControllerTest.java @@ -0,0 +1,113 @@ +package org.folio.controller; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.CREATED; + +import java.util.Date; +import java.util.UUID; + +import org.folio.domain.dto.EcsRequestExternal; +import org.folio.domain.dto.EcsTlr; +import org.folio.domain.mapper.ExternalEcsRequestMapper; +import org.folio.domain.mapper.ExternalEcsRequestMapperImpl; +import org.folio.exception.RequestCreatingException; +import org.folio.service.EcsTlrService; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class EcsRequestExternalControllerTest { + private static final String ERROR_MESSAGE = "Error message"; + @Mock + private EcsTlrService ecsTlrService; + @Spy + private final ExternalEcsRequestMapper externalEcsRequestMapper = + new ExternalEcsRequestMapperImpl(); + @InjectMocks + private EcsRequestExternalController ecsRequestExternalController; + + @Test + void ecsRequestExternalShouldSuccessfullyBeCreatedForPageRequestType() { + EcsRequestExternal ecsRequestExternal = new EcsRequestExternal() + .instanceId(UUID.randomUUID().toString()) + .requesterId(UUID.randomUUID().toString()) + .requestLevel(EcsRequestExternal.RequestLevelEnum.TITLE) + .fulfillmentPreference(EcsRequestExternal.FulfillmentPreferenceEnum.HOLD_SHELF) + .requestDate(new Date()); + EcsTlr pageEcsTlr = new EcsTlr().requestType(EcsTlr.RequestTypeEnum.PAGE); + + when(ecsTlrService.create(any(EcsTlr.class))) + .thenReturn(pageEcsTlr); + + var response = ecsRequestExternalController.postEcsRequestExternal(ecsRequestExternal); + + assertEquals(CREATED, response.getStatusCode()); + assertEquals(pageEcsTlr, response.getBody()); + } + + @Test + void ecsRequestExternalShouldSuccessfullyBeCreatedForRecallRequestType() { + EcsRequestExternal ecsRequestExternal = new EcsRequestExternal() + .instanceId(UUID.randomUUID().toString()) + .requesterId(UUID.randomUUID().toString()) + .requestLevel(EcsRequestExternal.RequestLevelEnum.TITLE) + .fulfillmentPreference(EcsRequestExternal.FulfillmentPreferenceEnum.HOLD_SHELF) + .requestDate(new Date()); + EcsTlr recallEcsTlr = new EcsTlr().requestType(EcsTlr.RequestTypeEnum.RECALL); + + when(ecsTlrService.create(any(EcsTlr.class))) + .thenThrow(new RequestCreatingException(ERROR_MESSAGE)) + .thenReturn(recallEcsTlr); + + var response = ecsRequestExternalController.postEcsRequestExternal(ecsRequestExternal); + + assertEquals(CREATED, response.getStatusCode()); + assertEquals(recallEcsTlr, response.getBody()); + } + + @Test + void ecsRequestExternalShouldSuccessfullyBeCreatedForHoldRequestType() { + EcsRequestExternal ecsRequestExternal = new EcsRequestExternal() + .instanceId(UUID.randomUUID().toString()) + .requesterId(UUID.randomUUID().toString()) + .requestLevel(EcsRequestExternal.RequestLevelEnum.TITLE) + .fulfillmentPreference(EcsRequestExternal.FulfillmentPreferenceEnum.HOLD_SHELF) + .requestDate(new Date()); + EcsTlr holdEcsTlr = new EcsTlr().requestType(EcsTlr.RequestTypeEnum.HOLD); + + when(ecsTlrService.create(any(EcsTlr.class))) + .thenThrow(new RequestCreatingException(ERROR_MESSAGE)) + .thenThrow(new RequestCreatingException(ERROR_MESSAGE)) + .thenReturn(holdEcsTlr); + + var response = ecsRequestExternalController.postEcsRequestExternal(ecsRequestExternal); + + assertEquals(CREATED, response.getStatusCode()); + assertEquals(holdEcsTlr, response.getBody()); + } + + @Test + void ecsRequestExternalShouldReturnBadRequest() { + EcsRequestExternal ecsRequestExternal = new EcsRequestExternal() + .instanceId(UUID.randomUUID().toString()) + .requesterId(UUID.randomUUID().toString()) + .requestLevel(EcsRequestExternal.RequestLevelEnum.TITLE) + .fulfillmentPreference(EcsRequestExternal.FulfillmentPreferenceEnum.HOLD_SHELF) + .requestDate(new Date()); + + when(ecsTlrService.create(any(EcsTlr.class))) + .thenThrow(new RequestCreatingException(ERROR_MESSAGE)) + .thenThrow(new RequestCreatingException(ERROR_MESSAGE)) + .thenThrow(new RequestCreatingException(ERROR_MESSAGE)); + + assertEquals(BAD_REQUEST, ecsRequestExternalController.postEcsRequestExternal( + ecsRequestExternal).getStatusCode()); + } +}