From 6d09dae49b39edfc1348661b56f236738f496e76 Mon Sep 17 00:00:00 2001 From: Saba-Zedginidze-EPAM <148070844+Saba-Zedginidze-EPAM@users.noreply.github.com> Date: Wed, 28 Aug 2024 19:59:02 +0400 Subject: [PATCH 1/2] [MODINVSTOR-1243] Implement endpoint to retrieve items from multiple tenants (#755) * [MODINVSTOR-1243] Implement endpoint to retrieve items from multiple tenants --- descriptors/ModuleDescriptor-template.json | 14 ++ pom.xml | 2 + ramls/inventory.raml | 13 ++ ramls/tenantItemPair.json | 18 +++ ramls/tenantItemPairCollection.json | 17 +++ .../folio/inventory/InventoryVerticle.java | 2 + .../inventory/resources/TenantItems.java | 142 ++++++++++++++++++ src/test/java/api/ApiTestSuite.java | 4 +- .../java/api/items/TenantItemApiTests.java | 107 +++++++++++++ src/test/java/api/support/ApiRoot.java | 6 + src/test/java/support/fakes/FakeOkapi.java | 9 ++ 11 files changed, 333 insertions(+), 1 deletion(-) create mode 100644 ramls/tenantItemPair.json create mode 100644 ramls/tenantItemPairCollection.json create mode 100644 src/main/java/org/folio/inventory/resources/TenantItems.java create mode 100644 src/test/java/api/items/TenantItemApiTests.java diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 4b65f2502..5bc3ee80b 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -365,6 +365,14 @@ "inventory-storage.instances.item.get" ] }, + { + "methods": ["POST"], + "pathPattern": "/inventory/tenant-items", + "permissionsRequired": ["inventory.tenant-items.collection.get"], + "modulePermissions": [ + "inventory-storage.items.collection.get" + ] + }, { "methods": ["GET"], "pathPattern": "/inventory/instances/{id}", @@ -681,6 +689,11 @@ "displayName": "Inventory - get item collection", "description": "Get item collection" }, + { + "permissionName": "inventory.tenant-items.collection.get", + "displayName": "Inventory - get item collection from multiple tenants", + "description": "Get item collection from multiple tenants" + }, { "permissionName": "inventory.items.collection.delete", "displayName": "Inventory - delete entire item collection", @@ -828,6 +841,7 @@ "description": "Entire set of permissions needed to use the inventory", "subPermissions": [ "inventory.items.collection.get", + "inventory.tenant-items.collection.get", "inventory.items.item.get", "inventory.items.item.post", "inventory.items.item.put", diff --git a/pom.xml b/pom.xml index 228b5b9f5..49b91ca8a 100644 --- a/pom.xml +++ b/pom.xml @@ -449,6 +449,8 @@ ${basedir}/ramls/items_update_ownership.json ${basedir}/ramls/update_ownership_response.json ${basedir}/ramls/instance-ingress-event.json + ${basedir}/ramls/tenantItemPair.json + ${basedir}/ramls/tenantItemPairCollection.json org.folio true diff --git a/ramls/inventory.raml b/ramls/inventory.raml index 97a7bda50..0b780b512 100644 --- a/ramls/inventory.raml +++ b/ramls/inventory.raml @@ -15,6 +15,7 @@ types: holdings: !include holdings-record.json instance: !include instance.json instances: !include instances.json + tenantItemPairCollection: !include tenantItemPairCollection.json traits: language: !include raml-util/traits/language.raml @@ -294,6 +295,18 @@ resourceTypes: Possible values of the 'relations' parameter are: 'onlyBoundWiths', 'onlyBoundWithsSkipDirectlyLinkedItem'", example: "holdingsRecordId==\"[UUID]\""} ] + /tenant-items: + displayName: Fetch items based on tenant IDs + post: + body: + application/json: + type: tenantItemPairCollection + responses: + 200: + description: "Fetched items based on tenant IDs" + body: + application/json: + type: items /holdings/{holdingsId}: put: description: Update Holdings by holdingsId diff --git a/ramls/tenantItemPair.json b/ramls/tenantItemPair.json new file mode 100644 index 000000000..0573be0e8 --- /dev/null +++ b/ramls/tenantItemPair.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Pair of item and tenant IDs", + "type": "object", + "properties": { + "tenantId": { + "type": "string", + "description": "Unique ID of the tenant where the item is located" + }, + "itemId": { + "type": "string", + "description": "Unique ID (UUID) of the item", + "$ref": "uuid.json" + } + }, + "additionalProperties": false, + "required": ["itemId", "tenantId"] +} diff --git a/ramls/tenantItemPairCollection.json b/ramls/tenantItemPairCollection.json new file mode 100644 index 000000000..390c7063a --- /dev/null +++ b/ramls/tenantItemPairCollection.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Collection of pairs of item and tenant IDs", + "type": "object", + "properties": { + "tenantItemPairs": { + "type": "array", + "description": "Pairs of tenantId and itemId", + "items": { + "type": "object", + "$ref": "tenantItemPair.json" + } + } + }, + "additionalProperties": false, + "required": ["tenantItemPairs"] +} diff --git a/src/main/java/org/folio/inventory/InventoryVerticle.java b/src/main/java/org/folio/inventory/InventoryVerticle.java index 80f2bdae9..1cc59b389 100644 --- a/src/main/java/org/folio/inventory/InventoryVerticle.java +++ b/src/main/java/org/folio/inventory/InventoryVerticle.java @@ -19,6 +19,7 @@ import org.folio.inventory.resources.ItemsByHoldingsRecordId; import org.folio.inventory.resources.MoveApi; import org.folio.inventory.resources.TenantApi; +import org.folio.inventory.resources.TenantItems; import org.folio.inventory.resources.UpdateOwnershipApi; import org.folio.inventory.storage.Storage; @@ -71,6 +72,7 @@ public void start(Promise started) { new InventoryConfigApi().register(router); new TenantApi().register(router); new UpdateOwnershipApi(storage, client, consortiumService).register(router); + new TenantItems(client).register(router); Handler> onHttpServerStart = result -> { if (result.succeeded()) { diff --git a/src/main/java/org/folio/inventory/resources/TenantItems.java b/src/main/java/org/folio/inventory/resources/TenantItems.java new file mode 100644 index 000000000..c6079b6f4 --- /dev/null +++ b/src/main/java/org/folio/inventory/resources/TenantItems.java @@ -0,0 +1,142 @@ +package org.folio.inventory.resources; + +import static java.lang.String.format; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.mapping; +import static java.util.stream.Collectors.toList; +import static org.folio.inventory.support.CqlHelper.multipleRecordsCqlQuery; + +import java.lang.invoke.MethodHandles; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +import org.apache.http.HttpStatus; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.folio.TenantItemPair; +import org.folio.TenantItemPairCollection; +import org.folio.inventory.common.WebContext; +import org.folio.inventory.storage.external.CollectionResourceClient; +import org.folio.inventory.support.JsonArrayHelper; +import org.folio.inventory.support.http.client.OkapiHttpClient; +import org.folio.inventory.support.http.client.Response; +import org.folio.inventory.support.http.server.JsonResponse; +import org.folio.inventory.support.http.server.ServerErrorResponse; + +import io.vertx.core.http.HttpClient; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.RoutingContext; +import io.vertx.ext.web.client.WebClient; +import io.vertx.ext.web.handler.BodyHandler; + +/** + * Resource that allows to get Inventory items from multiple tenants at once. + * User should have an affiliation in order to be able to retrieve items from the corresponding tenant. + */ +public class TenantItems { + + private static final Logger LOG = LogManager.getLogger(MethodHandles.lookup().lookupClass()); + + private static final String TENANT_ITEMS_PATH = "/inventory/tenant-items"; + public static final String ITEMS_FIELD = "items"; + public static final String TOTAL_RECORDS_FIELD = "totalRecords"; + public static final String TENANT_ID_FIELD = "tenantId"; + + private final HttpClient client; + + public TenantItems(HttpClient client) { + this.client = client; + } + + public void register(Router router) { + router.post(TENANT_ITEMS_PATH + "*").handler(BodyHandler.create()); + router.post(TENANT_ITEMS_PATH).handler(this::getItemsFromTenants); + } + + /** + * This API is meant to be used by UI to fetch different items from several + * tenants together within one call + * + */ + private void getItemsFromTenants(RoutingContext routingContext) { + var getItemsFutures = routingContext.body().asPojo(TenantItemPairCollection.class) + .getTenantItemPairs().stream() + .collect(groupingBy(TenantItemPair::getTenantId, mapping(TenantItemPair::getItemId, toList()))) + .entrySet().stream() + .map(tenantToItems -> getItemsWithTenantId(tenantToItems.getKey(), tenantToItems.getValue(), routingContext)) + .toList(); + + CompletableFuture.allOf(getItemsFutures.toArray(new CompletableFuture[0])) + .thenApply(v -> getItemsFutures.stream() + .map(CompletableFuture::join) + .flatMap(List::stream) + .toList()) + .thenApply(this::constructResponse) + .thenAccept(jsonObject -> JsonResponse.success(routingContext.response(), jsonObject)); + } + + private CompletableFuture> getItemsWithTenantId(String tenantId, List itemIds, RoutingContext routingContext) { + LOG.info("getItemsWithTenantId:: Fetching items - {} from tenant - {}", itemIds, tenantId); + var context = new WebContext(routingContext); + CollectionResourceClient itemsStorageClient; + try { + OkapiHttpClient okapiClient = createHttpClient(tenantId, context, routingContext); + itemsStorageClient = createItemsStorageClient(okapiClient, context); + } + catch (MalformedURLException e) { + invalidOkapiUrlResponse(routingContext, context); + return CompletableFuture.completedFuture(List.of()); + } + + var getByIdsQuery = multipleRecordsCqlQuery(itemIds); + var itemsFetched = new CompletableFuture(); + itemsStorageClient.getAll(getByIdsQuery, itemsFetched::complete); + + return itemsFetched.thenApplyAsync(response -> + getItemsWithTenantId(tenantId, response)); + } + + private List getItemsWithTenantId(String tenantId, Response response) { + if (response.getStatusCode() != HttpStatus.SC_OK || !response.hasBody()) { + return List.of(); + } + return JsonArrayHelper.toList(response.getJson(), ITEMS_FIELD).stream() + .map(item -> item.put(TENANT_ID_FIELD, tenantId)) + .toList(); + } + + private JsonObject constructResponse(List items) { + return JsonObject.of( + ITEMS_FIELD, JsonArray.of(items.toArray()), + TOTAL_RECORDS_FIELD, items.size() + ); + } + + private CollectionResourceClient createItemsStorageClient(OkapiHttpClient client, WebContext context) throws MalformedURLException { + return new CollectionResourceClient(client, new URL(context.getOkapiLocation() + "/item-storage/items")); + } + + private OkapiHttpClient createHttpClient(String tenantId, WebContext context, + RoutingContext routingContext) throws MalformedURLException { + return new OkapiHttpClient(WebClient.wrap(client), + URI.create(context.getOkapiLocation()).toURL(), + Optional.ofNullable(tenantId).orElse(context.getTenantId()), + context.getToken(), + context.getUserId(), + context.getRequestId(), + exception -> ServerErrorResponse.internalError(routingContext.response(), + format("Failed to contact storage module: %s", exception.toString()))); + } + + private void invalidOkapiUrlResponse(RoutingContext routingContext, WebContext context) { + ServerErrorResponse.internalError(routingContext.response(), + String.format("Invalid Okapi URL: %s", context.getOkapiLocation())); + } + +} diff --git a/src/test/java/api/ApiTestSuite.java b/src/test/java/api/ApiTestSuite.java index ccdf921b3..61f3a6b37 100644 --- a/src/test/java/api/ApiTestSuite.java +++ b/src/test/java/api/ApiTestSuite.java @@ -39,6 +39,7 @@ import api.items.MarkItemUnavailableApiTests; import api.items.MarkItemUnknownApiTests; import api.items.MarkItemWithdrawnApiTests; +import api.items.TenantItemApiTests; import api.support.ControlledVocabularyPreparation; import api.support.http.ResourceClient; import io.vertx.core.json.JsonArray; @@ -72,7 +73,8 @@ AdminApiTest.class, InventoryConfigApiTest.class, HoldingsUpdateOwnershipApiTest.class, - ItemUpdateOwnershipApiTest.class + ItemUpdateOwnershipApiTest.class, + TenantItemApiTests.class }) public class ApiTestSuite { public static final int INVENTORY_VERTICLE_TEST_PORT = 9603; diff --git a/src/test/java/api/items/TenantItemApiTests.java b/src/test/java/api/items/TenantItemApiTests.java new file mode 100644 index 000000000..4b0095bcd --- /dev/null +++ b/src/test/java/api/items/TenantItemApiTests.java @@ -0,0 +1,107 @@ +package api.items; + +import static api.ApiTestSuite.COLLEGE_TENANT_ID; +import static api.ApiTestSuite.CONSORTIA_TENANT_ID; +import static api.ApiTestSuite.getBookMaterialType; +import static api.ApiTestSuite.getCanCirculateLoanType; +import static api.support.InstanceSamples.smallAngryPlanet; +import static org.assertj.core.api.Assertions.assertThat; +import static org.folio.inventory.resources.TenantItems.ITEMS_FIELD; +import static org.folio.inventory.resources.TenantItems.TENANT_ID_FIELD; +import static org.folio.inventory.resources.TenantItems.TOTAL_RECORDS_FIELD; +import static org.folio.inventory.support.ItemUtil.ID; + +import java.net.MalformedURLException; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.folio.TenantItemPair; +import org.folio.TenantItemPairCollection; +import org.folio.inventory.support.JsonArrayHelper; +import org.folio.inventory.support.http.client.OkapiHttpClient; +import org.folio.inventory.support.http.client.Response; +import org.junit.Test; +import org.junit.runner.RunWith; + +import api.support.ApiRoot; +import api.support.ApiTests; +import api.support.InstanceApiClient; +import api.support.builders.HoldingRequestBuilder; +import api.support.http.ResourceClient; +import io.vertx.core.json.JsonObject; +import junitparams.JUnitParamsRunner; + +@RunWith(JUnitParamsRunner.class) +public class TenantItemApiTests extends ApiTests { + + @Test + public void testTenantItemsGetFromDifferentTenants() throws MalformedURLException, + ExecutionException, InterruptedException, TimeoutException { + + var consortiumItemId = createConsortiumInstanceHoldingItem(); + var collegeItemId = createCollegeInstanceHoldingItem(); + var consortiumItem = consortiumItemsClient.getById(consortiumItemId).getJson(); + var collegeItem = collegeItemsClient.getById(collegeItemId).getJson(); + + assertThat(consortiumItem.getString(ID)).matches(consortiumItemId.toString()); + assertThat(collegeItem.getString(ID)).matches(collegeItemId.toString()); + + var tenantItemPairCollection = constructTenantItemPairCollection(Map.of( + CONSORTIA_TENANT_ID, consortiumItem.getString(ID), + COLLEGE_TENANT_ID, collegeItem.getString(ID) + )); + + var response = okapiClient.post(ApiRoot.tenantItems(), JsonObject.mapFrom(tenantItemPairCollection)) + .toCompletableFuture().get(5, TimeUnit.SECONDS); + assertThat(response.getStatusCode()).isEqualTo(200); + + consortiumItem.put(TENANT_ID_FIELD, CONSORTIA_TENANT_ID); + collegeItem.put(TENANT_ID_FIELD, COLLEGE_TENANT_ID); + var items = extractItems(response, 2); + assertThat(items).contains(consortiumItem, collegeItem); + } + + private UUID createConsortiumInstanceHoldingItem() { + return createInstanceHoldingItem(consortiumItemsClient, consortiumHoldingsStorageClient, consortiumOkapiClient); + } + + private UUID createCollegeInstanceHoldingItem() { + return createInstanceHoldingItem(collegeItemsClient, collegeHoldingsStorageClient, collegeOkapiClient); + } + + private UUID createInstanceHoldingItem(ResourceClient itemsStorageClient, ResourceClient holdingsStorageClient, OkapiHttpClient okapiHttpClient) { + var instanceId = UUID.randomUUID(); + InstanceApiClient.createInstance(okapiHttpClient, smallAngryPlanet(instanceId)); + var holdingId = holdingsStorageClient.create(new HoldingRequestBuilder() + .forInstance(instanceId)).getId(); + var itemId = UUID.randomUUID(); + var newItemRequest = JsonObject.of( + "id", itemId.toString(), + "status", new JsonObject().put("name", "Available"), + "holdingsRecordId", holdingId, + "materialTypeId", getBookMaterialType(), + "permanentLoanTypeId", getCanCirculateLoanType()); + itemsStorageClient.create(newItemRequest); + return itemId; + } + + private List extractItems(Response itemsResponse, int expected) { + var itemsCollection = itemsResponse.getJson(); + var items = JsonArrayHelper.toList(itemsCollection.getJsonArray(ITEMS_FIELD)); + assertThat(items).hasSize(expected); + assertThat(itemsCollection.getInteger(TOTAL_RECORDS_FIELD)).isEqualTo(expected); + return items; + } + + private TenantItemPairCollection constructTenantItemPairCollection(Map tenantsToItemIds) { + return new TenantItemPairCollection() + .withTenantItemPairs(tenantsToItemIds.entrySet().stream() + .map(pair -> new TenantItemPair().withTenantId(pair.getKey()).withItemId(pair.getValue())) + .toList()); + } + +} diff --git a/src/test/java/api/support/ApiRoot.java b/src/test/java/api/support/ApiRoot.java index aa1373cc1..cac7fc07f 100644 --- a/src/test/java/api/support/ApiRoot.java +++ b/src/test/java/api/support/ApiRoot.java @@ -71,6 +71,12 @@ public static URL items(String query) return new URL(String.format("%s/items?%s", inventory(), query)); } + public static URL tenantItems() + throws MalformedURLException { + + return new URL(String.format("%s/tenant-items", inventory())); + } + public static String isbn() { return String.format("%s/isbn", ApiTestSuite.apiRoot()); } diff --git a/src/test/java/support/fakes/FakeOkapi.java b/src/test/java/support/fakes/FakeOkapi.java index ae75b0210..542a7b1bd 100644 --- a/src/test/java/support/fakes/FakeOkapi.java +++ b/src/test/java/support/fakes/FakeOkapi.java @@ -29,6 +29,7 @@ public void start(Promise startFuture) { registerFakeHoldingStorageModule(router); registerFakeAuthorityStorageModule(router); registerFakeItemsStorageModule(router); + registerFakeTenantItemsStorageModule(router); registerFakeMaterialTypesModule(router); registerFakeLoanTypesModule(router); registerFakeLocationsModule(router); @@ -171,6 +172,14 @@ private void registerFakeItemsStorageModule(Router router) { .create().register(router); } + private void registerFakeTenantItemsStorageModule(Router router) { + new FakeStorageModuleBuilder() + .withRecordName("tenantItem") + .withRootPath("/inventory/tenant-items") + .withCollectionPropertyName("items") + .create().register(router); + } + private void registerFakeMaterialTypesModule(Router router) { new FakeStorageModuleBuilder() From e450f3f6d64e585ba641039f3aba82b287e0da34 Mon Sep 17 00:00:00 2001 From: Saba-Zedginidze-EPAM <148070844+Saba-Zedginidze-EPAM@users.noreply.github.com> Date: Thu, 29 Aug 2024 18:41:29 +0400 Subject: [PATCH 2/2] [MODINV-1070] Modify TenantItems response schema (#759) * [MODINV-1070] Modify TenantItems response schema * [MODINV-1070] Fix failing test * [MODINV-1070] Remove unused import * [MODINV-1070] Remove unused import --- pom.xml | 1 + ramls/tenantItemResponse.json | 20 ++++++++++++ .../inventory/resources/TenantItems.java | 32 +++++++++---------- .../java/api/items/TenantItemApiTests.java | 10 +++--- 4 files changed, 42 insertions(+), 21 deletions(-) create mode 100644 ramls/tenantItemResponse.json diff --git a/pom.xml b/pom.xml index 49b91ca8a..705f4f467 100644 --- a/pom.xml +++ b/pom.xml @@ -451,6 +451,7 @@ ${basedir}/ramls/instance-ingress-event.json ${basedir}/ramls/tenantItemPair.json ${basedir}/ramls/tenantItemPairCollection.json + ${basedir}/ramls/tenantItemResponse.json org.folio true diff --git a/ramls/tenantItemResponse.json b/ramls/tenantItemResponse.json new file mode 100644 index 000000000..8bdf521e3 --- /dev/null +++ b/ramls/tenantItemResponse.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Collection of pairs of item and tenant IDs", + "type": "object", + "properties": { + "tenantItems": { + "type": "array", + "description": "Items with corresponding tenantIds", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "totalRecords": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": ["item", "totalRecords"] +} diff --git a/src/main/java/org/folio/inventory/resources/TenantItems.java b/src/main/java/org/folio/inventory/resources/TenantItems.java index c6079b6f4..c02ac570d 100644 --- a/src/main/java/org/folio/inventory/resources/TenantItems.java +++ b/src/main/java/org/folio/inventory/resources/TenantItems.java @@ -17,8 +17,10 @@ import org.apache.http.HttpStatus; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.folio.TenantItem; import org.folio.TenantItemPair; import org.folio.TenantItemPairCollection; +import org.folio.TenantItemResponse; import org.folio.inventory.common.WebContext; import org.folio.inventory.storage.external.CollectionResourceClient; import org.folio.inventory.support.JsonArrayHelper; @@ -28,7 +30,6 @@ import org.folio.inventory.support.http.server.ServerErrorResponse; import io.vertx.core.http.HttpClient; -import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.Router; import io.vertx.ext.web.RoutingContext; @@ -45,6 +46,7 @@ public class TenantItems { private static final String TENANT_ITEMS_PATH = "/inventory/tenant-items"; public static final String ITEMS_FIELD = "items"; + public static final String ITEM_FIELD = "item"; public static final String TOTAL_RECORDS_FIELD = "totalRecords"; public static final String TENANT_ID_FIELD = "tenantId"; @@ -77,11 +79,11 @@ private void getItemsFromTenants(RoutingContext routingContext) { .map(CompletableFuture::join) .flatMap(List::stream) .toList()) - .thenApply(this::constructResponse) - .thenAccept(jsonObject -> JsonResponse.success(routingContext.response(), jsonObject)); + .thenApply(items -> new TenantItemResponse().withTenantItems(items).withTotalRecords(items.size())) + .thenAccept(response -> JsonResponse.success(routingContext.response(), JsonObject.mapFrom(response))); } - private CompletableFuture> getItemsWithTenantId(String tenantId, List itemIds, RoutingContext routingContext) { + private CompletableFuture> getItemsWithTenantId(String tenantId, List itemIds, RoutingContext routingContext) { LOG.info("getItemsWithTenantId:: Fetching items - {} from tenant - {}", itemIds, tenantId); var context = new WebContext(routingContext); CollectionResourceClient itemsStorageClient; @@ -98,24 +100,20 @@ private CompletableFuture> getItemsWithTenantId(String tenantId var itemsFetched = new CompletableFuture(); itemsStorageClient.getAll(getByIdsQuery, itemsFetched::complete); - return itemsFetched.thenApplyAsync(response -> - getItemsWithTenantId(tenantId, response)); + return itemsFetched + .thenApply(this::getItems) + .thenApply(items -> items.stream() + .map(item -> new TenantItem() + .withAdditionalProperty(ITEM_FIELD, item) + .withAdditionalProperty(TENANT_ID_FIELD, tenantId)) + .toList()); } - private List getItemsWithTenantId(String tenantId, Response response) { + private List getItems(Response response) { if (response.getStatusCode() != HttpStatus.SC_OK || !response.hasBody()) { return List.of(); } - return JsonArrayHelper.toList(response.getJson(), ITEMS_FIELD).stream() - .map(item -> item.put(TENANT_ID_FIELD, tenantId)) - .toList(); - } - - private JsonObject constructResponse(List items) { - return JsonObject.of( - ITEMS_FIELD, JsonArray.of(items.toArray()), - TOTAL_RECORDS_FIELD, items.size() - ); + return JsonArrayHelper.toList(response.getJson(), ITEMS_FIELD); } private CollectionResourceClient createItemsStorageClient(OkapiHttpClient client, WebContext context) throws MalformedURLException { diff --git a/src/test/java/api/items/TenantItemApiTests.java b/src/test/java/api/items/TenantItemApiTests.java index 4b0095bcd..5471bda7a 100644 --- a/src/test/java/api/items/TenantItemApiTests.java +++ b/src/test/java/api/items/TenantItemApiTests.java @@ -6,7 +6,7 @@ import static api.ApiTestSuite.getCanCirculateLoanType; import static api.support.InstanceSamples.smallAngryPlanet; import static org.assertj.core.api.Assertions.assertThat; -import static org.folio.inventory.resources.TenantItems.ITEMS_FIELD; +import static org.folio.inventory.resources.TenantItems.ITEM_FIELD; import static org.folio.inventory.resources.TenantItems.TENANT_ID_FIELD; import static org.folio.inventory.resources.TenantItems.TOTAL_RECORDS_FIELD; import static org.folio.inventory.support.ItemUtil.ID; @@ -38,6 +38,8 @@ @RunWith(JUnitParamsRunner.class) public class TenantItemApiTests extends ApiTests { + private static final String TENANT_ITEMS_FIELD = "tenantItems"; + @Test public void testTenantItemsGetFromDifferentTenants() throws MalformedURLException, ExecutionException, InterruptedException, TimeoutException { @@ -59,8 +61,8 @@ public void testTenantItemsGetFromDifferentTenants() throws MalformedURLExceptio .toCompletableFuture().get(5, TimeUnit.SECONDS); assertThat(response.getStatusCode()).isEqualTo(200); - consortiumItem.put(TENANT_ID_FIELD, CONSORTIA_TENANT_ID); - collegeItem.put(TENANT_ID_FIELD, COLLEGE_TENANT_ID); + consortiumItem = JsonObject.of(ITEM_FIELD, consortiumItem, TENANT_ID_FIELD, CONSORTIA_TENANT_ID); + collegeItem = JsonObject.of(ITEM_FIELD, collegeItem, TENANT_ID_FIELD, COLLEGE_TENANT_ID); var items = extractItems(response, 2); assertThat(items).contains(consortiumItem, collegeItem); } @@ -91,7 +93,7 @@ private UUID createInstanceHoldingItem(ResourceClient itemsStorageClient, Resour private List extractItems(Response itemsResponse, int expected) { var itemsCollection = itemsResponse.getJson(); - var items = JsonArrayHelper.toList(itemsCollection.getJsonArray(ITEMS_FIELD)); + var items = JsonArrayHelper.toList(itemsCollection.getJsonArray(TENANT_ITEMS_FIELD)); assertThat(items).hasSize(expected); assertThat(itemsCollection.getInteger(TOTAL_RECORDS_FIELD)).isEqualTo(expected); return items;