diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 42a4ca62d..46e90f423 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -998,6 +998,22 @@ } ] }, + { + "id": "orders.routing-list", + "version": "1.0", + "handlers": [ + { + "methods": ["GET"], + "pathPattern": "/orders/routing-lists/{id}/template", + "permissionsRequired": ["orders.routing-list-template.item.get"], + "modulePermissions": [ + "orders-storage.routing-lists.item.get", + "users.collection.get", + "template-request.post" + ] + } + ] + }, { "id": "_jsonSchemas", "version": "1.0", @@ -1198,6 +1214,14 @@ { "id": "user-tenants", "version": "1.0" + }, + { + "id": "users", + "version": "16.0" + }, + { + "id": "template-engine", + "version": "2.2" } ], "optional": [ @@ -1705,6 +1729,11 @@ "displayName" : "orders holding-summary get", "description" : "Holding summary" }, + { + "permissionName": "orders.routing-list-template.item.get", + "displayName" : "orders routing-list-template item get", + "description" : "Orders routing-list-template item get" + }, { "permissionName": "orders.all", "displayName": "orders - all permissions", @@ -1739,8 +1768,8 @@ "orders.rollover.item.post", "orders.holding-summary.collection.get", "orders.acquisition-methods.all", - "orders.export-history.all" - + "orders.export-history.all", + "orders.routing-list-template.item.get" ] }, { diff --git a/ramls/routing-lists.raml b/ramls/routing-lists.raml new file mode 100644 index 000000000..b98725dad --- /dev/null +++ b/ramls/routing-lists.raml @@ -0,0 +1,47 @@ +#%RAML 1.0 +title: "RoutingList" +baseUri: https://github.com/folio-org/mod-orders +version: v1.0 + +documentation: + - title: Routing lists + content: CRUD API to manage 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}$ + +traits: + pageable: !include raml-util/traits/pageable.raml + searchable: !include raml-util/traits/searchable.raml + +resourceTypes: + collection: !include rtypes/collection-with-json-response.raml + collection-item: !include rtypes/item-collection-with-json-response.raml + +/orders/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 + get: + description: Get routing lists + is: [ + searchable: {description: "with valid searchable fields: for example routing list", example: "[\"routing_list\", \"ROUTING_LIST\", \"=\"]"}, + pageable + ] + post: + description: Create routing lists + + /{id}/template: + uriParameters: + id: + description: The UUID of a Title + 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 584837b4b..6c1a45321 100644 --- a/src/main/java/org/folio/config/ApplicationConfig.java +++ b/src/main/java/org/folio/config/ApplicationConfig.java @@ -26,6 +26,7 @@ import org.folio.service.ReasonForClosureService; import org.folio.service.SuffixService; import org.folio.service.TagService; +import org.folio.service.UserService; import org.folio.service.caches.ConfigurationEntriesCache; import org.folio.service.caches.InventoryCache; import org.folio.service.configuration.ConfigurationEntriesService; @@ -111,6 +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.titles.TitleValidationService; import org.folio.service.titles.TitlesService; import org.springframework.beans.factory.annotation.Qualifier; @@ -434,6 +436,16 @@ CompositeOrderDynamicDataPopulateService combinedPopulateService(CompositeOrderR return new CombinedOrderDataPopulateService(compositeOrderRetrieveHolderBuilder, populateServices); } + @Bean + RoutingListService routingListService(RestClient restClient, UserService userService) { + return new RoutingListService(restClient, userService); + } + + @Bean + UserService userService(RestClient restClient) { + return new UserService(restClient); + } + @Bean TitlesService titlesService(RestClient restClient, ProtectionService protectionService, InventoryManager inventoryManager) { return new TitlesService(restClient, protectionService, inventoryManager); diff --git a/src/main/java/org/folio/models/TemplateProcessingRequest.java b/src/main/java/org/folio/models/TemplateProcessingRequest.java new file mode 100644 index 000000000..7646ecfc6 --- /dev/null +++ b/src/main/java/org/folio/models/TemplateProcessingRequest.java @@ -0,0 +1,135 @@ +package org.folio.models; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import org.folio.rest.jaxrs.model.RoutingList; + +public class TemplateProcessingRequest { + @JsonProperty + private UUID templateId; + @JsonProperty + private String lang; + @JsonProperty + private String outputFormat; + @JsonProperty + private Context context; + + public UUID getTemplateId() { + return templateId; + } + + public String getLang() { + return lang; + } + + public String getOutputFormat() { + return outputFormat; + } + + public Context getContext() { + return context; + } + + public TemplateProcessingRequest withTemplateId(UUID templateId) { + this.templateId = templateId; + return this; + } + + public TemplateProcessingRequest withLang(String lang) { + this.lang = lang; + return this; + } + + public TemplateProcessingRequest withOutputFormat(String outputFormat) { + this.outputFormat = outputFormat; + return this; + } + + public TemplateProcessingRequest withContext(Context context) { + this.context = context; + return this; + } + + public static class Context { + @JsonProperty + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private RoutingList routingList; + @JsonProperty + private List users; + + public RoutingList getRoutingList() { + return routingList; + } + + public List getUsers() { + return users; + } + + public Context withRoutingList(RoutingList routingList) { + this.routingList = routingList; + return this; + } + + public Context withUsers(List users) { + this.users = users; + return this; + } + } + + public static class User { + @JsonProperty + private String lastName; + @JsonProperty + private String firstName; + @JsonProperty + private String routingAddress; + + public String getLastName() { + return lastName; + } + + public String getFirstName() { + return firstName; + } + + public String getRoutingAddress() { + return routingAddress; + } + + public User withLastName(String lastName) { + this.lastName = lastName; + return this; + } + + public User withFirstName(String firstName) { + this.firstName = firstName; + return this; + } + + public User withRoutingAddress(String routingAddress) { + this.routingAddress = routingAddress; + return this; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof TemplateProcessingRequest that)) return false; + return Objects.equals(templateId, that.templateId) + && Objects.equals(lang, that.lang) + && Objects.equals(outputFormat, that.outputFormat) + && Objects.equals(context, that.context); + } + + @Override + public int hashCode() { + return Objects.hash(templateId, lang, outputFormat, context); + } +} + diff --git a/src/main/java/org/folio/models/UserCollection.java b/src/main/java/org/folio/models/UserCollection.java new file mode 100644 index 000000000..889fbe31b --- /dev/null +++ b/src/main/java/org/folio/models/UserCollection.java @@ -0,0 +1,76 @@ +package org.folio.models; + +import java.util.List; +import java.util.UUID; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class UserCollection { + @JsonProperty + private List users; + @JsonProperty + private int totalRecords; + + public List getUsers() { + return users; + } + public int getTotalRecords() { + return totalRecords; + } + + public UserCollection withUsers(List users) { + this.users = users; + return this; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class User { + @JsonProperty + private UUID id; + @JsonProperty + private Personal personal; + + public UUID getId () { + return id; + } + public Personal getPersonal () { + return personal; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Personal { + @JsonProperty + private String firstName; + @JsonProperty + private String lastName; + @JsonProperty + private List
addresses; + + public String getFirstName() { + return firstName; + } + public String getLastName() { + return lastName; + } + public List
getAddresses() { + return addresses; + } + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Address { + @JsonProperty + private String addressLine1; + @JsonProperty + private String addressTypeId; + + public String getAddressLine1() { + return addressLine1; + } + public String getAddressTypeId() { + return addressTypeId; + } + } + } + } +} diff --git a/src/main/java/org/folio/orders/utils/ResourcePathResolver.java b/src/main/java/org/folio/orders/utils/ResourcePathResolver.java index 50b3da277..4e29b8e7a 100644 --- a/src/main/java/org/folio/orders/utils/ResourcePathResolver.java +++ b/src/main/java/org/folio/orders/utils/ResourcePathResolver.java @@ -28,6 +28,7 @@ private ResourcePathResolver() { public static final String PAYMENT_STATUS = "paymentStatus"; public static final String ORDER_TEMPLATES = "orderTemplates"; public static final String TITLES = "titles"; + public static final String TEMPLATE_REQUEST = "templateRequest"; public static final String FUNDS = "finance.funds"; public static final String BUDGETS = "finance.budgets"; public static final String LEDGERS = "finance.ledgers"; @@ -47,6 +48,9 @@ private ResourcePathResolver() { public static final String ORDER_INVOICE_RELATIONSHIP = "order-invoice-relationship"; public static final String EXPORT_HISTORY = "export-history"; public static final String TAGS = "tags"; + public static final String ROUTING_LISTS = "routingLists"; + public static final String ORDER_SETTINGS = "orderSettings"; + public static final String USERS = "users"; private static final Map SUB_OBJECT_ITEM_APIS; private static final Map SUB_OBJECT_COLLECTION_APIS; @@ -66,6 +70,7 @@ private ResourcePathResolver() { apis.put(RECEIVING_HISTORY, "/orders-storage/receiving-history"); apis.put(PO_LINE_NUMBER, "/orders-storage/po-line-number"); apis.put(ORDER_TEMPLATES, "/orders-storage/order-templates"); + apis.put(TEMPLATE_REQUEST, "/template-request"); apis.put(FUNDS, "/finance/funds"); apis.put(BUDGETS, "/finance/budgets"); apis.put(LEDGERS, "/finance-storage/ledgers"); @@ -86,6 +91,9 @@ private ResourcePathResolver() { apis.put(ORDER_INVOICE_RELATIONSHIP, "/orders-storage/order-invoice-relns"); apis.put(EXPORT_HISTORY, "/orders-storage/export-history"); apis.put(TAGS, "/tags"); + apis.put(USERS, "/users"); + apis.put(ORDER_SETTINGS, "/orders-storage/settings"); + apis.put(ROUTING_LISTS, "/orders-storage/routing-lists"); SUB_OBJECT_COLLECTION_APIS = Collections.unmodifiableMap(apis); SUB_OBJECT_ITEM_APIS = Collections.unmodifiableMap( diff --git a/src/main/java/org/folio/rest/core/RestClient.java b/src/main/java/org/folio/rest/core/RestClient.java index 17a787c98..d95ac0f29 100644 --- a/src/main/java/org/folio/rest/core/RestClient.java +++ b/src/main/java/org/folio/rest/core/RestClient.java @@ -10,13 +10,6 @@ import java.util.Map; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.folio.okapi.common.WebClientFactory; -import org.folio.rest.core.exceptions.HttpException; -import org.folio.rest.core.models.RequestContext; -import org.folio.rest.core.models.RequestEntry; - import io.vertx.core.Context; import io.vertx.core.Future; import io.vertx.core.MultiMap; @@ -29,6 +22,12 @@ import io.vertx.ext.web.client.WebClientOptions; import io.vertx.ext.web.client.predicate.ErrorConverter; import io.vertx.ext.web.client.predicate.ResponsePredicate; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.folio.okapi.common.WebClientFactory; +import org.folio.rest.core.exceptions.HttpException; +import org.folio.rest.core.models.RequestContext; +import org.folio.rest.core.models.RequestEntry; public class RestClient { @@ -243,12 +242,14 @@ public Future getAsJsonObject(RequestEntry requestEntry, RequestCont public String extractRecordId(HttpResponse response) { JsonObject body = response.bodyAsJsonObject(); - String id; + String id = ""; if (body != null && !body.isEmpty() && body.containsKey(ID)) { id = body.getString(ID); } else { String location = response.getHeader(LOCATION); - id = location.substring(location.lastIndexOf('/') + 1); + if (location != null) { + id = location.substring(location.lastIndexOf('/') + 1); + } } return id; } diff --git a/src/main/java/org/folio/rest/impl/RoutingListsAPI.java b/src/main/java/org/folio/rest/impl/RoutingListsAPI.java new file mode 100644 index 000000000..23a61328d --- /dev/null +++ b/src/main/java/org/folio/rest/impl/RoutingListsAPI.java @@ -0,0 +1,48 @@ +package org.folio.rest.impl; + +import static io.vertx.core.Future.succeededFuture; + +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.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.spring.SpringContextUtil; +import org.springframework.beans.factory.annotation.Autowired; + +public class RoutingListsAPI extends BaseApi implements OrdersRoutingLists { + + @Autowired + private RoutingListService routingListService; + + public RoutingListsAPI() { + SpringContextUtil.autowireDependencies(this, Vertx.currentContext()); + } + + @Override + public void getOrdersRoutingLists(String query, String totalRecords, int offset, int limit, Map okapiHeaders, + Handler> asyncResultHandler, Context vertxContext) { + throw new NotImplementedException(); + } + + @Override + public void postOrdersRoutingLists(RoutingList entity, Map okapiHeaders, + Handler> asyncResultHandler, Context vertxContext) { + throw new NotImplementedException(); + } + + @Override + public void getOrdersRoutingListsTemplateById(String id, Map okapiHeaders, + Handler> asyncResultHandler, Context vertxContext) { + routingListService.processTemplateRequest(id, new RequestContext(vertxContext, okapiHeaders)) + .onSuccess(jsonObject -> asyncResultHandler.handle(succeededFuture(this.buildOkResponse(jsonObject)))) + .onFailure(t -> handleErrorResponse(asyncResultHandler, t)); + } +} diff --git a/src/main/java/org/folio/service/RoutingListService.java b/src/main/java/org/folio/service/RoutingListService.java new file mode 100644 index 000000000..8c9b92b9e --- /dev/null +++ b/src/main/java/org/folio/service/RoutingListService.java @@ -0,0 +1,139 @@ +package org.folio.service; + +import static org.folio.orders.utils.ResourcePathResolver.ORDER_SETTINGS; +import static org.folio.orders.utils.ResourcePathResolver.ROUTING_LISTS; +import static org.folio.orders.utils.ResourcePathResolver.TEMPLATE_REQUEST; +import static org.folio.orders.utils.ResourcePathResolver.resourcesPath; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import io.vertx.core.Future; +import io.vertx.core.json.JsonObject; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.kafka.common.errors.ResourceNotFoundException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.folio.models.TemplateProcessingRequest; +import org.folio.models.UserCollection; +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; + +public class RoutingListService { + + 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 TEMPLATE_REQUEST_ENDPOINT = resourcesPath(TEMPLATE_REQUEST); + + private final RestClient restClient; + private final UserService userService; + + public RoutingListService(RestClient restClient, UserService userService) { + this.restClient = restClient; + this.userService = userService; + } + + public Future processTemplateRequest(String routingListId, RequestContext requestContext) { + log.debug("processTemplateRequest: Tying to process template request for routingListId={}", routingListId); + return getRoutingListById(routingListId, requestContext) + .compose(routingList -> getUsersAndCreateTemplate(routingList, requestContext)) + .compose(templateProcessingRequest -> postTemplateRequest(templateProcessingRequest, requestContext)); + } + + public Future getRoutingListById(String routingListId, RequestContext requestContext) { + var requestEntry = new RequestEntry(ROUTING_LIST_BY_ID_ENDPOINT).withId(routingListId); + return restClient.get(requestEntry, RoutingList.class, requestContext); + } + + private Future getUsersAndCreateTemplate(RoutingList routingList, RequestContext requestContext) { + return getAddressTypeId(requestContext) + .compose(addressTypId -> userService.getUsersByIds(routingList.getUserIds(), requestContext) + .map(users -> createTemplateRequest(routingList, users, addressTypId))); + } + + private Future getAddressTypeId(RequestContext requestContext) { + var requestEntry = new RequestEntry(ORDER_SETTINGS_ENDPOINT) + .withQuery("key=" + ROUTING_USER_ADDRESS_TYPE_ID); + return restClient.get(requestEntry, SettingCollection.class, 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); + throw new ResourceNotFoundException(String.format("Setting is not found with key=%s", ROUTING_USER_ADDRESS_TYPE_ID)); + } + return settings.get(0).getValue(); + }); + } + + private TemplateProcessingRequest createTemplateRequest(RoutingList routingList, UserCollection users, String addressTypeId) { + var templateRequest = createBaseTemplateRequest(); + templateRequest.withContext(new TemplateProcessingRequest.Context() + .withRoutingList(fillRoutingListForContext(routingList)) + .withUsers(fillUsersForContext(users, addressTypeId))); + + log.info("createTemplateRequest:: TemplateProcessingRequest object created for routing list name: {}", + templateRequest.getContext().getRoutingList().getName()); + return templateRequest; + } + + private TemplateProcessingRequest createBaseTemplateRequest() { + return new TemplateProcessingRequest() + .withTemplateId(TEMPLATE_REQUEST_ID) + .withLang(TEMPLATE_REQUEST_LANG) + .withOutputFormat(TEMPLATE_REQUEST_OUTPUT); + } + + private List fillUsersForContext(UserCollection userCollection, String addressTypeId) { + if (userCollection.getUsers().isEmpty()) { + return Collections.emptyList(); + } + return userCollection.getUsers().stream() + .map(UserCollection.User::getPersonal) + .filter(ObjectUtils::isNotEmpty) + .map(personalData -> { + var userForContext = new TemplateProcessingRequest.User() + .withFirstName(personalData.getFirstName()) + .withLastName(personalData.getLastName()); + List addressList = personalData.getAddresses(); + if (addressList != null && !addressList.isEmpty()) { + userForContext.withRoutingAddress(getUserAddress(addressList, addressTypeId)); + } + return userForContext; + } + ).toList(); + } + + 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); + return address.getAddressLine1(); + } + } + log.warn("getUserAddress:: Required address is not found with addressTypId={}", addressTypeId); + return ""; + } + + private RoutingList fillRoutingListForContext(RoutingList routingList) { + return new RoutingList() + .withName(routingList.getName()) + .withNotes(routingList.getNotes()); + } + + 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()); + return restClient.postJsonObject(requestEntry, JsonObject.mapFrom(templateRequest), requestContext); + } +} diff --git a/src/main/java/org/folio/service/UserService.java b/src/main/java/org/folio/service/UserService.java index d19d99f65..97aa93b75 100644 --- a/src/main/java/org/folio/service/UserService.java +++ b/src/main/java/org/folio/service/UserService.java @@ -1,12 +1,55 @@ package org.folio.service; +import static org.folio.orders.utils.HelperUtils.collectResultsOnSuccess; +import static org.folio.orders.utils.HelperUtils.convertIdsToCqlQuery; +import static org.folio.orders.utils.ResourcePathResolver.USERS; +import static org.folio.orders.utils.ResourcePathResolver.resourcesPath; +import static org.folio.rest.RestConstants.MAX_IDS_FOR_GET_RQ_15; import static org.folio.rest.RestVerticle.OKAPI_USERID_HEADER; +import java.util.List; import java.util.Map; +import io.vertx.core.Future; +import one.util.streamex.StreamEx; +import org.apache.commons.lang3.ObjectUtils; +import org.folio.models.UserCollection; +import org.folio.rest.core.RestClient; +import org.folio.rest.core.models.RequestContext; +import org.folio.rest.core.models.RequestEntry; + public class UserService { + private static final String USERS_ENDPOINT = resourcesPath(USERS); + private final RestClient restClient; + + public UserService(RestClient restClient) { + this.restClient = restClient; + } + public static String getCurrentUserId(Map okapiHeaders) { return okapiHeaders.get(OKAPI_USERID_HEADER); } + + public Future getUsersByIds(List userIds, RequestContext requestContext) { + var futures = StreamEx.ofSubLists(userIds, MAX_IDS_FOR_GET_RQ_15) + .map(ids -> getBatchUsersByIds(ids, requestContext)) + .toList(); + return collectResultsOnSuccess(futures) + .map(this::combineUserCollections); + } + + private Future getBatchUsersByIds(List userIds, RequestContext requestContext) { + var requestEntry = new RequestEntry(USERS_ENDPOINT).withOffset(0).withLimit(Integer.MAX_VALUE) + .withQuery(convertIdsToCqlQuery(userIds, "id")); + return restClient.get(requestEntry, UserCollection.class, requestContext); + } + + private UserCollection combineUserCollections(List userCollections) { + var userList = userCollections.stream() + .filter(ObjectUtils::isNotEmpty) + .flatMap(userCollection -> userCollection.getUsers().stream()) + .toList(); + return new UserCollection().withUsers(userList); + } } diff --git a/src/test/java/org/folio/ApiTestSuite.java b/src/test/java/org/folio/ApiTestSuite.java index aaa6890d9..f0dac243e 100644 --- a/src/test/java/org/folio/ApiTestSuite.java +++ b/src/test/java/org/folio/ApiTestSuite.java @@ -33,6 +33,7 @@ import org.folio.rest.impl.PurchaseOrderLinesApiTest; import org.folio.rest.impl.PurchaseOrdersApiTest; import org.folio.rest.impl.ReceivingHistoryApiTest; +import org.folio.rest.impl.RoutingListsApiTest; import org.folio.rest.impl.TitlesApiTest; import org.folio.rest.impl.crud.ConfigurationCrudTest; import org.folio.rest.impl.protection.LinesProtectionTest; @@ -43,6 +44,7 @@ import org.folio.service.ReasonForClosureServiceTest; import org.folio.service.SuffixServiceTest; import org.folio.service.TagServiceTest; +import org.folio.service.UserServiceTest; import org.folio.service.consortium.SharingInstanceServiceTest; import org.folio.service.exchange.ManualExchangeRateProviderTest; import org.folio.service.expenceclass.ExpenseClassValidationServiceTest; @@ -97,6 +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.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Nested; @@ -181,6 +184,10 @@ class OrderTemplateTestNested extends OrderTemplateTest { class TitlesApiTestNested extends TitlesApiTest { } + @Nested + class RoutingListsApiTestNested extends RoutingListsApiTest { + } + @Nested class ConfigurationCrudTestNested extends ConfigurationCrudTest { } @@ -470,4 +477,12 @@ class FiscalYearServiceTestNested extends FiscalYearServiceTest { @Nested class TagServiceTestNested extends TagServiceTest { } + + @Nested + class RoutingListServiceTestNested extends RoutingListServiceTest { + } + + @Nested + class UserServiceTestNested extends UserServiceTest { + } } diff --git a/src/test/java/org/folio/TestConstants.java b/src/test/java/org/folio/TestConstants.java index 809f13c6e..8297479bd 100644 --- a/src/test/java/org/folio/TestConstants.java +++ b/src/test/java/org/folio/TestConstants.java @@ -1,5 +1,6 @@ package org.folio; +import static org.folio.orders.utils.PermissionsUtil.OKAPI_HEADER_PERMISSIONS; import static org.folio.rest.RestVerticle.OKAPI_HEADER_TENANT; import static org.folio.rest.RestVerticle.OKAPI_HEADER_TOKEN; import static org.folio.rest.RestVerticle.OKAPI_USERID_HEADER; @@ -9,6 +10,8 @@ import java.util.UUID; import io.restassured.http.Header; +import io.vertx.core.json.JsonArray; +import org.folio.orders.utils.AcqDesiredPermissions; public final class TestConstants { @@ -63,6 +66,7 @@ private TestConstants() {} public static final Header NON_EXIST_LOAN_TYPE_TENANT_HEADER = new Header(OKAPI_HEADER_TENANT, NON_EXIST_LOAN_TYPE_TENANT); public static final Header NON_EXIST_CONFIG_X_OKAPI_TENANT = new Header(OKAPI_HEADER_TENANT, "ordersimpltest"); public static final Header X_OKAPI_USER_ID = new Header(OKAPI_USERID_HEADER, "440c89e3-7f6c-578a-9ea8-310dad23605e"); + public static final Header ALL_DESIRED_ACQ_PERMISSIONS_HEADER = new Header(OKAPI_HEADER_PERMISSIONS, new JsonArray(AcqDesiredPermissions.getValuesExceptBypass()).encode()); public static final Header X_OKAPI_USER_ID_WITH_ACQ_UNITS = new Header(OKAPI_USERID_HEADER, USER_ID_ASSIGNED_TO_ACQ_UNITS); public static final Header X_OKAPI_TOKEN = new Header(OKAPI_HEADER_TOKEN, "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkaWt1X2FkbWluIiwidXNlcl9pZCI6ImJmZTI2MjM0LTMzNjktNTdhYS05ZjhhLWU2ZWVhY2M0YTgzYiIsImlhdCI6MTU4MzE1Nzg5OCwidGVuYW50IjoiZGlrdSJ9.Mk7u4KaCywSuYtBgCT44oGcVC0C8jUMY9KjsUnug48I"); public static final Header EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10 = new Header(OKAPI_HEADER_TENANT, "test_diku_limit_10"); @@ -85,4 +89,5 @@ private TestConstants() {} public static final String PIECE_PATH = BASE_MOCK_DATA_PATH + "pieces/"; public static final String TILES_PATH = BASE_MOCK_DATA_PATH + "titles/"; public static final String ID_FOR_TEMPLATE_NAME_ALREADY_EXISTS = "cd0619fb-a628-4d90-be41-df8943e97768"; + public static final String ROUTING_LIST_ID = "eee951de-ea49-400a-96e8-705ae5a1e1e8"; } diff --git a/src/test/java/org/folio/rest/impl/MockServer.java b/src/test/java/org/folio/rest/impl/MockServer.java index 58626f43a..dc4c9220b 100644 --- a/src/test/java/org/folio/rest/impl/MockServer.java +++ b/src/test/java/org/folio/rest/impl/MockServer.java @@ -242,6 +242,8 @@ 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 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"; private static final String ORDER_TEMPLATES_MOCK_DATA_PATH = BASE_MOCK_DATA_PATH + "orderTemplates/"; diff --git a/src/test/java/org/folio/rest/impl/RoutingListsApiTest.java b/src/test/java/org/folio/rest/impl/RoutingListsApiTest.java new file mode 100644 index 000000000..401d0eb9e --- /dev/null +++ b/src/test/java/org/folio/rest/impl/RoutingListsApiTest.java @@ -0,0 +1,116 @@ +package org.folio.rest.impl; + +import static io.vertx.core.Future.succeededFuture; +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.folio.RestTestUtils.prepareHeaders; +import static org.folio.RestTestUtils.verifyGet; +import static org.folio.TestConfig.X_OKAPI_URL; +import static org.folio.TestConfig.autowireDependencies; +import static org.folio.TestConfig.clearServiceInteractions; +import static org.folio.TestConfig.getFirstContextFromVertx; +import static org.folio.TestConfig.getVertx; +import static org.folio.TestConfig.initSpringContext; +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.ROUTING_LIST_ID; +import static org.folio.TestConstants.X_OKAPI_TOKEN; +import static org.folio.TestConstants.X_OKAPI_USER_ID; +import static org.folio.rest.RestConstants.OKAPI_URL; +import static org.folio.rest.impl.PurchaseOrdersApiTest.X_OKAPI_TENANT; +import static org.mockito.ArgumentMatchers.any; +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.HashMap; +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.rest.core.models.RequestContext; +import org.folio.service.RoutingListService; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; + +public class RoutingListsApiTest { + + private static final Logger logger = LogManager.getLogger(); + private static final String TEMPLATE_PROCESSING_REQUEST_ENDPOINT = "orders/routing-lists/" + ROUTING_LIST_ID + "/template"; + private static boolean runningOnOwn; + @Autowired + private RoutingListService routingListService; + private RequestContext requestContext; + private Context ctxMock; + private Map okapiHeadersMock; + private AutoCloseable mockitoMocks; + + + @BeforeAll + static void before() throws InterruptedException, ExecutionException, TimeoutException { + if (isVerticleNotDeployed()) { + ApiTestSuite.before(); + runningOnOwn = true; + } + initSpringContext(RoutingListsApiTest.ContextConfiguration.class); + } + + + @BeforeEach + void beforeEach() { + mockitoMocks = MockitoAnnotations.openMocks(this); + autowireDependencies(this); + ctxMock = getFirstContextFromVertx(getVertx()); + okapiHeadersMock = new HashMap<>(); + okapiHeadersMock.put(OKAPI_URL, "http://localhost:" + mockPort); + okapiHeadersMock.put(X_OKAPI_TOKEN.getName(), X_OKAPI_TOKEN.getValue()); + 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); + } + + @AfterEach + void afterEach() throws Exception { + mockitoMocks.close(); + clearServiceInteractions(); + } + + @AfterAll + static void after() { + if (runningOnOwn) { + ApiTestSuite.after(); + } + } + + @Test + void testProcessTemplateRequest() { + logger.info("=== Test Execute template processing request ==="); + + doReturn(succeededFuture(new JsonObject())).when(routingListService).processTemplateRequest(eq(ROUTING_LIST_ID), any(RequestContext.class)); + + verifyGet(TEMPLATE_PROCESSING_REQUEST_ENDPOINT, prepareHeaders(X_OKAPI_URL, EMPTY_CONFIG_X_OKAPI_TENANT), + APPLICATION_JSON, 200); + + verify(routingListService, times(1)).processTemplateRequest(eq(ROUTING_LIST_ID), any(RequestContext.class)); + } + + static class ContextConfiguration { + @Bean + public RoutingListService routingListService() { + return mock(RoutingListService.class); + } + } +} diff --git a/src/test/java/org/folio/rest/impl/TitlesApiTest.java b/src/test/java/org/folio/rest/impl/TitlesApiTest.java index d5b7004b7..73dc5cf8d 100644 --- a/src/test/java/org/folio/rest/impl/TitlesApiTest.java +++ b/src/test/java/org/folio/rest/impl/TitlesApiTest.java @@ -10,6 +10,7 @@ import static org.folio.TestConfig.clearServiceInteractions; import static org.folio.TestConfig.initSpringContext; import static org.folio.TestConfig.isVerticleNotDeployed; +import static org.folio.TestConstants.ALL_DESIRED_ACQ_PERMISSIONS_HEADER; 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; @@ -20,7 +21,6 @@ import static org.folio.TestUtils.getMinimalContentCompositePoLine; import static org.folio.TestUtils.getMockAsJson; import static org.folio.TestUtils.getMockData; -import static org.folio.orders.utils.PermissionsUtil.OKAPI_HEADER_PERMISSIONS; import static org.folio.orders.utils.ResourcePathResolver.PO_LINES_STORAGE; import static org.folio.rest.core.exceptions.ErrorCodes.*; import static org.folio.rest.impl.MockServer.TITLES_MOCK_DATA_PATH; @@ -38,14 +38,12 @@ import java.util.concurrent.TimeoutException; import io.restassured.http.Header; -import io.vertx.core.json.JsonArray; 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.config.ApplicationConfig; -import org.folio.orders.utils.AcqDesiredPermissions; import org.folio.rest.acq.model.Title; import org.folio.rest.jaxrs.model.CompositePoLine; import org.folio.rest.jaxrs.model.Details; @@ -72,7 +70,6 @@ public class TitlesApiTest { public static final String SAMPLE_TITLE_ID = "9a665b22-9fe5-4c95-b4ee-837a5433c95d"; private final JsonObject titleJsonReqData = getMockAsJson(TITLES_MOCK_DATA_PATH + "title.json"); private final JsonObject packageTitleJsonReqData = getMockAsJson(TITLES_MOCK_DATA_PATH + "package_title.json"); - public static final Header ALL_DESIRED_PERMISSIONS_HEADER = new Header(OKAPI_HEADER_PERMISSIONS, new JsonArray(AcqDesiredPermissions.getValuesExceptBypass()).encode()); private static boolean runningOnOwn; @@ -121,7 +118,7 @@ void testPostTitle() { assertThat(postTitleRq.getId(), nullValue()); Title postTitleRs = verifyPostResponse(TITLES_ENDPOINT, JsonObject.mapFrom(postTitleRq).encode(), - prepareHeaders(EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10, X_OKAPI_USER_ID_WITH_ACQ_UNITS, ALL_DESIRED_PERMISSIONS_HEADER), APPLICATION_JSON, HttpStatus.HTTP_CREATED.toInt()).as(Title.class); + prepareHeaders(EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10, X_OKAPI_USER_ID_WITH_ACQ_UNITS, ALL_DESIRED_ACQ_PERMISSIONS_HEADER), APPLICATION_JSON, HttpStatus.HTTP_CREATED.toInt()).as(Title.class); // Title id not null assertThat(postTitleRs.getId(), Matchers.notNullValue()); @@ -129,12 +126,12 @@ void testPostTitle() { // Negative cases // Unable to create title test int status400 = HttpStatus.HTTP_BAD_REQUEST.toInt(); - verifyPostResponse(TITLES_ENDPOINT, JsonObject.mapFrom(postTitleRq).encode(), prepareHeaders(EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10, X_OKAPI_USER_ID_WITH_ACQ_UNITS, ALL_DESIRED_PERMISSIONS_HEADER, + verifyPostResponse(TITLES_ENDPOINT, JsonObject.mapFrom(postTitleRq).encode(), prepareHeaders(EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10, X_OKAPI_USER_ID_WITH_ACQ_UNITS, ALL_DESIRED_ACQ_PERMISSIONS_HEADER, new Header(X_ECHO_STATUS, String.valueOf(status400))), APPLICATION_JSON, status400); // Internal error on mod-orders-storage test int status500 = HttpStatus.HTTP_INTERNAL_SERVER_ERROR.toInt(); - verifyPostResponse(TITLES_ENDPOINT, JsonObject.mapFrom(postTitleRq).encode(), prepareHeaders(EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10, X_OKAPI_USER_ID_WITH_ACQ_UNITS, ALL_DESIRED_PERMISSIONS_HEADER, + verifyPostResponse(TITLES_ENDPOINT, JsonObject.mapFrom(postTitleRq).encode(), prepareHeaders(EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10, X_OKAPI_USER_ID_WITH_ACQ_UNITS, ALL_DESIRED_ACQ_PERMISSIONS_HEADER, new Header(X_ECHO_STATUS, String.valueOf(status500))), APPLICATION_JSON, status500); } @@ -163,7 +160,7 @@ void postTitleWithInvalidClaimingConfig() throws Exception { String reqData = getMockData(TITLES_MOCK_DATA_PATH + "title_invalid_claiming_config.json"); - List errors = verifyPostResponse(TITLES_ENDPOINT, reqData, prepareHeaders(EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10, X_OKAPI_USER_ID, ALL_DESIRED_PERMISSIONS_HEADER), APPLICATION_JSON, 422) + List errors = verifyPostResponse(TITLES_ENDPOINT, reqData, prepareHeaders(EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10, X_OKAPI_USER_ID, ALL_DESIRED_ACQ_PERMISSIONS_HEADER), APPLICATION_JSON, 422) .as(Errors.class) .getErrors(); @@ -192,7 +189,7 @@ void titleShouldBePopulatedFromPackagePoLine() { addMockEntry(PO_LINES_STORAGE, JsonObject.mapFrom(packagePoLine)); Title titleWithPackagePoLineRS = verifyPostResponse(TITLES_ENDPOINT, JsonObject.mapFrom(titleWithPackagePoLineRQ).encode(), - prepareHeaders(EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10, X_OKAPI_USER_ID, ALL_DESIRED_PERMISSIONS_HEADER), APPLICATION_JSON, HttpStatus.HTTP_CREATED.toInt()).as(Title.class); + prepareHeaders(EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10, X_OKAPI_USER_ID, ALL_DESIRED_ACQ_PERMISSIONS_HEADER), APPLICATION_JSON, HttpStatus.HTTP_CREATED.toInt()).as(Title.class); assertEquals(titleWithPackagePoLineRS.getPackageName(), packageTitleName); assertNotNull(titleWithPackagePoLineRS.getExpectedReceiptDate()); @@ -231,7 +228,7 @@ void testPutTitlesByIdTest() { .withAcqUnitIds(List.of(ACQ_UNIT_ID)); verifyPut(String.format(TITLES_ID_PATH, SAMPLE_TITLE_ID), JsonObject.mapFrom(reqData).encode(), - prepareHeaders(EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10, ALL_DESIRED_PERMISSIONS_HEADER), "", 204); + prepareHeaders(EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10, ALL_DESIRED_ACQ_PERMISSIONS_HEADER), "", 204); } @Test diff --git a/src/test/java/org/folio/service/RoutingListServiceTest.java b/src/test/java/org/folio/service/RoutingListServiceTest.java new file mode 100644 index 000000000..fe9651a93 --- /dev/null +++ b/src/test/java/org/folio/service/RoutingListServiceTest.java @@ -0,0 +1,102 @@ +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/UserServiceTest.java b/src/test/java/org/folio/service/UserServiceTest.java new file mode 100644 index 000000000..33e0ce7f3 --- /dev/null +++ b/src/test/java/org/folio/service/UserServiceTest.java @@ -0,0 +1,65 @@ +package org.folio.service; + +import static io.vertx.core.Future.succeededFuture; +import static org.folio.TestUtils.getMockData; +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.List; + +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.core.RestClient; +import org.folio.rest.core.models.RequestContext; +import org.folio.rest.core.models.RequestEntry; +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 UserServiceTest { + + @Mock + private RestClient restClient; + @InjectMocks + 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 getUsersByIds(VertxTestContext vertxTestContext) throws IOException { + var users = new JsonObject(getMockData(USERS_MOCK_DATA_PATH + "user_collection.json")).mapTo(UserCollection.class); + List userIds = users.getUsers().stream().map(user -> String.valueOf(user.getId())).toList(); + doReturn(succeededFuture(users)).when(restClient).get(any(RequestEntry.class), eq(UserCollection.class), any()); + + Future future = userService.getUsersByIds(userIds, requestContextMock); + + vertxTestContext.assertComplete(future) + .onComplete(result -> { + assertTrue(result.succeeded()); + vertxTestContext.completeNow(); + }); + } +} diff --git a/src/test/resources/mockdata/routingLists/eee951de-ea49-400a-96e8-705ae5a1e1e8-expected-template-request.json b/src/test/resources/mockdata/routingLists/eee951de-ea49-400a-96e8-705ae5a1e1e8-expected-template-request.json new file mode 100644 index 000000000..4863165c3 --- /dev/null +++ b/src/test/resources/mockdata/routingLists/eee951de-ea49-400a-96e8-705ae5a1e1e8-expected-template-request.json @@ -0,0 +1,24 @@ +{ + "templateId": "9465105a-e8a1-470c-9817-142d33bc4fcd", + "lang": "en", + "outputFormat": "text/html", + "context": { + "routingList": { + "name": "Mars expedition", + "notes": "Future note", + "userIds": [] + }, + "users": [ + { + "lastName": "Denesik", + "firstName": "Maiya", + "routingAddress": "144 Law" + }, + { + "lastName": "Huels", + "firstName": "Lois", + "routingAddress": "123 Law" + } + ] + } +} diff --git a/src/test/resources/mockdata/routingLists/eee951de-ea49-400a-96e8-705ae5a1e1e8.json b/src/test/resources/mockdata/routingLists/eee951de-ea49-400a-96e8-705ae5a1e1e8.json new file mode 100644 index 000000000..358a9642f --- /dev/null +++ b/src/test/resources/mockdata/routingLists/eee951de-ea49-400a-96e8-705ae5a1e1e8.json @@ -0,0 +1,11 @@ +{ + "id": "eee951de-ea49-400a-96e8-705ae5a1e1e8", + "_version": 1, + "name": "Mars expedition", + "notes": "Future note", + "userIds": [ + "00bc2807-4d5b-4a27-a2b5-b7b1ba431cc4", + "011dc219-6b7f-4d93-ae7f-f512ed651493" + ], + "poLineId": "8343e5a0-fed8-11e8-8eb2-f2801f1b9fd1" +} diff --git a/src/test/resources/mockdata/users/user_collection.json b/src/test/resources/mockdata/users/user_collection.json new file mode 100644 index 000000000..5a0b7aa23 --- /dev/null +++ b/src/test/resources/mockdata/users/user_collection.json @@ -0,0 +1,108 @@ +{ + "users": [ + { + "username": "sallie", + "id": "00bc2807-4d5b-4a27-a2b5-b7b1ba431cc4", + "barcode": "133143370961512", + "active": false, + "type": "patron", + "patronGroup": "503a81cd-6c26-400f-b620-14c08943697c", + "departments": [], + "proxyFor": [], + "personal": { + "lastName": "Denesik", + "firstName": "Maiya", + "middleName": "Noel", + "email": "ahmad@kertzmann-bailey-and-brekke.io", + "phone": "759.693.8557", + "mobilePhone": "(557)093-7575", + "dateOfBirth": "2009-07-21T00:00:00.000+00:00", + "addresses": [ + { + "countryId": "US", + "addressLine1": "17691 Rodriguez Divide", + "city": "Birmingham", + "region": "CO", + "postalCode": "85748", + "addressTypeId": "93d3d88d-499b-45d0-9bc7-ac73c3a19880", + "primaryAddress": true + }, + { + "city": "", + "region": "", + "countryId": "US", + "postalCode": "", + "addressLine1": "144 Law", + "addressTypeId": "1c4b225f-f669-4e9b-afcd-ebc0e273a34e", + "primaryAddress": false + } + ], + "preferredContactTypeId": "002" + }, + "enrollmentDate": "2015-04-13T00:00:00.000+00:00", + "expirationDate": "2019-06-20T00:00:00.000+00:00", + "createdDate": "2024-01-28T16:01:21.650+00:00", + "updatedDate": "2024-01-28T16:01:21.650+00:00", + "metadata": { + "createdDate": "2024-01-28T16:01:21.601+00:00", + "createdByUserId": "631fdcc3-c7c6-48e6-b51c-8a727c5d3dd7", + "updatedDate": "2024-01-28T16:01:21.601+00:00", + "updatedByUserId": "631fdcc3-c7c6-48e6-b51c-8a727c5d3dd7" + } + }, + { + "id": "011dc219-6b7f-4d93-ae7f-f512ed651493", + "type": "patron", + "active": false, + "barcode": "897083256223023", + "metadata": { + "createdDate": "2024-01-28T15:51:44.463Z", + "updatedDate": "2024-01-28T15:51:44.463Z", + "createdByUserId": "631fdcc3-c7c6-48e6-b51c-8a727c5d3dd7", + "updatedByUserId": "631fdcc3-c7c6-48e6-b51c-8a727c5d3dd7" + }, + "personal": { + "email": "monserrat@donnelly-skiles.ge", + "phone": "(619)645-7533 x5934", + "firstName": "Lois", + "lastName": "Huels", + "addresses": [ + { + "city": "Marana", + "region": "NH", + "countryId": "US", + "postalCode": "02013-0332", + "addressLine1": "69175 Haley Skyway", + "addressTypeId": "93d3d88d-499b-45d0-9bc7-ac73c3a19880", + "primaryAddress": true + }, + { + "city": "", + "region": "", + "countryId": "US", + "postalCode": "", + "addressLine1": "123 Law", + "addressTypeId": "1c4b225f-f669-4e9b-afcd-ebc0e273a34e", + "primaryAddress": false + } + ], + "dateOfBirth": "1947-06-23T00:00:00.000+00:00", + "preferredContactTypeId": "004" + }, + "proxyFor": [], + "username": "elmer", + "createdDate": "2024-01-28T15:51:44.505+00:00", + "departments": [], + "patronGroup": "3684a786-6671-4268-8ed0-9db82ebca60b", + "updatedDate": "2024-01-28T15:51:44.505+00:00", + "enrollmentDate": "2018-05-06T00:00:00.000+00:00", + "expirationDate": "2019-09-02T00:00:00.000+00:00" + } + ], + "totalRecords": 2, + "resultInfo": { + "totalRecords": 2, + "facets": [], + "diagnostics": [] + } +}