From b5765aec945c58169afa11b44949d86192773f5f Mon Sep 17 00:00:00 2001 From: Roman_Chernetskyi Date: Tue, 18 Jun 2024 13:31:24 +0300 Subject: [PATCH] Response with error message --- pom.xml | 2 +- ramls/update_ownership_response.json | 24 +++ .../resources/UpdateOwnershipApi.java | 103 ++++++++--- .../folio/inventory/support/MoveApiUtil.java | 6 + .../HoldingsUpdateOwnershipApiTest.java | 165 ++++++++++-------- 5 files changed, 205 insertions(+), 95 deletions(-) create mode 100644 ramls/update_ownership_response.json diff --git a/pom.xml b/pom.xml index 0b3c44ae0..ac5ff7c81 100644 --- a/pom.xml +++ b/pom.xml @@ -447,7 +447,7 @@ ${basedir}/ramls/mappingMetadataDto.json ${basedir}/ramls/holdings_update_ownership.json ${basedir}/ramls/items_update_ownership.json - ${basedir}/ramls/move_response.json + ${basedir}/ramls/update_ownership_response.json org.folio true diff --git a/ramls/update_ownership_response.json b/ramls/update_ownership_response.json new file mode 100644 index 000000000..ab603298f --- /dev/null +++ b/ramls/update_ownership_response.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Holder for errors during holdings/item update ownership", + "type": "object", + "properties": { + "notUpdatedEntities": { + "description": "Items/holdings errors", + "type": "array", + "items": { + "type": "object", + "properties": { + "entityId": { + "$ref": "uuid.json" + }, + "errorMessage": { + "type": "string", + "description": "Error message" + } + } + } + } + }, + "additionalProperties": false +} diff --git a/src/main/java/org/folio/inventory/resources/UpdateOwnershipApi.java b/src/main/java/org/folio/inventory/resources/UpdateOwnershipApi.java index 67913d140..0bca384a7 100644 --- a/src/main/java/org/folio/inventory/resources/UpdateOwnershipApi.java +++ b/src/main/java/org/folio/inventory/resources/UpdateOwnershipApi.java @@ -5,14 +5,15 @@ import io.vertx.core.json.JsonObject; import io.vertx.ext.web.Router; import io.vertx.ext.web.RoutingContext; +import org.apache.commons.collections4.ListUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.folio.HoldingsRecord; import org.folio.HoldingsUpdateOwnership; +import org.folio.NotUpdatedEntity; import org.folio.inventory.common.Context; import org.folio.inventory.common.WebContext; import org.folio.inventory.consortium.services.ConsortiumService; -import org.folio.inventory.domain.AsynchronousCollection; import org.folio.inventory.domain.HoldingsRecordCollection; import org.folio.inventory.domain.items.Item; import org.folio.inventory.domain.items.ItemCollection; @@ -25,8 +26,10 @@ import org.folio.inventory.support.MoveApiUtil; import java.lang.invoke.MethodHandles; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import static org.folio.inventory.dataimport.handlers.matching.util.EventHandlingUtil.constructContext; import static org.folio.inventory.domain.instances.InstanceSource.CONSORTIUM_FOLIO; @@ -46,6 +49,8 @@ public class UpdateOwnershipApi extends AbstractInventoryResource { public static final String INSTANCE_NOT_SHARED = "Instance with id: %s is not shared"; public static final String INSTANCE_NOT_FOUND_AT_SOURCE_TENANT = "Instance with id: %s not found at source tenant, tenant: %s"; public static final String TENANT_NOT_IN_CONSORTIA = "%s tenant is not in consortia"; + public static final String INSTANCE_NOT_FOUND = "Instance with id: %s not found on tenant: %s"; + private final ConsortiumService consortiumService; public UpdateOwnershipApi(Storage storage, HttpClient client, ConsortiumService consortiumService) { @@ -74,6 +79,8 @@ private void processUpdateHoldingsOwnership(RoutingContext routingContext) { } var holdingsUpdateOwnership = updateOwnershipRequest.mapTo(HoldingsUpdateOwnership.class); + List notUpdatedEntities = new ArrayList<>(); + LOGGER.info("updateHoldingsOwnership:: Started updating ownership of holdings record: {}, to tenant: {}", holdingsUpdateOwnership.getHoldingsRecordIds(), holdingsUpdateOwnership.getTargetTenantId()); @@ -86,7 +93,7 @@ private void processUpdateHoldingsOwnership(RoutingContext routingContext) { if (instance != null) { if (instance.getSource().equals(CONSORTIUM_MARC.getValue()) || instance.getSource().equals(CONSORTIUM_FOLIO.getValue())) { Context targetTenantContext = constructContext(holdingsUpdateOwnership.getTargetTenantId(), context.getToken(), context.getOkapiLocation()); - return updateOwnershipOfHoldingsRecords(holdingsUpdateOwnership, routingContext, context, targetTenantContext); + return updateOwnershipOfHoldingsRecords(holdingsUpdateOwnership, notUpdatedEntities, routingContext, context, targetTenantContext); } else { String instanceNotSharedErrorMessage = String.format(INSTANCE_NOT_SHARED, holdingsUpdateOwnership.getToInstanceId()); LOGGER.warn("updateHoldingsOwnership:: " + instanceNotSharedErrorMessage); @@ -103,7 +110,7 @@ private void processUpdateHoldingsOwnership(RoutingContext routingContext) { LOGGER.warn("updateHoldingsOwnership:: " + notInConsortiaErrorMessage, context); return CompletableFuture.failedFuture(new BadRequestException(notInConsortiaErrorMessage)); }) - .thenAccept(updateHoldingsRecords -> respond(routingContext, holdingsUpdateOwnership.getHoldingsRecordIds(), updateHoldingsRecords)) + .thenAccept(updateHoldingsRecords -> respond(routingContext, notUpdatedEntities)) .exceptionally(throwable -> { LOGGER.warn("updateHoldingsOwnership:: Error during update ownership of holdings {}, to tenant: {}", holdingsUpdateOwnership.getHoldingsRecordIds(), holdingsUpdateOwnership.getTargetTenantId(), throwable); @@ -120,7 +127,8 @@ private void processUpdateItemsOwnership(RoutingContext routingContext) { // should be implemented in MODINV-955 } - private CompletableFuture> updateOwnershipOfHoldingsRecords(HoldingsUpdateOwnership holdingsUpdateOwnership, RoutingContext routingContext, + private CompletableFuture> updateOwnershipOfHoldingsRecords(HoldingsUpdateOwnership holdingsUpdateOwnership, + List notUpdatedEntities, RoutingContext routingContext, WebContext context, Context targetTenantContext) { try { CollectionResourceClient holdingsStorageClient = createHoldingsStorageClient(createHttpClient(client, routingContext, context), @@ -131,11 +139,17 @@ private CompletableFuture> updateOwnershipOfHoldingsRecords(Holding HoldingsRecordCollection targetTenantHoldingsRecordCollection = storage.getHoldingsRecordCollection(targetTenantContext); return holdingsRecordFetchClient.find(holdingsUpdateOwnership.getHoldingsRecordIds(), MoveApiUtil::fetchByIdCql) - .thenCompose(jsons -> createHoldings(jsons, holdingsUpdateOwnership.getToInstanceId(), targetTenantHoldingsRecordCollection)) - .thenApply(createdHoldings -> createdHoldings.stream().map(HoldingsRecord::getId).toList()) - .thenCompose(createdHoldingsIds -> - transferAttachedItems(createdHoldingsIds, routingContext, context, targetTenantContext) - .thenCompose(items -> deleteCollectionItems(createdHoldingsIds, sourceTenantHoldingsRecordCollection))); + .thenCompose(jsons -> { + processNotFoundedInstances(holdingsUpdateOwnership.getHoldingsRecordIds(), notUpdatedEntities, context, jsons); + return createHoldings(jsons, notUpdatedEntities, holdingsUpdateOwnership.getToInstanceId(), targetTenantHoldingsRecordCollection); + }) + .thenCompose(createdHoldings -> { + List createdHoldingsIds = createdHoldings.stream().map(HoldingsRecord::getId).toList(); + + return transferAttachedItems(createdHoldingsIds, notUpdatedEntities, routingContext, context, targetTenantContext) + .thenCompose(itemIds -> + deleteHoldings(getHoldingsToDelete(notUpdatedEntities, createdHoldings), notUpdatedEntities, sourceTenantHoldingsRecordCollection)); + }); } catch (Exception e) { LOGGER.warn("updateOwnershipOfHoldingsRecords:: Error during update ownership of holdings {}, to tenant: {}", holdingsUpdateOwnership.getHoldingsRecordIds(), holdingsUpdateOwnership.getTargetTenantId(), e); @@ -143,8 +157,8 @@ private CompletableFuture> updateOwnershipOfHoldingsRecords(Holding } } - private CompletableFuture> transferAttachedItems(List holdingsRecordIds, RoutingContext routingContext, - WebContext context, Context targetTenantContext) { + private CompletableFuture> transferAttachedItems(List holdingsRecordIds, List notUpdatedEntities, + RoutingContext routingContext, WebContext context, Context targetTenantContext) { try { CollectionResourceClient itemsStorageClient = createItemStorageClient(createHttpClient(client, routingContext, context), context); MultipleRecordsFetchClient itemsFetchClient = createItemsFetchClient(itemsStorageClient); @@ -153,9 +167,8 @@ private CompletableFuture> transferAttachedItems(List holdi ItemCollection targetTenantItemCollection = storage.getItemCollection(targetTenantContext); return itemsFetchClient.find(holdingsRecordIds, MoveApiUtil::fetchByHoldingsRecordIdCql) - .thenCompose(jsons -> createItems(jsons, targetTenantItemCollection)) - .thenApply(items -> items.stream().map(Item::getId).toList()) - .thenCompose(itemIds -> deleteCollectionItems(itemIds, sourceTenantItemCollection)); + .thenCompose(jsons -> createItems(jsons, notUpdatedEntities, targetTenantItemCollection)) + .thenCompose(items -> deleteItems(items, notUpdatedEntities, sourceTenantItemCollection)); } catch (Exception e) { LOGGER.warn("transferAttachedItems:: Error during transfer attached items for holdings {}, to tenant: {}", holdingsRecordIds, targetTenantContext.getTenantId(), e); @@ -163,13 +176,18 @@ private CompletableFuture> transferAttachedItems(List holdi } } - private CompletableFuture> createItems(List jsons, ItemCollection itemCollection) { + private CompletableFuture> createItems(List jsons, List notUpdatedEntities, ItemCollection itemCollection) { List itemRecordsToUpdateOwnership = jsons.stream() .map(ItemUtil::fromStoredItemRepresentation) .toList(); List> createFutures = itemRecordsToUpdateOwnership.stream() - .map(itemCollection::add) + .map(item -> + itemCollection.add(item) + .exceptionally(e -> { + notUpdatedEntities.add(new NotUpdatedEntity().withEntityId(item.getHoldingId()).withErrorMessage(e.getMessage())); + throw new CompletionException(e); + })) .toList(); return CompletableFuture.allOf(createFutures.toArray(new CompletableFuture[0])) @@ -179,7 +197,7 @@ private CompletableFuture> createItems(List jsons, ItemCo .toList()); } - private CompletableFuture> createHoldings(List jsons, String instanceId, + private CompletableFuture> createHoldings(List jsons, List notUpdatedEntities, String instanceId, HoldingsRecordCollection holdingsRecordCollection) { List holdingsRecordsToUpdateOwnership = jsons.stream() .peek(MoveApiUtil::removeExtraRedundantFields) @@ -188,7 +206,12 @@ private CompletableFuture> createHoldings(List .toList(); List> createFutures = holdingsRecordsToUpdateOwnership.stream() - .map(holdingsRecordCollection::add) + .map(holdingRecord -> + holdingsRecordCollection.add(holdingRecord) + .exceptionally(e -> { + notUpdatedEntities.add(new NotUpdatedEntity().withEntityId(holdingRecord.getId()).withErrorMessage(e.getMessage())); + throw new CompletionException(e); + })) .toList(); return CompletableFuture.allOf(createFutures.toArray(new CompletableFuture[0])) @@ -198,11 +221,35 @@ private CompletableFuture> createHoldings(List .toList()); } - private CompletableFuture> deleteCollectionItems(List collectionItemIds, AsynchronousCollection collection) { - List> deleteFutures = collectionItemIds.stream() - .map(collectionItemId -> { + private CompletableFuture> deleteHoldings(List holdingsRecords, List notUpdatedEntities, + HoldingsRecordCollection holdingsRecordCollection) { + List> deleteFutures = holdingsRecords.stream() + .map(holdingsRecord -> { + Promise promise = Promise.promise(); + holdingsRecordCollection.delete(holdingsRecord.getId(), success -> promise.complete(holdingsRecord.getId()), + failure -> { + notUpdatedEntities.add(new NotUpdatedEntity().withEntityId(holdingsRecord.getId()).withErrorMessage(failure.getReason())); + promise.fail(failure.getReason()); + }); + return promise.future().toCompletionStage().toCompletableFuture(); + }).toList(); + + return CompletableFuture.allOf(deleteFutures.toArray(new CompletableFuture[0])) + .handle((vVoid, throwable) -> deleteFutures.stream() + .filter(future -> !future.isCompletedExceptionally()) + .map(CompletableFuture::join) + .toList()); + } + + private CompletableFuture> deleteItems(List itemIds, List notUpdatedEntities, ItemCollection itemCollection) { + List> deleteFutures = itemIds.stream() + .map(item -> { Promise promise = Promise.promise(); - collection.delete(collectionItemId, success -> promise.complete(collectionItemId), failure -> promise.fail(failure.getReason())); + itemCollection.delete(item.getId(), success -> promise.complete(item.getId()), + failure -> { + notUpdatedEntities.add(new NotUpdatedEntity().withEntityId(item.getHoldingId()).withErrorMessage(failure.getReason())); + promise.fail(failure.getReason()); + }); return promise.future().toCompletionStage().toCompletableFuture(); }).toList(); @@ -212,4 +259,16 @@ private CompletableFuture> deleteCollectionItems(List colle .map(CompletableFuture::join) .toList()); } + + private void processNotFoundedInstances(List holdingsRecordIds, List notUpdatedEntities, WebContext context, List jsons) { + List foundedIds = jsons.stream().map(json -> json.getString("id")).toList(); + List notFoundedIds = ListUtils.subtract(holdingsRecordIds, foundedIds); + notFoundedIds.forEach(id -> + notUpdatedEntities.add(new NotUpdatedEntity().withEntityId(id).withErrorMessage(String.format(INSTANCE_NOT_FOUND, id, context.getTenantId())))); + } + + private List getHoldingsToDelete(List notUpdatedEntities, List createdHoldings) { + List notUpdatedHoldingsIds = notUpdatedEntities.stream().map(NotUpdatedEntity::getEntityId).toList(); + return createdHoldings.stream().filter(holdingsRecord -> !notUpdatedHoldingsIds.contains(holdingsRecord.getId())).toList(); + } } diff --git a/src/main/java/org/folio/inventory/support/MoveApiUtil.java b/src/main/java/org/folio/inventory/support/MoveApiUtil.java index f416567a8..af6051a21 100644 --- a/src/main/java/org/folio/inventory/support/MoveApiUtil.java +++ b/src/main/java/org/folio/inventory/support/MoveApiUtil.java @@ -6,6 +6,8 @@ import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.client.WebClient; import org.apache.commons.collections4.ListUtils; +import org.folio.NotUpdatedEntity; +import org.folio.UpdateOwnershipResponse; import org.folio.inventory.common.WebContext; import org.folio.inventory.storage.external.CollectionResourceClient; import org.folio.inventory.storage.external.CqlQuery; @@ -101,4 +103,8 @@ public static void respond(RoutingContext routingContext, List itemIdsTo } } + public static void respond(RoutingContext routingContext, List notUpdatedEntities) { + HttpServerResponse response = routingContext.response(); + success(response, JsonObject.mapFrom(new UpdateOwnershipResponse().withNotUpdatedEntities(notUpdatedEntities))); + } } diff --git a/src/test/java/api/holdings/HoldingsUpdateOwnershipApiTest.java b/src/test/java/api/holdings/HoldingsUpdateOwnershipApiTest.java index a9f5e0935..ab8f860c1 100644 --- a/src/test/java/api/holdings/HoldingsUpdateOwnershipApiTest.java +++ b/src/test/java/api/holdings/HoldingsUpdateOwnershipApiTest.java @@ -18,7 +18,6 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import support.fakes.EndpointFailureDescriptor; @@ -73,7 +72,7 @@ public void canUpdateHoldingsOwnershipToDifferentTenant() throws MalformedURLExc Response postHoldingsUpdateOwnershipResponse = updateHoldingsRecordsOwnership(holdingsRecordUpdateOwnershipRequestBody); assertThat(postHoldingsUpdateOwnershipResponse.getStatusCode(), is(200)); - assertThat(new JsonObject(postHoldingsUpdateOwnershipResponse.getBody()).getJsonArray("nonUpdatedIds").size(), is(0)); + assertThat(new JsonObject(postHoldingsUpdateOwnershipResponse.getBody()).getJsonArray("notUpdatedEntities").size(), is(0)); assertThat(postHoldingsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON)); Response sourceTenantHoldingsRecord1 = holdingsStorageClient.getById(createHoldingsRecord1); @@ -82,8 +81,8 @@ public void canUpdateHoldingsOwnershipToDifferentTenant() throws MalformedURLExc Assert.assertEquals(HttpStatus.SC_NOT_FOUND, sourceTenantHoldingsRecord1.getStatusCode()); Assert.assertEquals(instanceId.toString(), targetTenantHoldingsRecord1.getJson().getString(INSTANCE_ID)); - Response sourceTenantHoldingsRecord2 = holdingsStorageClient.getById(createHoldingsRecord1); - Response targetTenantHoldingsRecord2 = collegeHoldingsStorageClient.getById(createHoldingsRecord1); + Response sourceTenantHoldingsRecord2 = holdingsStorageClient.getById(createHoldingsRecord2); + Response targetTenantHoldingsRecord2 = collegeHoldingsStorageClient.getById(createHoldingsRecord2); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, sourceTenantHoldingsRecord2.getStatusCode()); Assert.assertEquals(instanceId.toString(), targetTenantHoldingsRecord2.getJson().getString(INSTANCE_ID)); @@ -118,7 +117,7 @@ public void canUpdateHoldingsOwnershipWithRelatedItemsToDifferentTenant() throws Response postHoldingsUpdateOwnershipResponse = updateHoldingsRecordsOwnership(holdingsRecordUpdateOwnershipRequestBody); assertThat(postHoldingsUpdateOwnershipResponse.getStatusCode(), is(200)); - assertThat(new JsonObject(postHoldingsUpdateOwnershipResponse.getBody()).getJsonArray("nonUpdatedIds").size(), is(0)); + assertThat(new JsonObject(postHoldingsUpdateOwnershipResponse.getBody()).getJsonArray("notUpdatedEntities").size(), is(0)); assertThat(postHoldingsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON)); // Verify Holdings ownership updated @@ -128,8 +127,8 @@ public void canUpdateHoldingsOwnershipWithRelatedItemsToDifferentTenant() throws Assert.assertEquals(HttpStatus.SC_NOT_FOUND, sourceTenantHoldingsRecord1.getStatusCode()); Assert.assertEquals(instanceId.toString(), targetTenantHoldingsRecord1.getJson().getString(INSTANCE_ID)); - Response sourceTenantHoldingsRecord2 = holdingsStorageClient.getById(createHoldingsRecord1); - Response targetTenantHoldingsRecord2 = collegeHoldingsStorageClient.getById(createHoldingsRecord1); + Response sourceTenantHoldingsRecord2 = holdingsStorageClient.getById(createHoldingsRecord2); + Response targetTenantHoldingsRecord2 = collegeHoldingsStorageClient.getById(createHoldingsRecord2); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, sourceTenantHoldingsRecord2.getStatusCode()); Assert.assertEquals(instanceId.toString(), targetTenantHoldingsRecord2.getJson().getString(INSTANCE_ID)); @@ -157,7 +156,6 @@ public void canUpdateHoldingsOwnershipIfErrorUpdatingRelatedItemsToDifferentTena InstanceApiClient.createInstance(consortiumOkapiClient, instance.put("source", FOLIO.getValue())); final UUID createHoldingsRecord1 = createHoldingForInstance(instanceId); - final UUID createHoldingsRecord2 = createHoldingForInstance(instanceId); final var firstItem = itemsClient.create( new ItemRequestBuilder() @@ -165,12 +163,6 @@ public void canUpdateHoldingsOwnershipIfErrorUpdatingRelatedItemsToDifferentTena .withBarcode("645398607547") .withStatus(ItemStatusName.AVAILABLE.value())); - final var secondItem = itemsClient.create( - new ItemRequestBuilder() - .forHolding(createHoldingsRecord2) - .withBarcode("645398607546") - .withStatus(ItemStatusName.AVAILABLE.value())); - final JsonObject expectedErrorResponse = new JsonObject().put("message", "Server error"); collegeItemsClient.emulateFailure( new EndpointFailureDescriptor() @@ -189,19 +181,21 @@ public void canUpdateHoldingsOwnershipIfErrorUpdatingRelatedItemsToDifferentTena .setMethod(HttpMethod.DELETE.name())); JsonObject holdingsRecordUpdateOwnershipRequestBody = new HoldingsRecordUpdateOwnershipRequestBuilder(instanceId, - new JsonArray(List.of(createHoldingsRecord1.toString(), createHoldingsRecord2.toString())), ApiTestSuite.COLLEGE_TENANT_ID).create(); + new JsonArray(List.of(createHoldingsRecord1.toString())), ApiTestSuite.COLLEGE_TENANT_ID).create(); Response postHoldingsUpdateOwnershipResponse = updateHoldingsRecordsOwnership(holdingsRecordUpdateOwnershipRequestBody); + collegeItemsClient.disableFailureEmulation(); + holdingsStorageClient.disableFailureEmulation(); + assertThat(postHoldingsUpdateOwnershipResponse.getStatusCode(), is(200)); - List nonUpdatedIdsIds = postHoldingsUpdateOwnershipResponse.getJson() - .getJsonArray("nonUpdatedIds") - .getList(); + JsonArray notUpdatedEntitiesIds = postHoldingsUpdateOwnershipResponse.getJson() + .getJsonArray("notUpdatedEntities"); - assertThat(nonUpdatedIdsIds.size(), is(2)); - assertThat(nonUpdatedIdsIds.get(0), equalTo(createHoldingsRecord1.toString())); - assertThat(nonUpdatedIdsIds.get(1), equalTo(createHoldingsRecord2.toString())); + assertThat(notUpdatedEntitiesIds.size(), is(1)); + assertThat(notUpdatedEntitiesIds.getJsonObject(0).getString("entityId"), equalTo(createHoldingsRecord1.toString())); + assertThat(notUpdatedEntitiesIds.getJsonObject(0).getString("errorMessage"), containsString(expectedErrorResponse.toString())); assertThat(postHoldingsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON)); @@ -212,27 +206,70 @@ public void canUpdateHoldingsOwnershipIfErrorUpdatingRelatedItemsToDifferentTena Assert.assertEquals(instanceId.toString(), sourceTenantHoldingsRecord1.getJson().getString(INSTANCE_ID)); Assert.assertEquals(instanceId.toString(), targetTenantHoldingsRecord1.getJson().getString(INSTANCE_ID)); - Response sourceTenantHoldingsRecord2 = holdingsStorageClient.getById(createHoldingsRecord1); - Response targetTenantHoldingsRecord2 = collegeHoldingsStorageClient.getById(createHoldingsRecord1); - - Assert.assertEquals(instanceId.toString(), sourceTenantHoldingsRecord2.getJson().getString(INSTANCE_ID)); - Assert.assertEquals(instanceId.toString(), targetTenantHoldingsRecord2.getJson().getString(INSTANCE_ID)); - - // Verify related Items ownership updated + // Verify related Item ownership not updated Response sourceTenantItem1 = itemsClient.getById(firstItem.getId()); Response targetTenantItem1 = collegeItemsClient.getById(firstItem.getId()); assertThat(HttpStatus.SC_NOT_FOUND, is(targetTenantItem1.getStatusCode())); assertThat(sourceTenantItem1.getJson().getString(HOLDINGS_RECORD_ID), is(createHoldingsRecord1.toString())); + } - Response sourceTenantItem2 = itemsClient.getById(secondItem.getId()); - Response targetTenantItem2 = collegeItemsClient.getById(secondItem.getId()); + @Test + public void canUpdateHoldingsOwnershipIfErrorDeletingRelatedItemsToDifferentTenant() throws MalformedURLException, ExecutionException, InterruptedException, TimeoutException { + UUID instanceId = UUID.randomUUID(); + JsonObject instance = smallAngryPlanet(instanceId); + + InstanceApiClient.createInstance(okapiClient, instance.put("source", CONSORTIUM_FOLIO.getValue())); + InstanceApiClient.createInstance(consortiumOkapiClient, instance.put("source", FOLIO.getValue())); + + final UUID createHoldingsRecord1 = createHoldingForInstance(instanceId); + + final var firstItem = itemsClient.create( + new ItemRequestBuilder() + .forHolding(createHoldingsRecord1) + .withBarcode("645398607547") + .withStatus(ItemStatusName.AVAILABLE.value())); + + final JsonObject expectedErrorResponse = new JsonObject().put("message", "Server error"); + collegeItemsClient.emulateFailure( + new EndpointFailureDescriptor() + .setFailureExpireDate(DateTime.now().plusSeconds(2).toDate()) + .setStatusCode(500) + .setContentType("application/json") + .setBody(expectedErrorResponse.toString()) + .setMethod(HttpMethod.DELETE.name())); + + JsonObject holdingsRecordUpdateOwnershipRequestBody = new HoldingsRecordUpdateOwnershipRequestBuilder(instanceId, + new JsonArray(List.of(createHoldingsRecord1.toString())), ApiTestSuite.COLLEGE_TENANT_ID).create(); - assertThat(HttpStatus.SC_NOT_FOUND, is(targetTenantItem2.getStatusCode())); - assertThat(sourceTenantItem2.getJson().getString(HOLDINGS_RECORD_ID), is(createHoldingsRecord2.toString())); + Response postHoldingsUpdateOwnershipResponse = updateHoldingsRecordsOwnership(holdingsRecordUpdateOwnershipRequestBody); collegeItemsClient.disableFailureEmulation(); - holdingsStorageClient.disableFailureEmulation(); + + assertThat(postHoldingsUpdateOwnershipResponse.getStatusCode(), is(200)); + + JsonArray notUpdatedEntitiesIds = postHoldingsUpdateOwnershipResponse.getJson() + .getJsonArray("notUpdatedEntities"); + + assertThat(notUpdatedEntitiesIds.size(), is(1)); + assertThat(notUpdatedEntitiesIds.getJsonObject(0).getString("entityId"), equalTo(createHoldingsRecord1.toString())); + assertThat(notUpdatedEntitiesIds.getJsonObject(0).getString("errorMessage"), containsString(expectedErrorResponse.toString())); + + assertThat(postHoldingsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON)); + + // Verify Holdings ownership updated + Response sourceTenantHoldingsRecord1 = holdingsStorageClient.getById(createHoldingsRecord1); + Response targetTenantHoldingsRecord1 = collegeHoldingsStorageClient.getById(createHoldingsRecord1); + + Assert.assertEquals(instanceId.toString(), sourceTenantHoldingsRecord1.getJson().getString(INSTANCE_ID)); + Assert.assertEquals(instanceId.toString(), targetTenantHoldingsRecord1.getJson().getString(INSTANCE_ID)); + + // Verify related Item ownership not updated + Response sourceTenantItem1 = itemsClient.getById(firstItem.getId()); + Response targetTenantItem1 = collegeItemsClient.getById(firstItem.getId()); + + assertThat(targetTenantItem1.getJson().getString(HOLDINGS_RECORD_ID), is(createHoldingsRecord1.toString())); + assertThat(sourceTenantItem1.getJson().getString(HOLDINGS_RECORD_ID), is(createHoldingsRecord1.toString())); } @Test @@ -256,12 +293,12 @@ public void shouldReportErrorsWhenOnlySomeRequestedHoldingsRecordsCouldNotBeUpda assertThat(postHoldingsUpdateOwnershipResponse.getStatusCode(), is(200)); assertThat(postHoldingsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON)); - List notFoundIds = postHoldingsUpdateOwnershipResponse.getJson() - .getJsonArray("nonUpdatedIds") - .getList(); + JsonArray notFoundIds = postHoldingsUpdateOwnershipResponse.getJson() + .getJsonArray("notUpdatedEntities"); assertThat(notFoundIds.size(), is(1)); - assertThat(notFoundIds.get(0), equalTo(createHoldingsRecord2.toString())); + assertThat(notFoundIds.getJsonObject(0).getString("entityId"), equalTo(createHoldingsRecord2.toString())); + assertThat(notFoundIds.getJsonObject(0).getString("errorMessage"), containsString("not found on tenant")); Response sourceTenantHoldingsRecord1 = holdingsStorageClient.getById(createHoldingsRecord1); Response targetTenantHoldingsRecord1 = collegeHoldingsStorageClient.getById(createHoldingsRecord1); @@ -407,7 +444,6 @@ public void cannotUpdateHoldingsRecordOwnershipDueToHoldingsRecordCreateError() InstanceApiClient.createInstance(consortiumOkapiClient, instance.put("source", FOLIO.getValue())); final UUID createHoldingsRecord1 = createHoldingForInstance(instanceId); - final UUID createHoldingsRecord2 = createHoldingForInstance(instanceId); final JsonObject expectedErrorResponse = new JsonObject().put("message", "Server error"); collegeHoldingsStorageClient.emulateFailure( @@ -419,17 +455,18 @@ public void cannotUpdateHoldingsRecordOwnershipDueToHoldingsRecordCreateError() .setMethod(HttpMethod.POST.name())); JsonObject holdingsRecordUpdateOwnershipRequestBody = new HoldingsRecordUpdateOwnershipRequestBuilder(instanceId, - new JsonArray(List.of(createHoldingsRecord1.toString(), createHoldingsRecord2.toString())), ApiTestSuite.COLLEGE_TENANT_ID).create(); + new JsonArray(List.of(createHoldingsRecord1.toString())), ApiTestSuite.COLLEGE_TENANT_ID).create(); Response postHoldingsUpdateOwnershipResponse = updateHoldingsRecordsOwnership(holdingsRecordUpdateOwnershipRequestBody); - List nonUpdatedIdsIds = postHoldingsUpdateOwnershipResponse.getJson() - .getJsonArray("nonUpdatedIds") - .getList(); + collegeHoldingsStorageClient.disableFailureEmulation(); + + JsonArray notUpdatedEntitiesIds = postHoldingsUpdateOwnershipResponse.getJson() + .getJsonArray("notUpdatedEntities"); - assertThat(nonUpdatedIdsIds.size(), is(2)); - assertThat(nonUpdatedIdsIds.get(0), equalTo(createHoldingsRecord1.toString())); - assertThat(nonUpdatedIdsIds.get(1), equalTo(createHoldingsRecord2.toString())); + assertThat(notUpdatedEntitiesIds.size(), is(1)); + assertThat(notUpdatedEntitiesIds.getJsonObject(0).getString("entityId"), equalTo(createHoldingsRecord1.toString())); + assertThat(notUpdatedEntitiesIds.getJsonObject(0).getString("errorMessage"), containsString(expectedErrorResponse.toString())); assertThat(postHoldingsUpdateOwnershipResponse.getStatusCode(), is(200)); assertThat(postHoldingsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON)); @@ -439,14 +476,6 @@ public void cannotUpdateHoldingsRecordOwnershipDueToHoldingsRecordCreateError() Assert.assertEquals(instanceId.toString(), sourceTenantHoldingsRecord1.getJson().getString(INSTANCE_ID)); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, targetTenantHoldingsRecord1.getStatusCode()); - - Response sourceTenantHoldingsRecord2 = holdingsStorageClient.getById(createHoldingsRecord2); - Response targetTenantHoldingsRecord2 = collegeHoldingsStorageClient.getById(createHoldingsRecord2); - - Assert.assertEquals(instanceId.toString(), sourceTenantHoldingsRecord2.getJson().getString(INSTANCE_ID)); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, targetTenantHoldingsRecord2.getStatusCode()); - - collegeHoldingsStorageClient.disableFailureEmulation(); } @Test @@ -458,7 +487,6 @@ public void cannotUpdateHoldingsRecordOwnershipDueToHoldingsRecordDeleteError() InstanceApiClient.createInstance(consortiumOkapiClient, instance.put("source", FOLIO.getValue())); final UUID createHoldingsRecord1 = createHoldingForInstance(instanceId); - final UUID createHoldingsRecord2 = createHoldingForInstance(instanceId); final JsonObject expectedErrorResponse = new JsonObject().put("message", "Server error"); collegeHoldingsStorageClient.emulateFailure( @@ -470,17 +498,18 @@ public void cannotUpdateHoldingsRecordOwnershipDueToHoldingsRecordDeleteError() .setMethod(HttpMethod.DELETE.name())); JsonObject holdingsRecordUpdateOwnershipRequestBody = new HoldingsRecordUpdateOwnershipRequestBuilder(instanceId, - new JsonArray(List.of(createHoldingsRecord1.toString(), createHoldingsRecord2.toString())), ApiTestSuite.COLLEGE_TENANT_ID).create(); + new JsonArray(List.of(createHoldingsRecord1.toString())), ApiTestSuite.COLLEGE_TENANT_ID).create(); Response postHoldingsUpdateOwnershipResponse = updateHoldingsRecordsOwnership(holdingsRecordUpdateOwnershipRequestBody); - List nonUpdatedIdsIds = postHoldingsUpdateOwnershipResponse.getJson() - .getJsonArray("nonUpdatedIds") - .getList(); + collegeHoldingsStorageClient.disableFailureEmulation(); + + JsonArray notUpdatedEntitiesIds = postHoldingsUpdateOwnershipResponse.getJson() + .getJsonArray("notUpdatedEntities"); - assertThat(nonUpdatedIdsIds.size(), is(2)); - assertThat(nonUpdatedIdsIds.get(0), equalTo(createHoldingsRecord1.toString())); - assertThat(nonUpdatedIdsIds.get(1), equalTo(createHoldingsRecord2.toString())); + assertThat(notUpdatedEntitiesIds.size(), is(1)); + assertThat(notUpdatedEntitiesIds.getJsonObject(0).getString("entityId"), equalTo(createHoldingsRecord1.toString())); + assertThat(notUpdatedEntitiesIds.getJsonObject(0).getString("errorMessage"), containsString(expectedErrorResponse.toString())); assertThat(postHoldingsUpdateOwnershipResponse.getStatusCode(), is(200)); assertThat(postHoldingsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON)); @@ -490,14 +519,6 @@ public void cannotUpdateHoldingsRecordOwnershipDueToHoldingsRecordDeleteError() Assert.assertEquals(instanceId.toString(), sourceTenantHoldingsRecord1.getJson().getString(INSTANCE_ID)); Assert.assertEquals(instanceId.toString(), targetTenantHoldingsRecord1.getJson().getString(INSTANCE_ID)); - - Response sourceTenantHoldingsRecord2 = holdingsStorageClient.getById(createHoldingsRecord2); - Response targetTenantHoldingsRecord2 = collegeHoldingsStorageClient.getById(createHoldingsRecord2); - - Assert.assertEquals(instanceId.toString(), sourceTenantHoldingsRecord2.getJson().getString(INSTANCE_ID)); - Assert.assertEquals(instanceId.toString(), targetTenantHoldingsRecord2.getJson().getString(INSTANCE_ID)); - - collegeHoldingsStorageClient.disableFailureEmulation(); } @Test @@ -526,7 +547,7 @@ public void canUpdateHoldingsRecordOwnershipToDifferentInstanceWithExtraRedundan Response postHoldingsUpdateOwnershipResponse = updateHoldingsRecordsOwnership(holdingsRecordUpdateOwnershipRequestBody); assertThat(postHoldingsUpdateOwnershipResponse.getStatusCode(), is(200)); - assertThat(new JsonObject(postHoldingsUpdateOwnershipResponse.getBody()).getJsonArray("nonUpdatedIds").size(), is(0)); + assertThat(new JsonObject(postHoldingsUpdateOwnershipResponse.getBody()).getJsonArray("notUpdatedEntities").size(), is(0)); assertThat(postHoldingsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON)); Response sourceTenantHoldingsRecord1 = holdingsStorageClient.getById(createHoldingsRecord1); @@ -535,8 +556,8 @@ public void canUpdateHoldingsRecordOwnershipToDifferentInstanceWithExtraRedundan Assert.assertEquals(HttpStatus.SC_NOT_FOUND, sourceTenantHoldingsRecord1.getStatusCode()); Assert.assertEquals(instanceId.toString(), targetTenantHoldingsRecord1.getJson().getString(INSTANCE_ID)); - Response sourceTenantHoldingsRecord2 = holdingsStorageClient.getById(createHoldingsRecord1); - Response targetTenantHoldingsRecord2 = collegeHoldingsStorageClient.getById(createHoldingsRecord1); + Response sourceTenantHoldingsRecord2 = holdingsStorageClient.getById(createHoldingsRecord2); + Response targetTenantHoldingsRecord2 = collegeHoldingsStorageClient.getById(createHoldingsRecord2); Assert.assertEquals(HttpStatus.SC_NOT_FOUND, sourceTenantHoldingsRecord2.getStatusCode()); Assert.assertEquals(instanceId.toString(), targetTenantHoldingsRecord2.getJson().getString(INSTANCE_ID));