diff --git a/pom.xml b/pom.xml
index 4e7b3104d..228b5b9f5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -445,6 +445,9 @@
${basedir}/ramls/holdings-record.json
${basedir}/ramls/holdings-records-source.json
${basedir}/ramls/mappingMetadataDto.json
+ ${basedir}/ramls/holdings_update_ownership.json
+ ${basedir}/ramls/items_update_ownership.json
+ ${basedir}/ramls/update_ownership_response.json
${basedir}/ramls/instance-ingress-event.json
org.folio
diff --git a/ramls/update_ownership_response.json b/ramls/update_ownership_response.json
new file mode 100644
index 000000000..9ef38d0ef
--- /dev/null
+++ b/ramls/update_ownership_response.json
@@ -0,0 +1,25 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Holder for errors during item/holdingsRecord update ownership",
+ "type": "object",
+ "properties": {
+ "notUpdatedEntities": {
+ "description": "Item/HoldingsRecord errors",
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "entityId": {
+ "$ref": "uuid.json",
+ "description": "Id of item/holdingsRecord"
+ },
+ "errorMessage": {
+ "type": "string",
+ "description": "Error message"
+ }
+ }
+ }
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/src/main/java/org/folio/inventory/InventoryVerticle.java b/src/main/java/org/folio/inventory/InventoryVerticle.java
index 26af54824..80f2bdae9 100644
--- a/src/main/java/org/folio/inventory/InventoryVerticle.java
+++ b/src/main/java/org/folio/inventory/InventoryVerticle.java
@@ -70,7 +70,7 @@ public void start(Promise started) {
new ItemsByHoldingsRecordId(storage, client).register(router);
new InventoryConfigApi().register(router);
new TenantApi().register(router);
- new UpdateOwnershipApi(storage, client).register(router);
+ new UpdateOwnershipApi(storage, client, consortiumService).register(router);
Handler> onHttpServerStart = result -> {
if (result.succeeded()) {
diff --git a/src/main/java/org/folio/inventory/resources/MoveApi.java b/src/main/java/org/folio/inventory/resources/MoveApi.java
index 14216ddb0..68990f155 100644
--- a/src/main/java/org/folio/inventory/resources/MoveApi.java
+++ b/src/main/java/org/folio/inventory/resources/MoveApi.java
@@ -2,20 +2,21 @@
import static java.util.stream.Collectors.toList;
import static org.folio.inventory.support.JsonArrayHelper.toListOfStrings;
-import static org.folio.inventory.support.http.server.JsonResponse.success;
+import static org.folio.inventory.support.MoveApiUtil.createHoldingsRecordsFetchClient;
+import static org.folio.inventory.support.MoveApiUtil.createHoldingsStorageClient;
+import static org.folio.inventory.support.MoveApiUtil.createHttpClient;
+import static org.folio.inventory.support.MoveApiUtil.createItemStorageClient;
+import static org.folio.inventory.support.MoveApiUtil.createItemsFetchClient;
+import static org.folio.inventory.support.MoveApiUtil.respond;
import static org.folio.inventory.support.http.server.JsonResponse.unprocessableEntity;
import static org.folio.inventory.validation.MoveValidator.holdingsMoveHasRequiredFields;
import static org.folio.inventory.validation.MoveValidator.itemsMoveHasRequiredFields;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
-import org.apache.commons.collections4.ListUtils;
import org.folio.HoldingsRecord;
import org.folio.inventory.common.WebContext;
import org.folio.inventory.domain.HoldingsRecordCollection;
@@ -23,20 +24,17 @@
import org.folio.inventory.domain.items.ItemCollection;
import org.folio.inventory.storage.Storage;
import org.folio.inventory.storage.external.CollectionResourceClient;
-import org.folio.inventory.storage.external.CqlQuery;
import org.folio.inventory.storage.external.MultipleRecordsFetchClient;
import org.folio.inventory.support.ItemUtil;
-import org.folio.inventory.support.http.client.OkapiHttpClient;
+import org.folio.inventory.support.MoveApiUtil;
import org.folio.inventory.support.http.server.JsonResponse;
import org.folio.inventory.support.http.server.ServerErrorResponse;
import org.folio.inventory.support.http.server.ValidationError;
import io.vertx.core.http.HttpClient;
-import io.vertx.core.http.HttpServerResponse;
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;
public class MoveApi extends AbstractInventoryResource {
@@ -44,12 +42,6 @@ public class MoveApi extends AbstractInventoryResource {
public static final String TO_INSTANCE_ID = "toInstanceId";
public static final String ITEM_IDS = "itemIds";
public static final String HOLDINGS_RECORD_IDS = "holdingsRecordIds";
- public static final String ITEM_STORAGE = "/item-storage/items";
- public static final String ITEMS_PROPERTY = "items";
- public static final String HOLDINGS_RECORDS_PROPERTY = "holdingsRecords";
- public static final String HOLDINGS_STORAGE = "/holdings-storage/holdings";
- private static final String HOLDINGS_ITEMS_PROPERTY = "holdingsItems";
- private static final String BARE_HOLDINGS_ITEMS_PROPERTY = "bareHoldingsItems";
public MoveApi(final Storage storage, final HttpClient client) {
super(storage, client);
@@ -84,10 +76,10 @@ private void moveItems(RoutingContext routingContext) {
.thenAccept(holding -> {
if (holding != null) {
try {
- final var itemsStorageClient = createItemStorageClient(routingContext, context);
+ final var itemsStorageClient = createItemStorageClient(createHttpClient(client, routingContext, context), context);
final var itemsFetchClient = createItemsFetchClient(itemsStorageClient);
- itemsFetchClient.find(itemIdsToUpdate, this::fetchByIdCql)
+ itemsFetchClient.find(itemIdsToUpdate, MoveApiUtil::fetchByIdCql)
.thenAccept(jsons -> {
List- itemsToUpdate = updateHoldingsRecordIdForItems(toHoldingsRecordId, jsons);
updateItems(routingContext, context, itemIdsToUpdate, itemsToUpdate);
@@ -129,11 +121,11 @@ private void moveHoldings(RoutingContext routingContext) {
return;
}
try {
- CollectionResourceClient holdingsStorageClient = createHoldingsStorageClient(createHttpClient(routingContext, context),
+ CollectionResourceClient holdingsStorageClient = createHoldingsStorageClient(createHttpClient(client, routingContext, context),
context);
MultipleRecordsFetchClient holdingsRecordFetchClient = createHoldingsRecordsFetchClient(holdingsStorageClient);
- holdingsRecordFetchClient.find(holdingsRecordsIdsToUpdate, this::fetchByIdCql)
+ holdingsRecordFetchClient.find(holdingsRecordsIdsToUpdate, MoveApiUtil::fetchByIdCql)
.thenAccept(jsons -> {
List holdingsRecordsToUpdate = updateInstanceIdForHoldings(toInstanceId, jsons);
updateHoldings(routingContext, context, holdingsRecordsIdsToUpdate, holdingsRecordsToUpdate);
@@ -176,18 +168,14 @@ private void updateItems(RoutingContext routingContext, WebContext context, List
}
private List updateInstanceIdForHoldings(String toInstanceId, List jsons) {
+ jsons.forEach(MoveApiUtil::removeExtraRedundantFields);
+
return jsons.stream()
- .peek(this::removeExtraRedundantFields)
.map(json -> json.mapTo(HoldingsRecord.class))
.map(holding -> holding.withInstanceId(toInstanceId))
.collect(toList());
}
- private void removeExtraRedundantFields(JsonObject json) {
- json.remove(HOLDINGS_ITEMS_PROPERTY);
- json.remove(BARE_HOLDINGS_ITEMS_PROPERTY);
- }
-
private void updateHoldings(RoutingContext routingContext, WebContext context, List idsToUpdate,
List holdingsToUpdate) {
HoldingsRecordCollection storageHoldingsRecordsCollection = storage.getHoldingsRecordCollection(context);
@@ -204,66 +192,4 @@ private void updateHoldings(RoutingContext routingContext, WebContext context, L
.collect(toList()))
.thenAccept(updatedIds -> respond(routingContext, idsToUpdate, updatedIds));
}
-
- private void respond(RoutingContext routingContext, List itemIdsToUpdate, List updatedItemIds) {
- List nonUpdatedIds = ListUtils.subtract(itemIdsToUpdate, updatedItemIds);
- HttpServerResponse response = routingContext.response();
- if (nonUpdatedIds.isEmpty()) {
- successWithEmptyIds(response);
- } else {
- successWithIds(response, nonUpdatedIds);
- }
- }
-
- private OkapiHttpClient createHttpClient(RoutingContext routingContext, WebContext context) throws MalformedURLException {
- return new OkapiHttpClient(WebClient.wrap(client), context,
- exception -> ServerErrorResponse.internalError(routingContext.response(),
- String.format("Failed to contact storage module: %s", exception.toString())));
- }
-
- private CollectionResourceClient createStorageClient(OkapiHttpClient client, WebContext context, String storageUrl)
- throws MalformedURLException {
-
- return new CollectionResourceClient(client, new URL(context.getOkapiLocation() + storageUrl));
- }
-
- private CollectionResourceClient createItemStorageClient(
- RoutingContext routingContext, WebContext context) throws MalformedURLException {
-
- return createStorageClient(createHttpClient(routingContext, context),
- context, ITEM_STORAGE);
- }
-
- private CollectionResourceClient createHoldingsStorageClient(OkapiHttpClient client, WebContext context)
- throws MalformedURLException {
- return createStorageClient(client, context, HOLDINGS_STORAGE);
- }
-
- private CqlQuery fetchByIdCql(List ids) {
- return CqlQuery.exactMatchAny("id", ids);
- }
-
- private MultipleRecordsFetchClient createFetchClient(CollectionResourceClient client, String propertyName) {
- return MultipleRecordsFetchClient.builder()
- .withCollectionPropertyName(propertyName)
- .withExpectedStatus(200)
- .withCollectionResourceClient(client)
- .build();
- }
-
- private MultipleRecordsFetchClient createItemsFetchClient(CollectionResourceClient client) {
- return createFetchClient(client, ITEMS_PROPERTY);
- }
-
- private MultipleRecordsFetchClient createHoldingsRecordsFetchClient(CollectionResourceClient client) {
- return createFetchClient(client, HOLDINGS_RECORDS_PROPERTY);
- }
-
- private void successWithIds(HttpServerResponse response, List ids) {
- success(response, new JsonObject().put("nonUpdatedIds", ids));
- }
-
- private void successWithEmptyIds(HttpServerResponse response) {
- successWithIds(response, new ArrayList<>());
- }
}
diff --git a/src/main/java/org/folio/inventory/resources/UpdateOwnershipApi.java b/src/main/java/org/folio/inventory/resources/UpdateOwnershipApi.java
index 339fb8886..0c295d7ce 100644
--- a/src/main/java/org/folio/inventory/resources/UpdateOwnershipApi.java
+++ b/src/main/java/org/folio/inventory/resources/UpdateOwnershipApi.java
@@ -1,28 +1,304 @@
package org.folio.inventory.resources;
+import io.vertx.core.Promise;
import io.vertx.core.http.HttpClient;
+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.HoldingsRecordCollection;
+import org.folio.inventory.domain.items.Item;
+import org.folio.inventory.domain.items.ItemCollection;
+import org.folio.inventory.exceptions.BadRequestException;
+import org.folio.inventory.exceptions.NotFoundException;
import org.folio.inventory.storage.Storage;
+import org.folio.inventory.storage.external.CollectionResourceClient;
+import org.folio.inventory.storage.external.MultipleRecordsFetchClient;
+import org.folio.inventory.support.ItemUtil;
+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;
+import static org.folio.inventory.domain.instances.InstanceSource.CONSORTIUM_MARC;
+import static org.folio.inventory.support.EndpointFailureHandler.handleFailure;
+import static org.folio.inventory.support.MoveApiUtil.createHoldingsRecordsFetchClient;
+import static org.folio.inventory.support.MoveApiUtil.createHoldingsStorageClient;
+import static org.folio.inventory.support.MoveApiUtil.createHttpClient;
+import static org.folio.inventory.support.MoveApiUtil.createItemStorageClient;
+import static org.folio.inventory.support.MoveApiUtil.createItemsFetchClient;
+import static org.folio.inventory.support.MoveApiUtil.respond;
+import static org.folio.inventory.support.http.server.JsonResponse.unprocessableEntity;
+import static org.folio.inventory.validation.UpdateOwnershipValidator.updateOwnershipHasRequiredFields;
public class UpdateOwnershipApi extends AbstractInventoryResource {
- public UpdateOwnershipApi(Storage storage, HttpClient client) {
+ private static final Logger LOGGER = LogManager.getLogger(MethodHandles.lookup().lookupClass());
+ 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 HOLDINGS_NOT_FOUND = "HoldingsRecord with id: %s not found on tenant: %s";
+ public static final String LOG_UPDATE_HOLDINGS_OWNERSHIP = "updateHoldingsOwnership:: %s";
+
+ private final ConsortiumService consortiumService;
+
+ public UpdateOwnershipApi(Storage storage, HttpClient client, ConsortiumService consortiumService) {
super(storage, client);
+ this.consortiumService = consortiumService;
}
@Override
public void register(Router router) {
router.post("/inventory/items/update-ownership")
- .handler(this::updateItemsOwnership);
+ .handler(this::processUpdateItemsOwnership);
router.post("/inventory/holdings/update-ownership")
- .handler(this::updateHoldingsOwnership);
+ .handler(this::processUpdateHoldingsOwnership);
+ }
+
+ private void processUpdateHoldingsOwnership(RoutingContext routingContext) {
+ try {
+ final var context = new WebContext(routingContext);
+ final var updateOwnershipRequest = routingContext.body().asJsonObject();
+
+ final var validationError = updateOwnershipHasRequiredFields(context.getTenantId(), updateOwnershipRequest, HoldingsUpdateOwnership.class);
+
+ if (validationError.isPresent()) {
+ unprocessableEntity(routingContext.response(), validationError.get());
+ return;
+ }
+ 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());
+
+ consortiumService.getConsortiumConfiguration(context).toCompletionStage().toCompletableFuture()
+ .thenCompose(consortiumConfigurationOptional -> {
+ if (consortiumConfigurationOptional.isPresent()) {
+ return storage.getInstanceCollection(context)
+ .findById(holdingsUpdateOwnership.getToInstanceId())
+ .thenCompose(instance -> {
+ 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, notUpdatedEntities, routingContext, context, targetTenantContext);
+ } else {
+ String instanceNotSharedErrorMessage = String.format(INSTANCE_NOT_SHARED, holdingsUpdateOwnership.getToInstanceId());
+ LOGGER.warn(String.format(LOG_UPDATE_HOLDINGS_OWNERSHIP, instanceNotSharedErrorMessage));
+ return CompletableFuture.failedFuture(new BadRequestException(instanceNotSharedErrorMessage));
+ }
+ } else {
+ String instanceNotFoundErrorMessage = String.format(INSTANCE_NOT_FOUND_AT_SOURCE_TENANT, holdingsUpdateOwnership.getToInstanceId(), context.getTenantId());
+ LOGGER.warn(String.format(LOG_UPDATE_HOLDINGS_OWNERSHIP, instanceNotFoundErrorMessage));
+ return CompletableFuture.failedFuture(new NotFoundException(instanceNotFoundErrorMessage));
+ }
+ });
+ }
+ String notInConsortiaErrorMessage = String.format(TENANT_NOT_IN_CONSORTIA, context.getTenantId());
+ LOGGER.warn(String.format(LOG_UPDATE_HOLDINGS_OWNERSHIP, notInConsortiaErrorMessage));
+ return CompletableFuture.failedFuture(new BadRequestException(notInConsortiaErrorMessage));
+ })
+ .thenAccept(v -> respond(routingContext, notUpdatedEntities))
+ .exceptionally(throwable -> {
+ LOGGER.warn("updateHoldingsOwnership:: Error during update ownership of holdings {}, to tenant: {}",
+ holdingsUpdateOwnership.getHoldingsRecordIds(), holdingsUpdateOwnership.getTargetTenantId(), throwable);
+ handleFailure(throwable, routingContext);
+ return null;
+ });
+ } catch (Exception e) {
+ LOGGER.warn("updateHoldingsOwnership:: Error during update ownership of holdings", e);
+ handleFailure(e, routingContext);
+ }
+ }
+
+ private void processUpdateItemsOwnership(RoutingContext routingContext) {
+ // should be implemented in MODINV-955
+ }
+
+ private CompletableFuture
> updateOwnershipOfHoldingsRecords(HoldingsUpdateOwnership holdingsUpdateOwnership,
+ List notUpdatedEntities, RoutingContext routingContext,
+ WebContext context, Context targetTenantContext) {
+ try {
+ LOGGER.info("updateOwnershipOfHoldingsRecords:: Updating ownership of holdingsRecord: {}, to tenant: {}",
+ holdingsUpdateOwnership.getHoldingsRecordIds(), targetTenantContext.getTenantId());
+
+ CollectionResourceClient holdingsStorageClient = createHoldingsStorageClient(createHttpClient(client, routingContext, context),
+ context);
+ MultipleRecordsFetchClient holdingsRecordFetchClient = createHoldingsRecordsFetchClient(holdingsStorageClient);
+
+ HoldingsRecordCollection sourceTenantHoldingsRecordCollection = storage.getHoldingsRecordCollection(context);
+ HoldingsRecordCollection targetTenantHoldingsRecordCollection = storage.getHoldingsRecordCollection(targetTenantContext);
+
+ return holdingsRecordFetchClient.find(holdingsUpdateOwnership.getHoldingsRecordIds(), MoveApiUtil::fetchByIdCql)
+ .thenCompose(jsons -> {
+ LOGGER.info("updateOwnershipOfHoldingsRecords:: Found holdings to update ownership: {}", jsons);
+ processNotFoundInstances(holdingsUpdateOwnership.getHoldingsRecordIds(), notUpdatedEntities, context, jsons);
+ if (!jsons.isEmpty()) {
+ return createHoldings(jsons, notUpdatedEntities, holdingsUpdateOwnership.getToInstanceId(), targetTenantHoldingsRecordCollection)
+ .thenCompose(createdHoldings -> {
+ LOGGER.info("updateOwnershipOfHoldingsRecords:: Created holdings: {}, for tenant: {}", createdHoldings, targetTenantContext.getTenantId());
+ List createdHoldingsIds = createdHoldings.stream().map(HoldingsRecord::getId).toList();
+
+ return transferAttachedItems(createdHoldingsIds, notUpdatedEntities, routingContext, context, targetTenantContext)
+ .thenCompose(itemIds ->
+ deleteHoldings(getHoldingsToDelete(notUpdatedEntities, createdHoldings), notUpdatedEntities, sourceTenantHoldingsRecordCollection));
+ });
+ }
+ return CompletableFuture.completedFuture(new ArrayList<>());
+ });
+ } catch (Exception e) {
+ LOGGER.warn("updateOwnershipOfHoldingsRecords:: Error during update ownership of holdings {}, to tenant: {}",
+ holdingsUpdateOwnership.getHoldingsRecordIds(), holdingsUpdateOwnership.getTargetTenantId(), e);
+ return CompletableFuture.failedFuture(e);
+ }
+ }
+
+ private CompletableFuture> transferAttachedItems(List holdingsRecordIds, List notUpdatedEntities,
+ RoutingContext routingContext, WebContext context, Context targetTenantContext) {
+ try {
+ LOGGER.info("transferAttachedItems:: Transfer items of holdingsRecordIds: {}, to tenant: {}",
+ holdingsRecordIds, targetTenantContext.getTenantId());
+
+ CollectionResourceClient itemsStorageClient = createItemStorageClient(createHttpClient(client, routingContext, context), context);
+ MultipleRecordsFetchClient itemsFetchClient = createItemsFetchClient(itemsStorageClient);
+
+ ItemCollection sourceTenantItemCollection = storage.getItemCollection(context);
+ ItemCollection targetTenantItemCollection = storage.getItemCollection(targetTenantContext);
+
+ return itemsFetchClient.find(holdingsRecordIds, MoveApiUtil::fetchByHoldingsRecordIdCql)
+ .thenCompose(jsons -> {
+ LOGGER.info("transferAttachedItems:: Found items to transfer: {}", jsons);
+ if (!jsons.isEmpty()) {
+ return createItems(jsons, notUpdatedEntities, targetTenantItemCollection)
+ .thenCompose(items -> deleteItems(items, notUpdatedEntities, sourceTenantItemCollection));
+ }
+ return CompletableFuture.completedFuture(new ArrayList<>());
+ });
+ } catch (Exception e) {
+ LOGGER.warn("transferAttachedItems:: Error during transfer attached items for holdings {}, to tenant: {}",
+ holdingsRecordIds, targetTenantContext.getTenantId(), e);
+ return CompletableFuture.failedFuture(e);
+ }
+ }
+
+ private CompletableFuture> createItems(List jsons, List notUpdatedEntities, ItemCollection itemCollection) {
+ List- itemRecordsToUpdateOwnership = jsons.stream()
+ .map(json -> ItemUtil.fromStoredItemRepresentation(json).withHrid(null))
+ .toList();
+
+ List> createFutures = itemRecordsToUpdateOwnership.stream()
+ .map(item ->
+ itemCollection.add(item)
+ .exceptionally(e -> {
+ LOGGER.warn("createHoldings:: Error during creating item with id: {} for holdingsRecord with id: {}", item.getId(), item.getHoldingId(), e);
+ notUpdatedEntities.add(new NotUpdatedEntity().withEntityId(item.getHoldingId()).withErrorMessage(e.getMessage()));
+ throw new CompletionException(e);
+ }))
+ .toList();
+
+ return CompletableFuture.allOf(createFutures.toArray(new CompletableFuture[0]))
+ .handle((vVoid, throwable) -> createFutures.stream()
+ .filter(future -> !future.isCompletedExceptionally())
+ .map(CompletableFuture::join)
+ .toList());
+ }
+
+ private CompletableFuture
> createHoldings(List jsons, List notUpdatedEntities, String instanceId,
+ HoldingsRecordCollection holdingsRecordCollection) {
+ jsons.forEach(MoveApiUtil::removeExtraRedundantFields);
+
+ List holdingsRecordsToUpdateOwnership = jsons.stream()
+ .map(json -> json.mapTo(HoldingsRecord.class).withHrid(null))
+ .filter(holdingsRecord -> holdingsRecord.getInstanceId().equals(instanceId))
+ .toList();
+
+ List> createFutures = holdingsRecordsToUpdateOwnership.stream()
+ .map(holdingRecord ->
+ holdingsRecordCollection.add(holdingRecord)
+ .exceptionally(e -> {
+ LOGGER.warn("createHoldings:: Error during creating holdingsRecord with id: {}", holdingRecord.getId(), e);
+ notUpdatedEntities.add(new NotUpdatedEntity().withEntityId(holdingRecord.getId()).withErrorMessage(e.getMessage()));
+ throw new CompletionException(e);
+ }))
+ .toList();
+
+ return CompletableFuture.allOf(createFutures.toArray(new CompletableFuture[0]))
+ .handle((vVoid, throwable) -> createFutures.stream()
+ .filter(future -> !future.isCompletedExceptionally())
+ .map(CompletableFuture::join)
+ .toList());
+ }
+
+ 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 -> {
+ LOGGER.warn("deleteHoldings:: Error during deleting holdingsRecord with id: {}, status code: {}, reason: {}",
+ holdingsRecord.getId(), failure.getStatusCode(), failure.getReason());
+
+ 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- items, List notUpdatedEntities, ItemCollection itemCollection) {
+ List> deleteFutures = items.stream()
+ .map(item -> {
+ Promise promise = Promise.promise();
+ itemCollection.delete(item.getId(), success -> promise.complete(item.getId()),
+ failure -> {
+ LOGGER.warn("deleteItems:: Error during deleting item with id: {} for holdingsRecord with id {}, status code: {}, reason: {}",
+ item.getId(), item.getHoldingId(), failure.getStatusCode(), failure.getReason());
+
+ notUpdatedEntities.add(new NotUpdatedEntity().withEntityId(item.getHoldingId()).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 void updateHoldingsOwnership(RoutingContext routingContext) {
- // should be implemented in MODINV-1031
+ private void processNotFoundInstances(List holdingsRecordIds, List notUpdatedEntities, WebContext context, List jsons) {
+ List foundIds = jsons.stream().map(json -> json.getString("id")).toList();
+ List notFoundIds = ListUtils.subtract(holdingsRecordIds, foundIds);
+ notFoundIds.forEach(id -> {
+ String errorMessage = String.format(HOLDINGS_NOT_FOUND, id, context.getTenantId());
+ LOGGER.warn(String.format("processNotFoundInstances:: %s", errorMessage));
+ notUpdatedEntities.add(new NotUpdatedEntity().withEntityId(id).withErrorMessage(errorMessage));
+ });
}
- private void updateItemsOwnership(RoutingContext routingContext) {
- // should be implemented in MODINV-1031
+ 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
new file mode 100644
index 000000000..af6051a21
--- /dev/null
+++ b/src/main/java/org/folio/inventory/support/MoveApiUtil.java
@@ -0,0 +1,110 @@
+package org.folio.inventory.support;
+
+import io.vertx.core.http.HttpClient;
+import io.vertx.core.http.HttpServerResponse;
+import io.vertx.core.json.JsonObject;
+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;
+import org.folio.inventory.storage.external.MultipleRecordsFetchClient;
+import org.folio.inventory.support.http.client.OkapiHttpClient;
+import org.folio.inventory.support.http.server.ServerErrorResponse;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.folio.inventory.support.http.server.JsonResponse.success;
+
+public final class MoveApiUtil {
+ public static final String HOLDINGS_STORAGE = "/holdings-storage/holdings";
+ public static final String HOLDINGS_RECORDS_PROPERTY = "holdingsRecords";
+ public static final String TARGET_TENANT_ID = "targetTenantId";
+ public static final String ITEM_STORAGE = "/item-storage/items";
+ public static final String ITEMS_PROPERTY = "items";
+ private static final String HOLDINGS_ITEMS_PROPERTY = "holdingsItems";
+ private static final String BARE_HOLDINGS_ITEMS_PROPERTY = "bareHoldingsItems";
+
+ private MoveApiUtil() { }
+
+ public static OkapiHttpClient createHttpClient(HttpClient client, RoutingContext routingContext, WebContext context) throws MalformedURLException {
+ return new OkapiHttpClient(WebClient.wrap(client), context,
+ exception -> ServerErrorResponse.internalError(routingContext.response(),
+ String.format("Failed to contact storage module: %s", exception.toString())));
+ }
+
+
+ private static MultipleRecordsFetchClient createFetchClient(CollectionResourceClient client, String propertyName) {
+ return MultipleRecordsFetchClient.builder()
+ .withCollectionPropertyName(propertyName)
+ .withExpectedStatus(200)
+ .withCollectionResourceClient(client)
+ .build();
+ }
+
+ public static CollectionResourceClient createStorageClient(OkapiHttpClient client, WebContext context, String storageUrl)
+ throws MalformedURLException {
+
+ return new CollectionResourceClient(client, new URL(context.getOkapiLocation() + storageUrl));
+ }
+
+ public static CollectionResourceClient createHoldingsStorageClient(OkapiHttpClient client, WebContext context)
+ throws MalformedURLException {
+ return createStorageClient(client, context, HOLDINGS_STORAGE);
+ }
+
+ public static CollectionResourceClient createItemStorageClient(OkapiHttpClient client, WebContext context)
+ throws MalformedURLException {
+ return createStorageClient(client, context, ITEM_STORAGE);
+ }
+
+ public static MultipleRecordsFetchClient createHoldingsRecordsFetchClient(CollectionResourceClient client) {
+ return createFetchClient(client, HOLDINGS_RECORDS_PROPERTY);
+ }
+
+ public static MultipleRecordsFetchClient createItemsFetchClient(CollectionResourceClient client) {
+ return createFetchClient(client, ITEMS_PROPERTY);
+ }
+
+ public static CqlQuery fetchByIdCql(List ids) {
+ return CqlQuery.exactMatchAny("id", ids);
+ }
+
+ public static CqlQuery fetchByHoldingsRecordIdCql(List ids) {
+ return CqlQuery.exactMatchAny("holdingsRecordId", ids);
+ }
+
+ public static void successWithEmptyIds(HttpServerResponse response) {
+ successWithIds(response, new ArrayList<>());
+ }
+
+ public static void successWithIds(HttpServerResponse response, List ids) {
+ success(response, new JsonObject().put("nonUpdatedIds", ids));
+ }
+
+ public static void removeExtraRedundantFields(JsonObject json) {
+ json.remove(HOLDINGS_ITEMS_PROPERTY);
+ json.remove(BARE_HOLDINGS_ITEMS_PROPERTY);
+ }
+
+ public static void respond(RoutingContext routingContext, List itemIdsToUpdate, List updatedItemIds) {
+ List nonUpdatedIds = ListUtils.subtract(itemIdsToUpdate, updatedItemIds);
+ HttpServerResponse response = routingContext.response();
+ if (nonUpdatedIds.isEmpty()) {
+ successWithEmptyIds(response);
+ } else {
+ successWithIds(response, nonUpdatedIds);
+ }
+ }
+
+ public static void respond(RoutingContext routingContext, List notUpdatedEntities) {
+ HttpServerResponse response = routingContext.response();
+ success(response, JsonObject.mapFrom(new UpdateOwnershipResponse().withNotUpdatedEntities(notUpdatedEntities)));
+ }
+}
diff --git a/src/main/java/org/folio/inventory/validation/UpdateOwnershipValidator.java b/src/main/java/org/folio/inventory/validation/UpdateOwnershipValidator.java
new file mode 100644
index 000000000..bd5afdb55
--- /dev/null
+++ b/src/main/java/org/folio/inventory/validation/UpdateOwnershipValidator.java
@@ -0,0 +1,31 @@
+package org.folio.inventory.validation;
+
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.JsonObject;
+import org.folio.inventory.support.http.server.ValidationError;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import static org.folio.inventory.support.MoveApiUtil.TARGET_TENANT_ID;
+
+public final class UpdateOwnershipValidator {
+ private UpdateOwnershipValidator() { }
+
+ public static Optional updateOwnershipHasRequiredFields(String sourceTenant, JsonObject updateOwnershipRequest, Class> updateOwnershipClass) {
+ List requiredFields = Arrays.stream(updateOwnershipClass.getDeclaredFields()).map(Field::getName).toList();
+ for (String field: requiredFields) {
+ var value = updateOwnershipRequest.getValue(field);
+ if (value == null || (value instanceof JsonArray jsonArray && jsonArray.isEmpty())) {
+ return Optional.of(new ValidationError(field + " is a required field", field, null));
+ }
+ }
+ String targetTenantId = updateOwnershipRequest.getString(TARGET_TENANT_ID);
+ if (sourceTenant.equals(targetTenantId)) {
+ return Optional.of(new ValidationError("targetTenantId field cannot be equal to source tenant id", TARGET_TENANT_ID, targetTenantId));
+ }
+ return Optional.empty();
+ }
+}
diff --git a/src/test/java/api/ApiTestSuite.java b/src/test/java/api/ApiTestSuite.java
index 2373dc0fd..4f3c967bc 100644
--- a/src/test/java/api/ApiTestSuite.java
+++ b/src/test/java/api/ApiTestSuite.java
@@ -11,6 +11,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import api.holdings.HoldingsUpdateOwnershipApiTest;
import org.folio.inventory.InventoryVerticle;
import org.folio.inventory.common.VertxAssistant;
import org.folio.inventory.consortium.util.ConsortiumUtil;
@@ -68,12 +69,14 @@
BoundWithTests.class,
TenantApiTest.class,
AdminApiTest.class,
- InventoryConfigApiTest.class
+ InventoryConfigApiTest.class,
+ HoldingsUpdateOwnershipApiTest.class
})
public class ApiTestSuite {
public static final int INVENTORY_VERTICLE_TEST_PORT = 9603;
public static final String TENANT_ID = "test_tenant";
public static final String CONSORTIA_TENANT_ID = "consortium";
+ public static final String COLLEGE_TENANT_ID = "college";
public static final UUID ID_FOR_FAILURE = UUID.fromString("fa45a95b-38a3-430b-8f34-548ca005a176");
public static final UUID ID_FOR_OPTIMISTIC_LOCKING_FAILURE = UUID.fromString("40900409-0409-4444-8888-409000000409");
diff --git a/src/test/java/api/holdings/HoldingsUpdateOwnershipApiTest.java b/src/test/java/api/holdings/HoldingsUpdateOwnershipApiTest.java
index 28f8145e1..85c48a826 100644
--- a/src/test/java/api/holdings/HoldingsUpdateOwnershipApiTest.java
+++ b/src/test/java/api/holdings/HoldingsUpdateOwnershipApiTest.java
@@ -6,17 +6,21 @@
import api.support.InstanceApiClient;
import api.support.builders.HoldingRequestBuilder;
import api.support.builders.HoldingsRecordUpdateOwnershipRequestBuilder;
+import api.support.builders.ItemRequestBuilder;
+import io.vertx.core.http.HttpMethod;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import junitparams.JUnitParamsRunner;
import org.apache.http.HttpStatus;
+import org.folio.inventory.domain.items.ItemStatusName;
import org.folio.inventory.support.http.client.Response;
+import org.joda.time.DateTime;
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;
import java.net.MalformedURLException;
import java.util.List;
@@ -25,9 +29,11 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import static api.ApiTestSuite.ID_FOR_FAILURE;
import static api.ApiTestSuite.createConsortiumTenant;
import static api.support.InstanceSamples.smallAngryPlanet;
+import static org.folio.inventory.domain.instances.InstanceSource.CONSORTIUM_FOLIO;
+import static org.folio.inventory.domain.instances.InstanceSource.FOLIO;
+import static org.folio.inventory.support.ItemUtil.HOLDINGS_RECORD_ID;
import static org.folio.inventory.support.http.ContentType.APPLICATION_JSON;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -35,7 +41,6 @@
import static org.hamcrest.Matchers.equalTo;
import static support.matchers.ResponseMatchers.hasValidationError;
-@Ignore
@RunWith(JUnitParamsRunner.class)
public class HoldingsUpdateOwnershipApiTest extends ApiTests {
private static final String INSTANCE_ID = "instanceId";
@@ -55,32 +60,220 @@ public void canUpdateHoldingsOwnershipToDifferentTenant() throws MalformedURLExc
UUID instanceId = UUID.randomUUID();
JsonObject instance = smallAngryPlanet(instanceId);
- InstanceApiClient.createInstance(okapiClient, instance);
- InstanceApiClient.createInstance(consortiumOkapiClient, instance);
+ InstanceApiClient.createInstance(okapiClient, instance.put("source", CONSORTIUM_FOLIO.getValue()));
+ InstanceApiClient.createInstance(consortiumOkapiClient, instance.put("source", FOLIO.getValue()));
+
+ final UUID createHoldingsRecord1 = createHoldingForInstance(instanceId);
+ final UUID createHoldingsRecord2 = createHoldingForInstance(instanceId);
+
+ JsonObject holdingsRecordUpdateOwnershipRequestBody = new HoldingsRecordUpdateOwnershipRequestBuilder(instanceId,
+ new JsonArray(List.of(createHoldingsRecord1.toString(), createHoldingsRecord2.toString())), ApiTestSuite.COLLEGE_TENANT_ID).create();
+
+ Response postHoldingsUpdateOwnershipResponse = updateHoldingsRecordsOwnership(holdingsRecordUpdateOwnershipRequestBody);
+
+ assertThat(postHoldingsUpdateOwnershipResponse.getStatusCode(), is(200));
+ assertThat(new JsonObject(postHoldingsUpdateOwnershipResponse.getBody()).getJsonArray("notUpdatedEntities").size(), is(0));
+ assertThat(postHoldingsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON));
+
+ Response sourceTenantHoldingsRecord1 = holdingsStorageClient.getById(createHoldingsRecord1);
+ Response targetTenantHoldingsRecord1 = collegeHoldingsStorageClient.getById(createHoldingsRecord1);
+
+ Assert.assertEquals(HttpStatus.SC_NOT_FOUND, sourceTenantHoldingsRecord1.getStatusCode());
+ Assert.assertEquals(instanceId.toString(), targetTenantHoldingsRecord1.getJson().getString(INSTANCE_ID));
+
+ 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));
+ Assert.assertNull(targetTenantHoldingsRecord2.getJson().getString("hrid"));
+ }
+
+ @Test
+ public void canUpdateHoldingsOwnershipWithRelatedItemsToDifferentTenant() throws MalformedURLException, ExecutionException, InterruptedException, TimeoutException {
+ UUID instanceId = UUID.randomUUID();
+ JsonObject instance = smallAngryPlanet(instanceId);
+ String itemHrId = "it0000001";
+
+ InstanceApiClient.createInstance(okapiClient, instance.put("source", CONSORTIUM_FOLIO.getValue()));
+ 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()
+ .forHolding(createHoldingsRecord1)
+ .withBarcode("645398607547")
+ .withStatus(ItemStatusName.AVAILABLE.value()));
+
+ final var secondItem = itemsClient.create(
+ new ItemRequestBuilder()
+ .forHolding(createHoldingsRecord2)
+ .withHrid(itemHrId)
+ .withBarcode("645398607546")
+ .withStatus(ItemStatusName.AVAILABLE.value()));
+
JsonObject holdingsRecordUpdateOwnershipRequestBody = new HoldingsRecordUpdateOwnershipRequestBuilder(instanceId,
- new JsonArray(List.of(createHoldingsRecord1.toString(), createHoldingsRecord2.toString())), ApiTestSuite.CONSORTIA_TENANT_ID).create();
+ new JsonArray(List.of(createHoldingsRecord1.toString(), createHoldingsRecord2.toString())), ApiTestSuite.COLLEGE_TENANT_ID).create();
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
Response sourceTenantHoldingsRecord1 = holdingsStorageClient.getById(createHoldingsRecord1);
- Response targetTenantHoldingsRecord1 = consortiumHoldingsStorageClient.getById(createHoldingsRecord1);
+ Response targetTenantHoldingsRecord1 = collegeHoldingsStorageClient.getById(createHoldingsRecord1);
Assert.assertEquals(HttpStatus.SC_NOT_FOUND, sourceTenantHoldingsRecord1.getStatusCode());
Assert.assertEquals(instanceId.toString(), targetTenantHoldingsRecord1.getJson().getString(INSTANCE_ID));
- Response sourceTenantHoldingsRecord2 = holdingsStorageClient.getById(createHoldingsRecord1);
- Response targetTenantHoldingsRecord2 = consortiumHoldingsStorageClient.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));
+
+ // Verify related Items ownership updated
+ Response sourceTenantItem1 = itemsClient.getById(firstItem.getId());
+ Response targetTenantItem1 = collegeItemsClient.getById(firstItem.getId());
+
+ assertThat(HttpStatus.SC_NOT_FOUND, is(sourceTenantItem1.getStatusCode()));
+ assertThat(targetTenantItem1.getJson().getString(HOLDINGS_RECORD_ID), is(createHoldingsRecord1.toString()));
+
+ Response sourceTenantItem2 = itemsClient.getById(secondItem.getId());
+ Response targetTenantItem2 = collegeItemsClient.getById(secondItem.getId());
+
+ assertThat(HttpStatus.SC_NOT_FOUND, is(sourceTenantItem2.getStatusCode()));
+ assertThat(targetTenantItem2.getJson().getString(HOLDINGS_RECORD_ID), is(createHoldingsRecord2.toString()));
+ Assert.assertNotEquals(targetTenantItem2.getJson().getString("hrid"), itemHrId);
+ }
+
+ @Test
+ public void canUpdateHoldingsOwnershipIfErrorUpdatingRelatedItemsToDifferentTenant() 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.POST.name()));
+
+ holdingsStorageClient.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();
+
+ 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(HttpStatus.SC_NOT_FOUND, is(targetTenantItem1.getStatusCode()));
+ assertThat(sourceTenantItem1.getJson().getString(HOLDINGS_RECORD_ID), is(createHoldingsRecord1.toString()));
+ }
+
+ @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();
+
+ Response postHoldingsUpdateOwnershipResponse = updateHoldingsRecordsOwnership(holdingsRecordUpdateOwnershipRequestBody);
+
+ collegeItemsClient.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
@@ -88,8 +281,8 @@ public void shouldReportErrorsWhenOnlySomeRequestedHoldingsRecordsCouldNotBeUpda
UUID instanceId = UUID.randomUUID();
JsonObject instance = smallAngryPlanet(instanceId);
- InstanceApiClient.createInstance(okapiClient, instance);
- InstanceApiClient.createInstance(consortiumOkapiClient, instance);
+ InstanceApiClient.createInstance(okapiClient, instance.put("source", CONSORTIUM_FOLIO.getValue()));
+ InstanceApiClient.createInstance(consortiumOkapiClient, instance.put("source", FOLIO.getValue()));
final UUID createHoldingsRecord1 = createHoldingForInstance(instanceId);
final UUID createHoldingsRecord2 = UUID.randomUUID();
@@ -97,27 +290,28 @@ public void shouldReportErrorsWhenOnlySomeRequestedHoldingsRecordsCouldNotBeUpda
Assert.assertNotEquals(createHoldingsRecord1, createHoldingsRecord2);
JsonObject holdingsRecordUpdateOwnershipRequestBody = new HoldingsRecordUpdateOwnershipRequestBuilder(instanceId,
- new JsonArray(List.of(createHoldingsRecord1.toString(), createHoldingsRecord2.toString())), ApiTestSuite.CONSORTIA_TENANT_ID).create();
+ new JsonArray(List.of(createHoldingsRecord1.toString(), createHoldingsRecord2.toString())), ApiTestSuite.COLLEGE_TENANT_ID).create();
Response postHoldingsUpdateOwnershipResponse = updateHoldingsRecordsOwnership(holdingsRecordUpdateOwnershipRequestBody);
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"),
+ equalTo(String.format("HoldingsRecord with id: %s not found on tenant: %s", createHoldingsRecord2, ApiTestSuite.TENANT_ID)));
Response sourceTenantHoldingsRecord1 = holdingsStorageClient.getById(createHoldingsRecord1);
- Response targetTenantHoldingsRecord1 = consortiumHoldingsStorageClient.getById(createHoldingsRecord1);
+ Response targetTenantHoldingsRecord1 = collegeHoldingsStorageClient.getById(createHoldingsRecord1);
Assert.assertEquals(HttpStatus.SC_NOT_FOUND, sourceTenantHoldingsRecord1.getStatusCode());
assertThat(instanceId.toString(), equalTo(targetTenantHoldingsRecord1.getJson().getString(INSTANCE_ID)));
- Response targetTenantHoldingsRecord2 = consortiumHoldingsStorageClient.getById(createHoldingsRecord1);
+ Response targetTenantHoldingsRecord2 = collegeHoldingsStorageClient.getById(createHoldingsRecord2);
Assert.assertEquals(HttpStatus.SC_NOT_FOUND, targetTenantHoldingsRecord2.getStatusCode());
}
@@ -125,7 +319,7 @@ public void shouldReportErrorsWhenOnlySomeRequestedHoldingsRecordsCouldNotBeUpda
public void cannotUpdateHoldingsRecordsOwnershipToUnspecifiedInstance()
throws InterruptedException, MalformedURLException, TimeoutException, ExecutionException {
JsonObject holdingsRecordUpdateOwnershipWithoutToInstanceId = new HoldingsRecordUpdateOwnershipRequestBuilder(null,
- new JsonArray(List.of(UUID.randomUUID())), ApiTestSuite.CONSORTIA_TENANT_ID).create();
+ new JsonArray(List.of(UUID.randomUUID())), ApiTestSuite.COLLEGE_TENANT_ID).create();
Response postHoldingsUpdateOwnershipResponse = updateHoldingsRecordsOwnership(holdingsRecordUpdateOwnershipWithoutToInstanceId);
@@ -149,7 +343,23 @@ public void cannotUpdateHoldingsRecordsOwnershipToUnspecifiedTenant()
assertThat(postHoldingsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON));
assertThat(postHoldingsUpdateOwnershipResponse, hasValidationError(
- "tenantId is a required field", "toInstanceId", null
+ "targetTenantId is a required field", "targetTenantId", null
+ ));
+ }
+
+ @Test
+ public void cannotUpdateHoldingsRecordOwnershipToSameTenant()
+ throws MalformedURLException, InterruptedException, ExecutionException, TimeoutException {
+ JsonObject holdingsRecordUpdateOwnershipRequestBody = new HoldingsRecordUpdateOwnershipRequestBuilder(UUID.randomUUID(),
+ new JsonArray(List.of(UUID.randomUUID().toString())), ApiTestSuite.TENANT_ID).create();
+
+ Response postHoldingsUpdateOwnershipResponse = updateHoldingsRecordsOwnership(holdingsRecordUpdateOwnershipRequestBody);
+
+ assertThat(postHoldingsUpdateOwnershipResponse.getStatusCode(), is(422));
+ assertThat(postHoldingsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON));
+
+ assertThat(postHoldingsUpdateOwnershipResponse, hasValidationError(
+ "targetTenantId field cannot be equal to source tenant id", "targetTenantId", ApiTestSuite.TENANT_ID
));
}
@@ -157,7 +367,7 @@ public void cannotUpdateHoldingsRecordsOwnershipToUnspecifiedTenant()
public void cannotUpdateUnspecifiedHoldingsRecordsOwnership()
throws MalformedURLException, InterruptedException, ExecutionException, TimeoutException {
JsonObject holdingsRecordUpdateOwnershipWithoutHoldingsRecordIds = new HoldingsRecordUpdateOwnershipRequestBuilder(UUID.randomUUID(),
- new JsonArray(), ApiTestSuite.CONSORTIA_TENANT_ID).create();
+ new JsonArray(), ApiTestSuite.COLLEGE_TENANT_ID).create();
Response postHoldingsUpdateOwnershipResponse = updateHoldingsRecordsOwnership(holdingsRecordUpdateOwnershipWithoutHoldingsRecordIds);
@@ -165,15 +375,29 @@ public void cannotUpdateUnspecifiedHoldingsRecordsOwnership()
assertThat(postHoldingsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON));
assertThat(postHoldingsUpdateOwnershipResponse, hasValidationError(
- "Holdings record ids aren't specified", "holdingsRecordIds", null
+ "holdingsRecordIds is a required field", "holdingsRecordIds", null
));
}
@Test
- public void cannotUpdateHoldingsRecordOwnershipOfNonExistedInstance()
+ public void cannotUpdateHoldingsRecordOwnershipIfTenantNotInConsortium()
throws MalformedURLException, InterruptedException, ExecutionException, TimeoutException {
+ userTenantsClient.deleteAll();
+
+ JsonObject holdingsRecordUpdateOwnershipRequestBody = new HoldingsRecordUpdateOwnershipRequestBuilder(UUID.randomUUID(),
+ new JsonArray(List.of(UUID.randomUUID().toString())), ApiTestSuite.COLLEGE_TENANT_ID).create();
+
+ Response postHoldingsUpdateOwnershipResponse = updateHoldingsRecordsOwnership(holdingsRecordUpdateOwnershipRequestBody);
+
+ assertThat(postHoldingsUpdateOwnershipResponse.getStatusCode(), is(400));
+
+ assertThat(postHoldingsUpdateOwnershipResponse.getBody(), containsString("tenant is not in consortia"));
createConsortiumTenant();
+ }
+ @Test
+ public void cannotUpdateHoldingsRecordOwnershipOfNonExistedInstance()
+ throws MalformedURLException, InterruptedException, ExecutionException, TimeoutException {
UUID instanceId = UUID.randomUUID();
JsonObject instance = smallAngryPlanet(instanceId);
@@ -185,54 +409,121 @@ public void cannotUpdateHoldingsRecordOwnershipOfNonExistedInstance()
final UUID createHoldingsRecord1 = createHoldingForInstance(instanceId);
JsonObject holdingsRecordUpdateOwnershipWithoutHoldingsRecordIds = new HoldingsRecordUpdateOwnershipRequestBuilder(invalidInstanceId,
- new JsonArray(List.of(createHoldingsRecord1)), ApiTestSuite.CONSORTIA_TENANT_ID).create();
+ new JsonArray(List.of(createHoldingsRecord1)), ApiTestSuite.COLLEGE_TENANT_ID).create();
Response postHoldingsUpdateOwnershipResponse = updateHoldingsRecordsOwnership(holdingsRecordUpdateOwnershipWithoutHoldingsRecordIds);
- assertThat(postHoldingsUpdateOwnershipResponse.getStatusCode(), is(422));
- assertThat(postHoldingsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON));
+ assertThat(postHoldingsUpdateOwnershipResponse.getStatusCode(), is(404));
- assertThat(postHoldingsUpdateOwnershipResponse.getBody(), containsString("errors"));
+ assertThat(postHoldingsUpdateOwnershipResponse.getBody(), containsString("not found"));
assertThat(postHoldingsUpdateOwnershipResponse.getBody(), containsString(invalidInstanceId.toString()));
}
@Test
- public void canUpdateHoldingsRecordOwnershipDueToHoldingsRecordUpdateError() throws InterruptedException, MalformedURLException, TimeoutException, ExecutionException {
+ public void cannotUpdateHoldingsRecordOwnershipOfNonSharedInstance()
+ throws MalformedURLException, InterruptedException, ExecutionException, TimeoutException {
UUID instanceId = UUID.randomUUID();
JsonObject instance = smallAngryPlanet(instanceId);
InstanceApiClient.createInstance(okapiClient, instance);
- InstanceApiClient.createInstance(consortiumOkapiClient, instance);
final UUID createHoldingsRecord1 = createHoldingForInstance(instanceId);
- final UUID createHoldingsRecord2 = createHoldingForInstance(ID_FOR_FAILURE, instanceId);
+ final UUID createHoldingsRecord2 = createHoldingForInstance(instanceId);
JsonObject holdingsRecordUpdateOwnershipRequestBody = new HoldingsRecordUpdateOwnershipRequestBuilder(instanceId,
- new JsonArray(List.of(createHoldingsRecord1.toString(), createHoldingsRecord2.toString())), ApiTestSuite.CONSORTIA_TENANT_ID).create();
+ new JsonArray(List.of(createHoldingsRecord1.toString(), createHoldingsRecord2.toString())), ApiTestSuite.COLLEGE_TENANT_ID).create();
Response postHoldingsUpdateOwnershipResponse = updateHoldingsRecordsOwnership(holdingsRecordUpdateOwnershipRequestBody);
- List nonUpdatedIdsIds = postHoldingsUpdateOwnershipResponse.getJson()
- .getJsonArray("nonUpdatedIds")
- .getList();
+ assertThat(postHoldingsUpdateOwnershipResponse.getStatusCode(), is(400));
- assertThat(nonUpdatedIdsIds.size(), is(1));
- assertThat(nonUpdatedIdsIds.get(0), equalTo(ID_FOR_FAILURE.toString()));
+ assertThat(postHoldingsUpdateOwnershipResponse.getBody(), containsString(String.format("Instance with id: %s is not shared", instanceId)));
+ }
+
+ @Test
+ public void cannotUpdateHoldingsRecordOwnershipDueToHoldingsRecordCreateError() throws InterruptedException, MalformedURLException, TimeoutException, ExecutionException {
+ 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 JsonObject expectedErrorResponse = new JsonObject().put("message", "Server error");
+ collegeHoldingsStorageClient.emulateFailure(
+ new EndpointFailureDescriptor()
+ .setFailureExpireDate(DateTime.now().plusSeconds(2).toDate())
+ .setStatusCode(500)
+ .setContentType("application/json")
+ .setBody(expectedErrorResponse.toString())
+ .setMethod(HttpMethod.POST.name()));
+
+ JsonObject holdingsRecordUpdateOwnershipRequestBody = new HoldingsRecordUpdateOwnershipRequestBuilder(instanceId,
+ new JsonArray(List.of(createHoldingsRecord1.toString())), ApiTestSuite.COLLEGE_TENANT_ID).create();
+
+ Response postHoldingsUpdateOwnershipResponse = updateHoldingsRecordsOwnership(holdingsRecordUpdateOwnershipRequestBody);
+
+ collegeHoldingsStorageClient.disableFailureEmulation();
+
+ 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.getStatusCode(), is(200));
assertThat(postHoldingsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON));
Response sourceTenantHoldingsRecord1 = holdingsStorageClient.getById(createHoldingsRecord1);
- Response targetTenantHoldingsRecord1 = consortiumHoldingsStorageClient.getById(createHoldingsRecord1);
+ Response targetTenantHoldingsRecord1 = collegeHoldingsStorageClient.getById(createHoldingsRecord1);
- Assert.assertEquals(HttpStatus.SC_NOT_FOUND, sourceTenantHoldingsRecord1.getStatusCode());
- Assert.assertEquals(instanceId.toString(), targetTenantHoldingsRecord1.getJson().getString(INSTANCE_ID));
+ Assert.assertEquals(instanceId.toString(), sourceTenantHoldingsRecord1.getJson().getString(INSTANCE_ID));
+ Assert.assertEquals(HttpStatus.SC_NOT_FOUND, targetTenantHoldingsRecord1.getStatusCode());
+ }
+
+ @Test
+ public void cannotUpdateHoldingsRecordOwnershipDueToHoldingsRecordDeleteError() throws InterruptedException, MalformedURLException, TimeoutException, ExecutionException {
+ UUID instanceId = UUID.randomUUID();
+ JsonObject instance = smallAngryPlanet(instanceId);
- Response sourceTenantHoldingsRecord2 = holdingsStorageClient.getById(createHoldingsRecord1);
- Response targetTenantHoldingsRecord2 = consortiumHoldingsStorageClient.getById(createHoldingsRecord1);
+ InstanceApiClient.createInstance(okapiClient, instance.put("source", CONSORTIUM_FOLIO.getValue()));
+ InstanceApiClient.createInstance(consortiumOkapiClient, instance.put("source", FOLIO.getValue()));
- Assert.assertEquals(instanceId.toString(), sourceTenantHoldingsRecord2.getJson().getString(INSTANCE_ID));
- Assert.assertEquals(HttpStatus.SC_NOT_FOUND, targetTenantHoldingsRecord2.getStatusCode());
+ final UUID createHoldingsRecord1 = createHoldingForInstance(instanceId);
+
+ final JsonObject expectedErrorResponse = new JsonObject().put("message", "Server error");
+ collegeHoldingsStorageClient.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();
+
+ Response postHoldingsUpdateOwnershipResponse = updateHoldingsRecordsOwnership(holdingsRecordUpdateOwnershipRequestBody);
+
+ collegeHoldingsStorageClient.disableFailureEmulation();
+
+ 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.getStatusCode(), is(200));
+ assertThat(postHoldingsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON));
+
+ 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));
}
@Test
@@ -240,8 +531,8 @@ public void canUpdateHoldingsRecordOwnershipToDifferentInstanceWithExtraRedundan
UUID instanceId = UUID.randomUUID();
JsonObject instance = smallAngryPlanet(instanceId);
- InstanceApiClient.createInstance(okapiClient, instance);
- InstanceApiClient.createInstance(consortiumOkapiClient, instance);
+ InstanceApiClient.createInstance(okapiClient, instance.put("source", CONSORTIUM_FOLIO.getValue()));
+ InstanceApiClient.createInstance(consortiumOkapiClient, instance.put("source", FOLIO.getValue()));
JsonObject firstJsonHoldingsAsRequest = new HoldingRequestBuilder().forInstance(instanceId).create();
final UUID createHoldingsRecord1 = holdingsStorageClient.create(firstJsonHoldingsAsRequest
@@ -256,22 +547,22 @@ public void canUpdateHoldingsRecordOwnershipToDifferentInstanceWithExtraRedundan
.getId();
JsonObject holdingsRecordUpdateOwnershipRequestBody = new HoldingsRecordUpdateOwnershipRequestBuilder(instanceId,
- new JsonArray(List.of(createHoldingsRecord1.toString(), createHoldingsRecord2.toString())), ApiTestSuite.CONSORTIA_TENANT_ID).create();
+ new JsonArray(List.of(createHoldingsRecord1.toString(), createHoldingsRecord2.toString())), ApiTestSuite.COLLEGE_TENANT_ID).create();
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);
- Response targetTenantHoldingsRecord1 = consortiumHoldingsStorageClient.getById(createHoldingsRecord1);
+ Response targetTenantHoldingsRecord1 = collegeHoldingsStorageClient.getById(createHoldingsRecord1);
Assert.assertEquals(HttpStatus.SC_NOT_FOUND, sourceTenantHoldingsRecord1.getStatusCode());
Assert.assertEquals(instanceId.toString(), targetTenantHoldingsRecord1.getJson().getString(INSTANCE_ID));
- Response sourceTenantHoldingsRecord2 = holdingsStorageClient.getById(createHoldingsRecord1);
- Response targetTenantHoldingsRecord2 = consortiumHoldingsStorageClient.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));
@@ -280,18 +571,11 @@ public void canUpdateHoldingsRecordOwnershipToDifferentInstanceWithExtraRedundan
private Response updateHoldingsRecordsOwnership(JsonObject holdingsRecordUpdateOwnershipRequestBody) throws MalformedURLException, InterruptedException, ExecutionException, TimeoutException {
final var postHoldingRecordsUpdateOwnershipCompleted = okapiClient.post(
ApiRoot.updateHoldingsRecordsOwnership(), holdingsRecordUpdateOwnershipRequestBody);
- return postHoldingRecordsUpdateOwnershipCompleted.toCompletableFuture().get(5, TimeUnit.SECONDS);
+ return postHoldingRecordsUpdateOwnershipCompleted.toCompletableFuture().get(50, TimeUnit.SECONDS);
}
private UUID createHoldingForInstance(UUID instanceId) {
- return holdingsStorageClient.create(new HoldingRequestBuilder().forInstance(instanceId))
+ return holdingsStorageClient.create(new HoldingRequestBuilder().withHrId("hol0000001").forInstance(instanceId))
.getId();
}
-
- private UUID createHoldingForInstance(UUID id, UUID instanceId) {
- JsonObject obj = new HoldingRequestBuilder().forInstance(instanceId).create();
- obj.put("id", id.toString());
- holdingsStorageClient.create(obj);
- return id;
- }
}
diff --git a/src/test/java/api/support/ApiTests.java b/src/test/java/api/support/ApiTests.java
index b23394342..c1300a26b 100644
--- a/src/test/java/api/support/ApiTests.java
+++ b/src/test/java/api/support/ApiTests.java
@@ -19,6 +19,7 @@ public abstract class ApiTests {
private static boolean runningOnOwn;
protected static OkapiHttpClient okapiClient;
protected static OkapiHttpClient consortiumOkapiClient;
+ protected static OkapiHttpClient collegeOkapiClient;
protected final ResourceClient holdingsStorageClient;
protected final ResourceClient holdingsSourceStorageClient;
protected final ResourceClient itemsStorageClient;
@@ -35,6 +36,8 @@ public abstract class ApiTests {
protected final ResourceClient sourceRecordStorageClient;
protected final ResourceClient consortiumItemsClient;
protected final ResourceClient consortiumHoldingsStorageClient;
+ protected final ResourceClient collegeItemsClient;
+ protected final ResourceClient collegeHoldingsStorageClient;
protected final InstanceRelationshipTypeFixture instanceRelationshipTypeFixture;
protected final MarkItemFixture markItemFixture;
@@ -59,6 +62,9 @@ public ApiTests() {
consortiumHoldingsStorageClient = ResourceClient.forHoldingsStorage(consortiumOkapiClient);
consortiumItemsClient = ResourceClient.forItemsStorage(consortiumOkapiClient);
+
+ collegeHoldingsStorageClient = ResourceClient.forHoldingsStorage(collegeOkapiClient);
+ collegeItemsClient = ResourceClient.forItemsStorage(collegeOkapiClient);
}
@BeforeClass
@@ -76,6 +82,7 @@ public static void before()
okapiClient = ApiTestSuite.createOkapiHttpClient();
consortiumOkapiClient = ApiTestSuite.createOkapiHttpClient(ApiTestSuite.CONSORTIA_TENANT_ID);
+ collegeOkapiClient = ApiTestSuite.createOkapiHttpClient(ApiTestSuite.COLLEGE_TENANT_ID);
}
@AfterClass
diff --git a/src/test/java/api/support/builders/HoldingRequestBuilder.java b/src/test/java/api/support/builders/HoldingRequestBuilder.java
index cff460332..f8a587bae 100644
--- a/src/test/java/api/support/builders/HoldingRequestBuilder.java
+++ b/src/test/java/api/support/builders/HoldingRequestBuilder.java
@@ -20,6 +20,7 @@ public class HoldingRequestBuilder extends AbstractBuilder {
private final String callNumberTypeId;
private final UUID sourceId;
private final List administrativeNotes;
+ private final String hrId;
public HoldingRequestBuilder() {
this(
@@ -31,6 +32,7 @@ public HoldingRequestBuilder() {
null,
null,
FOLIO_SOURCE_HOLDINGS_ID,
+ null,
null);
}
@@ -43,7 +45,8 @@ private HoldingRequestBuilder(
String callNumberPrefix,
String callNumberTypeId,
UUID sourceId,
- List administrativeNotes) {
+ List administrativeNotes,
+ String hrId) {
this.instanceId = instanceId;
this.permanentLocationId = permanentLocationId;
@@ -54,6 +57,7 @@ private HoldingRequestBuilder(
this.callNumberTypeId = callNumberTypeId;
this.sourceId = sourceId;
this.administrativeNotes = administrativeNotes;
+ this.hrId = hrId;
}
@Override
@@ -72,6 +76,7 @@ public JsonObject create() {
holding.put("callNumberSuffix", callNumberSuffix);
holding.put("callNumberTypeId", callNumberTypeId);
holding.put("sourceId", sourceId);
+ holding.put("hrid", hrId);
return holding;
}
@@ -85,7 +90,8 @@ private HoldingRequestBuilder withPermanentLocation(UUID permanentLocationId) {
this.callNumberPrefix,
this.callNumberTypeId,
this.sourceId,
- this.administrativeNotes);
+ this.administrativeNotes,
+ this.hrId);
}
private HoldingRequestBuilder withTemporaryLocation(UUID temporaryLocationId) {
@@ -98,7 +104,8 @@ private HoldingRequestBuilder withTemporaryLocation(UUID temporaryLocationId) {
this.callNumberPrefix,
this.callNumberTypeId,
this.sourceId,
- this.administrativeNotes);
+ this.administrativeNotes,
+ this.hrId);
}
public JsonObject createFolioHoldingsSource() {
@@ -135,7 +142,8 @@ public HoldingRequestBuilder forInstance(UUID instanceId) {
this.callNumberPrefix,
this.callNumberTypeId,
this.sourceId,
- this.administrativeNotes);
+ this.administrativeNotes,
+ this.hrId);
}
public HoldingRequestBuilder withCallNumber(String callNumber) {
@@ -148,7 +156,8 @@ public HoldingRequestBuilder withCallNumber(String callNumber) {
this.callNumberPrefix,
this.callNumberTypeId,
this.sourceId,
- this.administrativeNotes);
+ this.administrativeNotes,
+ this.hrId);
}
public HoldingRequestBuilder withCallNumberSuffix(String suffix) {
@@ -161,7 +170,8 @@ public HoldingRequestBuilder withCallNumberSuffix(String suffix) {
this.callNumberPrefix,
this.callNumberTypeId,
this.sourceId,
- this.administrativeNotes);
+ this.administrativeNotes,
+ this.hrId);
}
public HoldingRequestBuilder withCallNumberPrefix(String prefix) {
@@ -174,7 +184,8 @@ public HoldingRequestBuilder withCallNumberPrefix(String prefix) {
prefix,
this.callNumberTypeId,
this.sourceId,
- this.administrativeNotes);
+ this.administrativeNotes,
+ this.hrId);
}
public HoldingRequestBuilder withCallNumberTypeId(String callNumberTypeId) {
@@ -187,7 +198,8 @@ public HoldingRequestBuilder withCallNumberTypeId(String callNumberTypeId) {
this.callNumberPrefix,
callNumberTypeId,
this.sourceId,
- this.administrativeNotes);
+ this.administrativeNotes,
+ this.hrId);
}
public HoldingRequestBuilder withMarcSource() {
@@ -200,7 +212,8 @@ public HoldingRequestBuilder withMarcSource() {
this.callNumberPrefix,
this.callNumberTypeId,
MARC_SOURCE_HOLDINGS_ID,
- this.administrativeNotes);
+ this.administrativeNotes,
+ this.hrId);
}
public HoldingRequestBuilder withAdministrativeNotes(List administrativeNotes) {
@@ -213,6 +226,21 @@ public HoldingRequestBuilder withAdministrativeNotes(List administrative
this.callNumberPrefix,
this.callNumberTypeId,
this.sourceId,
- administrativeNotes);
+ administrativeNotes,
+ this.hrId);
+ }
+
+ public HoldingRequestBuilder withHrId(String hrId) {
+ return new HoldingRequestBuilder(
+ this.instanceId,
+ this.permanentLocationId,
+ this.temporaryLocationId,
+ this.callNumber,
+ this.callNumberSuffix,
+ this.callNumberPrefix,
+ this.callNumberTypeId,
+ this.sourceId,
+ this.administrativeNotes,
+ hrId);
}
}
diff --git a/src/test/java/api/support/builders/HoldingsRecordUpdateOwnershipRequestBuilder.java b/src/test/java/api/support/builders/HoldingsRecordUpdateOwnershipRequestBuilder.java
index 2c4e22e0a..fe8e8009d 100644
--- a/src/test/java/api/support/builders/HoldingsRecordUpdateOwnershipRequestBuilder.java
+++ b/src/test/java/api/support/builders/HoldingsRecordUpdateOwnershipRequestBuilder.java
@@ -6,9 +6,9 @@
import java.util.UUID;
-import static api.ApiTestSuite.TENANT_ID;
import static org.folio.inventory.resources.MoveApi.HOLDINGS_RECORD_IDS;
import static org.folio.inventory.resources.MoveApi.TO_INSTANCE_ID;
+import static org.folio.inventory.support.MoveApiUtil.TARGET_TENANT_ID;
public class HoldingsRecordUpdateOwnershipRequestBuilder extends AbstractBuilder {
@@ -27,7 +27,7 @@ public JsonObject create() {
includeWhenPresent(holdingsRecordUpdateOwnershipRequest, TO_INSTANCE_ID, toInstanceId);
includeWhenPresent(holdingsRecordUpdateOwnershipRequest, HOLDINGS_RECORD_IDS, holdingsRecordsIds);
- includeWhenPresent(holdingsRecordUpdateOwnershipRequest, TENANT_ID, tenantId);
+ includeWhenPresent(holdingsRecordUpdateOwnershipRequest, TARGET_TENANT_ID, tenantId);
return holdingsRecordUpdateOwnershipRequest;
}
diff --git a/src/test/java/support/fakes/FakeStorageModuleBuilder.java b/src/test/java/support/fakes/FakeStorageModuleBuilder.java
index 2ed59cf63..9ce8f1a18 100644
--- a/src/test/java/support/fakes/FakeStorageModuleBuilder.java
+++ b/src/test/java/support/fakes/FakeStorageModuleBuilder.java
@@ -24,7 +24,7 @@ public class FakeStorageModuleBuilder {
private final List recordPreProcessors;
FakeStorageModuleBuilder() {
- this(null, null, List.of(ApiTestSuite.TENANT_ID, ApiTestSuite.CONSORTIA_TENANT_ID), new ArrayList<>(), true, "",
+ this(null, null, List.of(ApiTestSuite.TENANT_ID, ApiTestSuite.CONSORTIA_TENANT_ID, ApiTestSuite.COLLEGE_TENANT_ID), new ArrayList<>(), true, "",
new ArrayList<>(), new HashMap<>(), Collections.emptyList());
}