From e8d11c091cc1527716ec4dad0e26c8c5ebc51228 Mon Sep 17 00:00:00 2001
From: RomanChernetskyi <111740564+RomanChernetskyi@users.noreply.github.com>
Date: Wed, 5 Jun 2024 15:55:58 +0300
Subject: [PATCH] [MODINV-1029] Declare an interface to update ownership of
Holdings and Items (#725)
---
descriptors/ModuleDescriptor-template.json | 45 ++-
ramls/holdings_update_ownership.json | 28 ++
ramls/inventory-update-ownership.raml | 66 ++++
ramls/items_update_ownership.json | 28 ++
.../folio/inventory/InventoryVerticle.java | 2 +
.../resources/UpdateOwnershipApi.java | 28 ++
src/test/java/api/ApiTestSuite.java | 24 +-
.../java/api/InstanceRelationshipsTest.java | 19 +-
.../HoldingsUpdateOwnershipApiTest.java | 297 ++++++++++++++++++
.../api/items/ItemUpdateOwnershipApiTest.java | 288 +++++++++++++++++
src/test/java/api/support/ApiRoot.java | 12 +
src/test/java/api/support/ApiTests.java | 7 +
...gsRecordUpdateOwnershipRequestBuilder.java | 34 ++
.../ItemsUpdateOwnershipRequestBuilder.java | 33 ++
.../java/support/fakes/FakeStorageModule.java | 4 +-
.../fakes/FakeStorageModuleBuilder.java | 22 +-
16 files changed, 906 insertions(+), 31 deletions(-)
create mode 100644 ramls/holdings_update_ownership.json
create mode 100644 ramls/inventory-update-ownership.raml
create mode 100644 ramls/items_update_ownership.json
create mode 100644 src/main/java/org/folio/inventory/resources/UpdateOwnershipApi.java
create mode 100644 src/test/java/api/holdings/HoldingsUpdateOwnershipApiTest.java
create mode 100644 src/test/java/api/items/ItemUpdateOwnershipApiTest.java
create mode 100644 src/test/java/api/support/builders/HoldingsRecordUpdateOwnershipRequestBuilder.java
create mode 100644 src/test/java/api/support/builders/ItemsUpdateOwnershipRequestBuilder.java
diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json
index 39b727800..d614c5b3b 100644
--- a/descriptors/ModuleDescriptor-template.json
+++ b/descriptors/ModuleDescriptor-template.json
@@ -536,6 +536,37 @@
}
]
},
+ {
+ "id": "inventory-update-ownership",
+ "version": "1.0",
+ "handlers": [
+ {
+ "methods": ["POST"],
+ "pathPattern": "/inventory/items/update-ownership",
+ "permissionsRequired": ["inventory.items.update-ownership.item.post"],
+ "modulePermissions": [
+ "inventory-storage.items.item.post",
+ "inventory-storage.items.item.delete",
+ "inventory-storage.items.collection.get",
+ "inventory-storage.holdings.item.get"
+ ]
+ },
+ {
+ "methods": ["POST"],
+ "pathPattern": "/inventory/holdings/update-ownership",
+ "permissionsRequired": ["inventory.holdings.update-ownership.item.post"],
+ "modulePermissions": [
+ "inventory-storage.holdings.item.post",
+ "inventory-storage.holdings.item.delete",
+ "inventory-storage.holdings.collection.get",
+ "inventory-storage.items.item.post",
+ "inventory-storage.items.item.delete",
+ "inventory-storage.items.collection.get",
+ "inventory-storage.instances.item.get"
+ ]
+ }
+ ]
+ },
{
"id": "_tenant",
"version": "1.2",
@@ -706,6 +737,11 @@
"displayName": "Inventory - mark an item as unknown",
"description": "Mark an item as unknown"
},
+ {
+ "permissionName": "inventory.items.update-ownership.item.post",
+ "displayName": "Inventory - update an item ownership",
+ "description": "Update an item ownership"
+ },
{
"permissionName": "inventory.holdings.move.item.post",
@@ -742,6 +778,11 @@
"displayName": "Inventory - modify holdings",
"description": "Modify individual instance"
},
+ {
+ "permissionName": "inventory.holdings.update-ownership.item.post",
+ "displayName": "Inventory - update holdings ownership",
+ "description": "Update holdings record ownership"
+ },
{
"permissionName": "inventory.instances.item.get",
"displayName": "Inventory - get individual instance",
@@ -803,7 +844,9 @@
"inventory.items.item.mark-in-process-non-requestable.post",
"inventory.items.item.mark-missing.post",
"inventory.items.move.item.post",
- "inventory.holdings.move.item.post"
+ "inventory.holdings.move.item.post",
+ "inventory.items.update-ownership.item.post",
+ "inventory.holdings.update-ownership.item.post"
]
},
{
diff --git a/ramls/holdings_update_ownership.json b/ramls/holdings_update_ownership.json
new file mode 100644
index 000000000..e185dcfbc
--- /dev/null
+++ b/ramls/holdings_update_ownership.json
@@ -0,0 +1,28 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Ids holder for updating ownership of the holdings records",
+ "type": "object",
+ "properties": {
+ "toInstanceId": {
+ "description": "Id of the shared Instance, ownership of Holdings linked to this Instance will be changed.",
+ "$ref": "uuid.json"
+ },
+ "holdingsRecordIds": {
+ "description": "Ids of the holdings to update ownership.",
+ "type": "array",
+ "items": {
+ "$ref": "uuid.json"
+ }
+ },
+ "targetTenantId": {
+ "description": "Target tenant Id, where selected Holdings will be created.",
+ "type": "string"
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "toInstanceId",
+ "holdingsRecordIds",
+ "targetTenantId"
+ ]
+}
diff --git a/ramls/inventory-update-ownership.raml b/ramls/inventory-update-ownership.raml
new file mode 100644
index 000000000..c81864eb0
--- /dev/null
+++ b/ramls/inventory-update-ownership.raml
@@ -0,0 +1,66 @@
+#%RAML 1.0
+title: Inventory Update Ownership API
+version: v0.1
+protocols: [ HTTP, HTTPS ]
+baseUri: http://localhost
+
+documentation:
+ - title: "Inventory Update Ownership API"
+ content: API for updating ownership of holdings records and items between ECS tenants
+
+types:
+ errors: !include raml-util/schemas/errors.schema
+ items_update_ownership: !include items_update_ownership.json
+ holdings_update_ownership: !include holdings_update_ownership.json
+ move_response: !include move_response.json
+
+traits:
+ language: !include raml-util/traits/language.raml
+ validate: !include raml-util/traits/validation.raml
+
+/inventory/items/update-ownership:
+ displayName: Items Update Ownership
+ post:
+ is: [validate]
+ body:
+ application/json:
+ type: items_update_ownership
+ responses:
+ 200:
+ description: "Items ownership updated to another tenant"
+ body:
+ application/json:
+ type: move_response
+ 422:
+ description: "Validation error"
+ body:
+ application/json:
+ type: errors
+ 500:
+ description: "Internal server error"
+ body:
+ text/plain:
+ example: "Internal server error"
+/inventory/holdings/update-ownership:
+ displayName: Holdings Record Update Ownership
+ post:
+ is: [validate]
+ body:
+ application/json:
+ type: holdings_update_ownership
+ responses:
+ 200:
+ description: "Holdings record ownership updated to another tenant"
+ body:
+ application/json:
+ type: move_response
+ 422:
+ description: "Validation error"
+ body:
+ application/json:
+ type: errors
+ 500:
+ description: "Internal server error"
+ body:
+ text/plain:
+ example: "Internal server error"
diff --git a/ramls/items_update_ownership.json b/ramls/items_update_ownership.json
new file mode 100644
index 000000000..b6221e93c
--- /dev/null
+++ b/ramls/items_update_ownership.json
@@ -0,0 +1,28 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Ids holder for updating ownership of the items",
+ "type": "object",
+ "properties": {
+ "toHoldingsRecordId": {
+ "description": "Id of the Holdings Record, ownership of Items linked to this Holdings Record will be changed.",
+ "$ref": "uuid.json"
+ },
+ "itemIds": {
+ "description": "Ids of the items to update ownership.",
+ "type": "array",
+ "items": {
+ "$ref": "uuid.json"
+ }
+ },
+ "targetTenantId": {
+ "description": "Target tenant Id, where selected Items will be created.",
+ "type": "string"
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "toHoldingsRecordId",
+ "itemIds",
+ "targetTenantId"
+ ]
+}
diff --git a/src/main/java/org/folio/inventory/InventoryVerticle.java b/src/main/java/org/folio/inventory/InventoryVerticle.java
index 08a08b54b..8079714c6 100644
--- a/src/main/java/org/folio/inventory/InventoryVerticle.java
+++ b/src/main/java/org/folio/inventory/InventoryVerticle.java
@@ -19,6 +19,7 @@
import org.folio.inventory.resources.ItemsByHoldingsRecordId;
import org.folio.inventory.resources.MoveApi;
import org.folio.inventory.resources.TenantApi;
+import org.folio.inventory.resources.UpdateOwnershipApi;
import org.folio.inventory.storage.Storage;
import io.vertx.core.AbstractVerticle;
@@ -69,6 +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);
Handler> onHttpServerStart = result -> {
if (result.succeeded()) {
diff --git a/src/main/java/org/folio/inventory/resources/UpdateOwnershipApi.java b/src/main/java/org/folio/inventory/resources/UpdateOwnershipApi.java
new file mode 100644
index 000000000..339fb8886
--- /dev/null
+++ b/src/main/java/org/folio/inventory/resources/UpdateOwnershipApi.java
@@ -0,0 +1,28 @@
+package org.folio.inventory.resources;
+
+import io.vertx.core.http.HttpClient;
+import io.vertx.ext.web.Router;
+import io.vertx.ext.web.RoutingContext;
+import org.folio.inventory.storage.Storage;
+
+public class UpdateOwnershipApi extends AbstractInventoryResource {
+ public UpdateOwnershipApi(Storage storage, HttpClient client) {
+ super(storage, client);
+ }
+
+ @Override
+ public void register(Router router) {
+ router.post("/inventory/items/update-ownership")
+ .handler(this::updateItemsOwnership);
+ router.post("/inventory/holdings/update-ownership")
+ .handler(this::updateHoldingsOwnership);
+ }
+
+ private void updateHoldingsOwnership(RoutingContext routingContext) {
+ // should be implemented in MODINV-1031
+ }
+
+ private void updateItemsOwnership(RoutingContext routingContext) {
+ // should be implemented in MODINV-1031
+ }
+}
diff --git a/src/test/java/api/ApiTestSuite.java b/src/test/java/api/ApiTestSuite.java
index ca5dc7344..2373dc0fd 100644
--- a/src/test/java/api/ApiTestSuite.java
+++ b/src/test/java/api/ApiTestSuite.java
@@ -79,6 +79,8 @@ public class ApiTestSuite {
public static final String TOKEN = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsInRlbmFudCI6ImRlbW9fdGVuYW50In0.29VPjLI6fLJzxQW0UhQ0jsvAn8xHz501zyXAxRflXfJ9wuDzT8TDf-V75PjzD7fe2kHjSV2dzRXbstt3BTtXIQ";
public static final String USER_ID = "7e115dfb-d1d6-46ac-b2dc-2b3e74cda694";
+ public static final String CENTRAL_TENANT_ID_FIELD = "centralTenantId";
+ public static final String CONSORTIUM_ID_FIELD = "consortiumId";
private static String bookMaterialTypeId;
private static String dvdMaterialTypeId;
@@ -218,10 +220,16 @@ public static String getBibliographyNatureOfContentTermId() {
public static OkapiHttpClient createOkapiHttpClient()
throws MalformedURLException {
+ return createOkapiHttpClient(TENANT_ID);
+ }
+
+ public static OkapiHttpClient createOkapiHttpClient(String tenantId)
+ throws MalformedURLException {
+
return new OkapiHttpClient(
vertxAssistant.getVertx(),
- new URL(storageOkapiUrl()), TENANT_ID, TOKEN, USER_ID, null,
- it -> System.out.println(String.format("Request failed: %s", it.toString())));
+ new URL(storageOkapiUrl()), tenantId, TOKEN, USER_ID, null,
+ it -> System.out.printf("Request failed: %s%n", it.toString()));
}
public static String storageOkapiUrl() {
@@ -449,6 +457,18 @@ private static void createNatureOfContentTerms()
);
}
+ public static void createConsortiumTenant() throws MalformedURLException {
+ String expectedConsortiumId = UUID.randomUUID().toString();
+
+ JsonObject userTenantsCollection = new JsonObject()
+ .put(ApiTestSuite.CENTRAL_TENANT_ID_FIELD, ApiTestSuite.CONSORTIA_TENANT_ID)
+ .put(ApiTestSuite.CONSORTIUM_ID_FIELD, expectedConsortiumId);
+
+ ResourceClient client = ResourceClient.forUserTenants(createOkapiHttpClient());
+
+ client.create(userTenantsCollection);
+ }
+
private static void createIdentifierTypes()
throws MalformedURLException,
InterruptedException,
diff --git a/src/test/java/api/InstanceRelationshipsTest.java b/src/test/java/api/InstanceRelationshipsTest.java
index 172cf5c66..c120a1ef2 100644
--- a/src/test/java/api/InstanceRelationshipsTest.java
+++ b/src/test/java/api/InstanceRelationshipsTest.java
@@ -1,5 +1,6 @@
package api;
+import static api.ApiTestSuite.createConsortiumTenant;
import static api.support.InstanceSamples.nod;
import static api.support.InstanceSamples.smallAngryPlanet;
import static io.vertx.core.json.JsonObject.mapFrom;
@@ -34,8 +35,6 @@
public class InstanceRelationshipsTest extends ApiTests {
private static final String PARENT_INSTANCES = "parentInstances";
- private static final String CENTRAL_TENANT_ID_FIELD = "centralTenantId";
- private static final String CONSORTIUM_ID_FIELD = "consortiumId";
@After
public void disableFailureEmulationAndClearConsortia() throws Exception {
@@ -490,7 +489,7 @@ public void canUpdateInstanceWithChildInstancesWhenParentInstancesAlreadySet() t
public void cannotLinkLocalInstanceToSharedInstance() throws MalformedURLException, ExecutionException, InterruptedException, TimeoutException {
UUID parentId = UUID.randomUUID();
- initConsortiumTenant();
+ createConsortiumTenant();
final JsonObject parentRelationship = createParentRelationship(parentId.toString(),
instanceRelationshipTypeFixture.boundWith().getId());
@@ -503,8 +502,8 @@ public void cannotLinkLocalInstanceToSharedInstance() throws MalformedURLExcepti
}
@Test
- public void canCreateInstanceWithParentInstancesWhenConsortiaEnabled() {
- initConsortiumTenant();
+ public void canCreateInstanceWithParentInstancesWhenConsortiaEnabled() throws MalformedURLException {
+ createConsortiumTenant();
final IndividualResource parentInstance = instancesClient.create(nod(UUID.randomUUID()));
@@ -518,16 +517,6 @@ public void canCreateInstanceWithParentInstancesWhenConsortiaEnabled() {
is(parentRelationship));
}
- private void initConsortiumTenant() {
- String expectedConsortiumId = UUID.randomUUID().toString();
-
- JsonObject userTenantsCollection = new JsonObject()
- .put(CENTRAL_TENANT_ID_FIELD, ApiTestSuite.CONSORTIA_TENANT_ID)
- .put(CONSORTIUM_ID_FIELD, expectedConsortiumId);
-
- userTenantsClient.create(userTenantsCollection);
- }
-
private JsonObject createParentRelationship(String superInstanceId, String relationshipType) {
return mapFrom(new InstanceRelationshipToParent(UUID.randomUUID().toString(),
superInstanceId, relationshipType));
diff --git a/src/test/java/api/holdings/HoldingsUpdateOwnershipApiTest.java b/src/test/java/api/holdings/HoldingsUpdateOwnershipApiTest.java
new file mode 100644
index 000000000..28f8145e1
--- /dev/null
+++ b/src/test/java/api/holdings/HoldingsUpdateOwnershipApiTest.java
@@ -0,0 +1,297 @@
+package api.holdings;
+
+import api.ApiTestSuite;
+import api.support.ApiRoot;
+import api.support.ApiTests;
+import api.support.InstanceApiClient;
+import api.support.builders.HoldingRequestBuilder;
+import api.support.builders.HoldingsRecordUpdateOwnershipRequestBuilder;
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.JsonObject;
+import junitparams.JUnitParamsRunner;
+import org.apache.http.HttpStatus;
+import org.folio.inventory.support.http.client.Response;
+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 java.net.MalformedURLException;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+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.support.http.ContentType.APPLICATION_JSON;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+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";
+
+ @Before
+ public void initConsortia() throws Exception {
+ createConsortiumTenant();
+ }
+
+ @After
+ public void clearConsortia() throws Exception {
+ userTenantsClient.deleteAll();
+ }
+
+ @Test
+ public void canUpdateHoldingsOwnershipToDifferentTenant() throws MalformedURLException, ExecutionException, InterruptedException, 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(instanceId);
+
+ JsonObject holdingsRecordUpdateOwnershipRequestBody = new HoldingsRecordUpdateOwnershipRequestBuilder(instanceId,
+ new JsonArray(List.of(createHoldingsRecord1.toString(), createHoldingsRecord2.toString())), ApiTestSuite.CONSORTIA_TENANT_ID).create();
+
+ Response postHoldingsUpdateOwnershipResponse = updateHoldingsRecordsOwnership(holdingsRecordUpdateOwnershipRequestBody);
+
+ assertThat(postHoldingsUpdateOwnershipResponse.getStatusCode(), is(200));
+ assertThat(new JsonObject(postHoldingsUpdateOwnershipResponse.getBody()).getJsonArray("nonUpdatedIds").size(), is(0));
+ assertThat(postHoldingsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON));
+
+ Response sourceTenantHoldingsRecord1 = holdingsStorageClient.getById(createHoldingsRecord1);
+ Response targetTenantHoldingsRecord1 = consortiumHoldingsStorageClient.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);
+
+ Assert.assertEquals(HttpStatus.SC_NOT_FOUND, sourceTenantHoldingsRecord2.getStatusCode());
+ Assert.assertEquals(instanceId.toString(), targetTenantHoldingsRecord2.getJson().getString(INSTANCE_ID));
+ }
+
+ @Test
+ public void shouldReportErrorsWhenOnlySomeRequestedHoldingsRecordsCouldNotBeUpdated() throws InterruptedException, MalformedURLException, TimeoutException, ExecutionException {
+ UUID instanceId = UUID.randomUUID();
+ JsonObject instance = smallAngryPlanet(instanceId);
+
+ InstanceApiClient.createInstance(okapiClient, instance);
+ InstanceApiClient.createInstance(consortiumOkapiClient, instance);
+
+ final UUID createHoldingsRecord1 = createHoldingForInstance(instanceId);
+ final UUID createHoldingsRecord2 = UUID.randomUUID();
+
+ Assert.assertNotEquals(createHoldingsRecord1, createHoldingsRecord2);
+
+ JsonObject holdingsRecordUpdateOwnershipRequestBody = new HoldingsRecordUpdateOwnershipRequestBuilder(instanceId,
+ new JsonArray(List.of(createHoldingsRecord1.toString(), createHoldingsRecord2.toString())), ApiTestSuite.CONSORTIA_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();
+
+ assertThat(notFoundIds.size(), is(1));
+ assertThat(notFoundIds.get(0), equalTo(createHoldingsRecord2.toString()));
+
+ Response sourceTenantHoldingsRecord1 = holdingsStorageClient.getById(createHoldingsRecord1);
+ Response targetTenantHoldingsRecord1 = consortiumHoldingsStorageClient.getById(createHoldingsRecord1);
+
+ Assert.assertEquals(HttpStatus.SC_NOT_FOUND, sourceTenantHoldingsRecord1.getStatusCode());
+ assertThat(instanceId.toString(), equalTo(targetTenantHoldingsRecord1.getJson().getString(INSTANCE_ID)));
+
+ Response targetTenantHoldingsRecord2 = consortiumHoldingsStorageClient.getById(createHoldingsRecord1);
+ Assert.assertEquals(HttpStatus.SC_NOT_FOUND, targetTenantHoldingsRecord2.getStatusCode());
+ }
+
+ @Test
+ public void cannotUpdateHoldingsRecordsOwnershipToUnspecifiedInstance()
+ throws InterruptedException, MalformedURLException, TimeoutException, ExecutionException {
+ JsonObject holdingsRecordUpdateOwnershipWithoutToInstanceId = new HoldingsRecordUpdateOwnershipRequestBuilder(null,
+ new JsonArray(List.of(UUID.randomUUID())), ApiTestSuite.CONSORTIA_TENANT_ID).create();
+
+ Response postHoldingsUpdateOwnershipResponse = updateHoldingsRecordsOwnership(holdingsRecordUpdateOwnershipWithoutToInstanceId);
+
+ assertThat(postHoldingsUpdateOwnershipResponse.getStatusCode(), is(422));
+ assertThat(postHoldingsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON));
+
+ assertThat(postHoldingsUpdateOwnershipResponse, hasValidationError(
+ "toInstanceId is a required field", "toInstanceId", null
+ ));
+ }
+
+ @Test
+ public void cannotUpdateHoldingsRecordsOwnershipToUnspecifiedTenant()
+ throws InterruptedException, MalformedURLException, TimeoutException, ExecutionException {
+ JsonObject holdingsRecordUpdateOwnershipWithoutTenantId = new HoldingsRecordUpdateOwnershipRequestBuilder(UUID.randomUUID(),
+ new JsonArray(List.of(UUID.randomUUID())), null).create();
+
+ Response postHoldingsUpdateOwnershipResponse = updateHoldingsRecordsOwnership(holdingsRecordUpdateOwnershipWithoutTenantId);
+
+ assertThat(postHoldingsUpdateOwnershipResponse.getStatusCode(), is(422));
+ assertThat(postHoldingsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON));
+
+ assertThat(postHoldingsUpdateOwnershipResponse, hasValidationError(
+ "tenantId is a required field", "toInstanceId", null
+ ));
+ }
+
+ @Test
+ public void cannotUpdateUnspecifiedHoldingsRecordsOwnership()
+ throws MalformedURLException, InterruptedException, ExecutionException, TimeoutException {
+ JsonObject holdingsRecordUpdateOwnershipWithoutHoldingsRecordIds = new HoldingsRecordUpdateOwnershipRequestBuilder(UUID.randomUUID(),
+ new JsonArray(), ApiTestSuite.CONSORTIA_TENANT_ID).create();
+
+ Response postHoldingsUpdateOwnershipResponse = updateHoldingsRecordsOwnership(holdingsRecordUpdateOwnershipWithoutHoldingsRecordIds);
+
+ assertThat(postHoldingsUpdateOwnershipResponse.getStatusCode(), is(422));
+ assertThat(postHoldingsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON));
+
+ assertThat(postHoldingsUpdateOwnershipResponse, hasValidationError(
+ "Holdings record ids aren't specified", "holdingsRecordIds", null
+ ));
+ }
+
+ @Test
+ public void cannotUpdateHoldingsRecordOwnershipOfNonExistedInstance()
+ throws MalformedURLException, InterruptedException, ExecutionException, TimeoutException {
+ createConsortiumTenant();
+
+ UUID instanceId = UUID.randomUUID();
+ JsonObject instance = smallAngryPlanet(instanceId);
+
+ UUID invalidInstanceId = UUID.randomUUID();
+
+ InstanceApiClient.createInstance(okapiClient, instance);
+ InstanceApiClient.createInstance(consortiumOkapiClient, instance);
+
+ final UUID createHoldingsRecord1 = createHoldingForInstance(instanceId);
+
+ JsonObject holdingsRecordUpdateOwnershipWithoutHoldingsRecordIds = new HoldingsRecordUpdateOwnershipRequestBuilder(invalidInstanceId,
+ new JsonArray(List.of(createHoldingsRecord1)), ApiTestSuite.CONSORTIA_TENANT_ID).create();
+
+ Response postHoldingsUpdateOwnershipResponse = updateHoldingsRecordsOwnership(holdingsRecordUpdateOwnershipWithoutHoldingsRecordIds);
+
+ assertThat(postHoldingsUpdateOwnershipResponse.getStatusCode(), is(422));
+ assertThat(postHoldingsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON));
+
+ assertThat(postHoldingsUpdateOwnershipResponse.getBody(), containsString("errors"));
+ assertThat(postHoldingsUpdateOwnershipResponse.getBody(), containsString(invalidInstanceId.toString()));
+ }
+
+ @Test
+ public void canUpdateHoldingsRecordOwnershipDueToHoldingsRecordUpdateError() throws InterruptedException, MalformedURLException, TimeoutException, ExecutionException {
+ 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);
+
+ JsonObject holdingsRecordUpdateOwnershipRequestBody = new HoldingsRecordUpdateOwnershipRequestBuilder(instanceId,
+ new JsonArray(List.of(createHoldingsRecord1.toString(), createHoldingsRecord2.toString())), ApiTestSuite.CONSORTIA_TENANT_ID).create();
+
+ Response postHoldingsUpdateOwnershipResponse = updateHoldingsRecordsOwnership(holdingsRecordUpdateOwnershipRequestBody);
+
+ List nonUpdatedIdsIds = postHoldingsUpdateOwnershipResponse.getJson()
+ .getJsonArray("nonUpdatedIds")
+ .getList();
+
+ assertThat(nonUpdatedIdsIds.size(), is(1));
+ assertThat(nonUpdatedIdsIds.get(0), equalTo(ID_FOR_FAILURE.toString()));
+
+ assertThat(postHoldingsUpdateOwnershipResponse.getStatusCode(), is(200));
+ assertThat(postHoldingsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON));
+
+ Response sourceTenantHoldingsRecord1 = holdingsStorageClient.getById(createHoldingsRecord1);
+ Response targetTenantHoldingsRecord1 = consortiumHoldingsStorageClient.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);
+
+ Assert.assertEquals(instanceId.toString(), sourceTenantHoldingsRecord2.getJson().getString(INSTANCE_ID));
+ Assert.assertEquals(HttpStatus.SC_NOT_FOUND, targetTenantHoldingsRecord2.getStatusCode());
+ }
+
+ @Test
+ public void canUpdateHoldingsRecordOwnershipToDifferentInstanceWithExtraRedundantFields() throws InterruptedException, MalformedURLException, TimeoutException, ExecutionException {
+ UUID instanceId = UUID.randomUUID();
+ JsonObject instance = smallAngryPlanet(instanceId);
+
+ InstanceApiClient.createInstance(okapiClient, instance);
+ InstanceApiClient.createInstance(consortiumOkapiClient, instance);
+
+ JsonObject firstJsonHoldingsAsRequest = new HoldingRequestBuilder().forInstance(instanceId).create();
+ final UUID createHoldingsRecord1 = holdingsStorageClient.create(firstJsonHoldingsAsRequest
+ .put("holdingsItems", new JsonArray().add(new JsonObject().put("id", UUID.randomUUID())).add(new JsonObject().put("id", UUID.randomUUID())))
+ .put("bareHoldingsItems", new JsonArray().add(new JsonObject().put("id", UUID.randomUUID())).add(new JsonObject().put("id", UUID.randomUUID()))))
+ .getId();
+
+ JsonObject secondJsonHoldingsAsRequest = new HoldingRequestBuilder().forInstance(instanceId).create();
+ final UUID createHoldingsRecord2 = holdingsStorageClient.create(secondJsonHoldingsAsRequest
+ .put("holdingsItems", new JsonArray().add(new JsonObject().put("id", UUID.randomUUID())).add(new JsonObject().put("id", UUID.randomUUID())))
+ .put("bareHoldingsItems", new JsonArray().add(new JsonObject().put("id", UUID.randomUUID())).add(new JsonObject().put("id", UUID.randomUUID()))))
+ .getId();
+
+ JsonObject holdingsRecordUpdateOwnershipRequestBody = new HoldingsRecordUpdateOwnershipRequestBuilder(instanceId,
+ new JsonArray(List.of(createHoldingsRecord1.toString(), createHoldingsRecord2.toString())), ApiTestSuite.CONSORTIA_TENANT_ID).create();
+
+ Response postHoldingsUpdateOwnershipResponse = updateHoldingsRecordsOwnership(holdingsRecordUpdateOwnershipRequestBody);
+
+ assertThat(postHoldingsUpdateOwnershipResponse.getStatusCode(), is(200));
+ assertThat(new JsonObject(postHoldingsUpdateOwnershipResponse.getBody()).getJsonArray("nonUpdatedIds").size(), is(0));
+ assertThat(postHoldingsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON));
+
+ Response sourceTenantHoldingsRecord1 = holdingsStorageClient.getById(createHoldingsRecord1);
+ Response targetTenantHoldingsRecord1 = consortiumHoldingsStorageClient.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);
+
+ Assert.assertEquals(HttpStatus.SC_NOT_FOUND, sourceTenantHoldingsRecord2.getStatusCode());
+ Assert.assertEquals(instanceId.toString(), targetTenantHoldingsRecord2.getJson().getString(INSTANCE_ID));
+ }
+
+ 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);
+ }
+
+ private UUID createHoldingForInstance(UUID instanceId) {
+ return holdingsStorageClient.create(new HoldingRequestBuilder().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/items/ItemUpdateOwnershipApiTest.java b/src/test/java/api/items/ItemUpdateOwnershipApiTest.java
new file mode 100644
index 000000000..e6ab0eeca
--- /dev/null
+++ b/src/test/java/api/items/ItemUpdateOwnershipApiTest.java
@@ -0,0 +1,288 @@
+package api.items;
+
+import api.ApiTestSuite;
+import api.support.ApiRoot;
+import api.support.ApiTests;
+import api.support.InstanceApiClient;
+import api.support.builders.HoldingRequestBuilder;
+import api.support.builders.ItemRequestBuilder;
+import api.support.builders.ItemsUpdateOwnershipRequestBuilder;
+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.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.MalformedURLException;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+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.support.ItemUtil.HOLDINGS_RECORD_ID;
+import static org.folio.inventory.support.JsonArrayHelper.toList;
+import static org.folio.inventory.support.JsonArrayHelper.toListOfStrings;
+import static org.folio.inventory.support.http.ContentType.APPLICATION_JSON;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
+
+@Ignore
+@RunWith(JUnitParamsRunner.class)
+public class ItemUpdateOwnershipApiTest extends ApiTests {
+
+ @Before
+ public void initConsortia() throws Exception {
+ createConsortiumTenant();
+ }
+
+ @After
+ public void clearConsortia() throws Exception {
+ userTenantsClient.deleteAll();
+ }
+
+ @Test
+ public void canUpdateItemsOwnershipToDifferentTenant() throws MalformedURLException, ExecutionException, InterruptedException, TimeoutException {
+ UUID sourceInstanceId = UUID.randomUUID();
+ JsonObject sourceInstance = smallAngryPlanet(sourceInstanceId);
+
+ UUID targetInstanceId = UUID.randomUUID();
+ JsonObject targetInstance = smallAngryPlanet(targetInstanceId);
+
+ InstanceApiClient.createInstance(okapiClient, sourceInstance);
+ InstanceApiClient.createInstance(consortiumOkapiClient, targetInstance);
+
+ final UUID createHoldingsRecord1 = createHoldingForInstance(sourceInstanceId);
+ final UUID createHoldingsRecord2 = createHoldingForInstanceAtConsortium(targetInstanceId);
+
+ final var firstItem = itemsClient.create(
+ new ItemRequestBuilder()
+ .forHolding(createHoldingsRecord1)
+ .withBarcode("645398607547")
+ .withStatus(ItemStatusName.AVAILABLE.value()));
+
+ final var secondItem = itemsClient.create(
+ new ItemRequestBuilder()
+ .forHolding(createHoldingsRecord1)
+ .withBarcode("645398607546")
+ .withStatus(ItemStatusName.AVAILABLE.value()));
+
+ JsonObject itemsUpdateOwnershipRequestBody = new ItemsUpdateOwnershipRequestBuilder(createHoldingsRecord2,
+ new JsonArray(List.of(firstItem.getId().toString(), secondItem.getId().toString())), ApiTestSuite.CONSORTIA_TENANT_ID).create();
+
+ Response postItemsUpdateOwnershipResponse = updateItemsOwnership(itemsUpdateOwnershipRequestBody);
+
+ assertThat(postItemsUpdateOwnershipResponse.getStatusCode(), is(200));
+ assertThat(toList(postItemsUpdateOwnershipResponse.getJson(), "nonUpdatedIds"), hasSize(0));
+ assertThat(postItemsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON));
+
+ final var sourceFirstUpdatedItem = itemsClient.getById(firstItem.getId());
+ final var targetFirstUpdatedItem = consortiumItemsClient.getById(firstItem.getId());
+
+ final var sourceSecondUpdatedItem = itemsClient.getById(secondItem.getId());
+ final var targetSecondUpdatedItem = consortiumItemsClient.getById(secondItem.getId());
+
+ assertThat(HttpStatus.SC_NOT_FOUND, is(sourceFirstUpdatedItem.getStatusCode()));
+ assertThat(targetFirstUpdatedItem.getJson().getString(HOLDINGS_RECORD_ID), is(createHoldingsRecord2.toString()));
+
+ assertThat(HttpStatus.SC_NOT_FOUND, is(sourceSecondUpdatedItem.getStatusCode()));
+ assertThat(targetSecondUpdatedItem.getJson().getString(HOLDINGS_RECORD_ID), is(createHoldingsRecord2.toString()));
+ }
+
+ @Test
+ public void shouldReportErrorsWhenOnlySomeRequestedItemsOwnershipCouldNotBeUpdated() throws MalformedURLException, ExecutionException, InterruptedException, TimeoutException {
+ UUID sourceInstanceId = UUID.randomUUID();
+ JsonObject sourceInstance = smallAngryPlanet(sourceInstanceId);
+
+ UUID targetInstanceId = UUID.randomUUID();
+ JsonObject targetInstance = smallAngryPlanet(targetInstanceId);
+
+ InstanceApiClient.createInstance(okapiClient, sourceInstance);
+ InstanceApiClient.createInstance(consortiumOkapiClient, targetInstance);
+
+ final UUID createHoldingsRecord1 = createHoldingForInstance(sourceInstanceId);
+ final UUID createHoldingsRecord2 = createHoldingForInstanceAtConsortium(targetInstanceId);
+
+ final var item = itemsClient.create(
+ new ItemRequestBuilder()
+ .forHolding(createHoldingsRecord1)
+ .withBarcode("645398607547")
+ .withStatus(ItemStatusName.AVAILABLE.value()));
+
+ final var nonExistentItemId = UUID.randomUUID();
+
+ JsonObject itemsUpdateOwnershipRequestBody = new ItemsUpdateOwnershipRequestBuilder(createHoldingsRecord2,
+ new JsonArray(List.of(item.getId().toString(), nonExistentItemId.toString())), ApiTestSuite.CONSORTIA_TENANT_ID).create();
+
+ Response postItemsUpdateOwnershipResponse = updateItemsOwnership(itemsUpdateOwnershipRequestBody);
+
+ assertThat(postItemsUpdateOwnershipResponse.getStatusCode(), is(200));
+ assertThat(postItemsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON));
+
+ final var notFoundIds = toListOfStrings(postItemsUpdateOwnershipResponse.getJson(),
+ "nonUpdatedIds");
+
+ assertThat(notFoundIds, hasSize(1));
+ assertThat(notFoundIds.get(0), equalTo(nonExistentItemId.toString()));
+
+ final var sourceUpdatedItem = itemsClient.getById(item.getId());
+ final var targetUpdatedItem = consortiumItemsClient.getById(item.getId());
+
+ assertThat(HttpStatus.SC_NOT_FOUND, is(sourceUpdatedItem.getStatusCode()));
+ assertThat(targetUpdatedItem.getJson().getString(HOLDINGS_RECORD_ID), is(createHoldingsRecord2.toString()));
+ }
+
+ @Test
+ public void cannotUpdateItemsOwnershipToUnspecifiedHoldingsRecord() throws MalformedURLException, ExecutionException, InterruptedException, TimeoutException {
+ JsonObject itemsUpdateOwnershipRequestBody = new ItemsUpdateOwnershipRequestBuilder(null,
+ new JsonArray(List.of(UUID.randomUUID().toString())), ApiTestSuite.CONSORTIA_TENANT_ID).create();
+
+ Response postItemsUpdateOwnershipResponse = updateItemsOwnership(itemsUpdateOwnershipRequestBody);
+
+ assertThat(postItemsUpdateOwnershipResponse.getStatusCode(), is(422));
+ assertThat(postItemsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON));
+
+ assertThat(postItemsUpdateOwnershipResponse.getBody(), containsString("errors"));
+ assertThat(postItemsUpdateOwnershipResponse.getBody(), containsString("toHoldingsRecordId"));
+ assertThat(postItemsUpdateOwnershipResponse.getBody(), containsString("toHoldingsRecordId is a required field"));
+ }
+
+ @Test
+ public void cannotUpdateOwnershipOfUnspecifiedItems() throws MalformedURLException, ExecutionException, InterruptedException, TimeoutException {
+ JsonObject itemsUpdateOwnershipRequestBody = new ItemsUpdateOwnershipRequestBuilder(UUID.randomUUID(),
+ new JsonArray(List.of()), ApiTestSuite.CONSORTIA_TENANT_ID).create();
+
+ Response postItemsUpdateOwnershipResponse = updateItemsOwnership(itemsUpdateOwnershipRequestBody);
+
+ assertThat(postItemsUpdateOwnershipResponse.getStatusCode(), is(422));
+ assertThat(postItemsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON));
+
+ assertThat(postItemsUpdateOwnershipResponse.getBody(), containsString("errors"));
+ assertThat(postItemsUpdateOwnershipResponse.getBody(), containsString("itemIds"));
+ assertThat(postItemsUpdateOwnershipResponse.getBody(), containsString("Item ids aren't specified"));
+ }
+
+ @Test
+ public void cannotUpdateItemsOwnershipToUnspecifiedTenant() throws MalformedURLException, ExecutionException, InterruptedException, TimeoutException {
+ JsonObject itemsUpdateOwnershipRequestBody = new ItemsUpdateOwnershipRequestBuilder(UUID.randomUUID(),
+ new JsonArray(List.of(UUID.randomUUID())), null).create();
+
+ Response postItemsUpdateOwnershipResponse = updateItemsOwnership(itemsUpdateOwnershipRequestBody);
+
+ assertThat(postItemsUpdateOwnershipResponse.getStatusCode(), is(422));
+ assertThat(postItemsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON));
+
+ assertThat(postItemsUpdateOwnershipResponse.getBody(), containsString("errors"));
+ assertThat(postItemsUpdateOwnershipResponse.getBody(), containsString("tenantId"));
+ assertThat(postItemsUpdateOwnershipResponse.getBody(), containsString("tenantId is a required field"));
+ }
+
+ @Test
+ public void cannotUpdateItemsOwnershipToNonExistedHoldingsRecord() throws MalformedURLException, ExecutionException, InterruptedException, TimeoutException {
+ UUID sourceInstanceId = UUID.randomUUID();
+ JsonObject sourceInstance = smallAngryPlanet(sourceInstanceId);
+
+ InstanceApiClient.createInstance(okapiClient, sourceInstance);
+
+ final UUID existingHoldingsId = createHoldingForInstance(sourceInstanceId);
+ final UUID nonExistentHoldingsId = UUID.randomUUID();
+
+ final var item = itemsClient.create(
+ new ItemRequestBuilder()
+ .forHolding(existingHoldingsId)
+ .withBarcode("645398607547")
+ .withStatus(ItemStatusName.AVAILABLE.value()));
+
+ JsonObject itemsUpdateOwnershipRequestBody = new ItemsUpdateOwnershipRequestBuilder(nonExistentHoldingsId,
+ new JsonArray(List.of(item.getId().toString())), ApiTestSuite.CONSORTIA_TENANT_ID).create();
+
+ Response postItemsUpdateOwnershipResponse = updateItemsOwnership(itemsUpdateOwnershipRequestBody);
+
+ assertThat(postItemsUpdateOwnershipResponse.getStatusCode(), is(422));
+ assertThat(postItemsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON));
+
+ assertThat(postItemsUpdateOwnershipResponse.getBody(), containsString("errors"));
+ assertThat(postItemsUpdateOwnershipResponse.getBody(), containsString(nonExistentHoldingsId.toString()));
+ }
+
+ @Test
+ public void canUpdateItemsOwnershipDueToItemUpdateError() throws MalformedURLException, ExecutionException, InterruptedException, TimeoutException {
+ UUID sourceInstanceId = UUID.randomUUID();
+ JsonObject sourceInstance = smallAngryPlanet(sourceInstanceId);
+
+ UUID targetInstanceId = UUID.randomUUID();
+ JsonObject targetInstance = smallAngryPlanet(targetInstanceId);
+
+ InstanceApiClient.createInstance(okapiClient, sourceInstance);
+ InstanceApiClient.createInstance(consortiumOkapiClient, targetInstance);
+
+ final UUID createHoldingsRecord1 = createHoldingForInstance(sourceInstanceId);
+ final UUID createHoldingsRecord2 = createHoldingForInstanceAtConsortium(targetInstanceId);
+
+ final var firstItem = itemsClient.create(
+ new ItemRequestBuilder()
+ .forHolding(createHoldingsRecord1)
+ .withBarcode("645398607547")
+ .withStatus(ItemStatusName.AVAILABLE.value()));
+
+ final var secondItem = itemsClient.create(
+ new ItemRequestBuilder()
+ .forHolding(createHoldingsRecord1)
+ .withBarcode("645398607546")
+ .withId(ID_FOR_FAILURE)
+ .withStatus(ItemStatusName.AVAILABLE.value()));
+
+ JsonObject itemsUpdateOwnershipRequestBody = new ItemsUpdateOwnershipRequestBuilder(createHoldingsRecord2,
+ new JsonArray(List.of(firstItem.getId().toString(), secondItem.getId().toString())), ApiTestSuite.CONSORTIA_TENANT_ID).create();
+ Response postItemsUpdateOwnershipResponse = updateItemsOwnership(itemsUpdateOwnershipRequestBody);
+
+ final var nonUpdatedIdsIds = toListOfStrings(postItemsUpdateOwnershipResponse.getJson(),
+ "nonUpdatedIds");
+
+ assertThat(nonUpdatedIdsIds, hasSize(1));
+ assertThat(nonUpdatedIdsIds.get(0), equalTo(ID_FOR_FAILURE.toString()));
+
+ assertThat(postItemsUpdateOwnershipResponse.getStatusCode(), is(200));
+ assertThat(postItemsUpdateOwnershipResponse.getContentType(), containsString(APPLICATION_JSON));
+
+ final var sourceFirstItemUpdated = itemsClient.getById(firstItem.getId());
+ final var targetFirstItemUpdated = consortiumItemsClient.getById(firstItem.getId());
+
+ assertThat(HttpStatus.SC_NOT_FOUND, is(sourceFirstItemUpdated.getStatusCode()));
+ assertThat(createHoldingsRecord2.toString(), equalTo(targetFirstItemUpdated.getJson().getString(HOLDINGS_RECORD_ID)));
+
+ final var sourceSecondUpdatedItem = itemsClient.getById(secondItem.getId());
+ final var targetSecondUpdatedItem = consortiumItemsClient.getById(secondItem.getId());
+
+ assertThat(createHoldingsRecord1.toString(), equalTo(sourceSecondUpdatedItem.getJson().getString(HOLDINGS_RECORD_ID)));
+ assertThat(HttpStatus.SC_NOT_FOUND, is(targetSecondUpdatedItem.getStatusCode()));
+ }
+
+ private Response updateItemsOwnership(JsonObject itemsUpdateOwnershipRequestBody) throws MalformedURLException, ExecutionException, InterruptedException, TimeoutException {
+ final var postItemsUpdateOwnershipCompleted = okapiClient.post(
+ ApiRoot.updateItemsOwnership(), itemsUpdateOwnershipRequestBody);
+ return postItemsUpdateOwnershipCompleted.toCompletableFuture().get(5, TimeUnit.SECONDS);
+ }
+
+ private UUID createHoldingForInstance(UUID instanceId) {
+ return holdingsStorageClient.create(new HoldingRequestBuilder().forInstance(instanceId))
+ .getId();
+ }
+
+ private UUID createHoldingForInstanceAtConsortium(UUID instanceId) {
+ return consortiumHoldingsStorageClient.create(new HoldingRequestBuilder().forInstance(instanceId))
+ .getId();
+ }
+}
diff --git a/src/test/java/api/support/ApiRoot.java b/src/test/java/api/support/ApiRoot.java
index 07734f499..aa1373cc1 100644
--- a/src/test/java/api/support/ApiRoot.java
+++ b/src/test/java/api/support/ApiRoot.java
@@ -53,6 +53,18 @@ public static URL moveHoldingsRecords()
return new URL(String.format("%s/holdings/move", inventory()));
}
+ public static URL updateItemsOwnership()
+ throws MalformedURLException {
+
+ return new URL(String.format("%s/items/update-ownership", inventory()));
+ }
+
+ public static URL updateHoldingsRecordsOwnership()
+ throws MalformedURLException {
+
+ return new URL(String.format("%s/holdings/update-ownership", inventory()));
+ }
+
public static URL items(String query)
throws MalformedURLException {
diff --git a/src/test/java/api/support/ApiTests.java b/src/test/java/api/support/ApiTests.java
index d5dcb5783..2d079664f 100644
--- a/src/test/java/api/support/ApiTests.java
+++ b/src/test/java/api/support/ApiTests.java
@@ -18,6 +18,7 @@
public abstract class ApiTests {
private static boolean runningOnOwn;
protected static OkapiHttpClient okapiClient;
+ protected static OkapiHttpClient consortiumOkapiClient;
protected final ResourceClient holdingsStorageClient;
protected final ResourceClient itemsStorageClient;
protected final ResourceClient itemsClient;
@@ -31,6 +32,8 @@ public abstract class ApiTests {
protected final ResourceClient instanceRelationshipClient;
protected final ResourceClient requestStorageClient;
protected final ResourceClient sourceRecordStorageClient;
+ protected final ResourceClient consortiumItemsClient;
+ protected final ResourceClient consortiumHoldingsStorageClient;
protected final InstanceRelationshipTypeFixture instanceRelationshipTypeFixture;
protected final MarkItemFixture markItemFixture;
@@ -51,6 +54,9 @@ public ApiTests() {
sourceRecordStorageClient = ResourceClient.forSourceRecordStorage(okapiClient);
instanceRelationshipTypeFixture = new InstanceRelationshipTypeFixture(okapiClient);
markItemFixture = new MarkItemFixture(okapiClient);
+
+ consortiumHoldingsStorageClient = ResourceClient.forHoldingsStorage(consortiumOkapiClient);
+ consortiumItemsClient = ResourceClient.forItemsStorage(consortiumOkapiClient);
}
@BeforeClass
@@ -67,6 +73,7 @@ public static void before()
}
okapiClient = ApiTestSuite.createOkapiHttpClient();
+ consortiumOkapiClient = ApiTestSuite.createOkapiHttpClient(ApiTestSuite.CONSORTIA_TENANT_ID);
}
@AfterClass
diff --git a/src/test/java/api/support/builders/HoldingsRecordUpdateOwnershipRequestBuilder.java b/src/test/java/api/support/builders/HoldingsRecordUpdateOwnershipRequestBuilder.java
new file mode 100644
index 000000000..2c4e22e0a
--- /dev/null
+++ b/src/test/java/api/support/builders/HoldingsRecordUpdateOwnershipRequestBuilder.java
@@ -0,0 +1,34 @@
+package api.support.builders;
+
+
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.JsonObject;
+
+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;
+
+public class HoldingsRecordUpdateOwnershipRequestBuilder extends AbstractBuilder {
+
+ private final UUID toInstanceId;
+ private final JsonArray holdingsRecordsIds;
+ private final String tenantId;
+
+ public HoldingsRecordUpdateOwnershipRequestBuilder(UUID toInstanceId, JsonArray holdingsRecordsIds, String tenantId) {
+ this.toInstanceId = toInstanceId;
+ this.holdingsRecordsIds = holdingsRecordsIds;
+ this.tenantId = tenantId;
+ }
+
+ public JsonObject create() {
+ JsonObject holdingsRecordUpdateOwnershipRequest = new JsonObject();
+
+ includeWhenPresent(holdingsRecordUpdateOwnershipRequest, TO_INSTANCE_ID, toInstanceId);
+ includeWhenPresent(holdingsRecordUpdateOwnershipRequest, HOLDINGS_RECORD_IDS, holdingsRecordsIds);
+ includeWhenPresent(holdingsRecordUpdateOwnershipRequest, TENANT_ID, tenantId);
+
+ return holdingsRecordUpdateOwnershipRequest;
+ }
+}
diff --git a/src/test/java/api/support/builders/ItemsUpdateOwnershipRequestBuilder.java b/src/test/java/api/support/builders/ItemsUpdateOwnershipRequestBuilder.java
new file mode 100644
index 000000000..04b07cd38
--- /dev/null
+++ b/src/test/java/api/support/builders/ItemsUpdateOwnershipRequestBuilder.java
@@ -0,0 +1,33 @@
+package api.support.builders;
+
+
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.JsonObject;
+
+import java.util.UUID;
+
+import static api.ApiTestSuite.TENANT_ID;
+import static org.folio.inventory.resources.MoveApi.ITEM_IDS;
+import static org.folio.inventory.resources.MoveApi.TO_HOLDINGS_RECORD_ID;
+
+public class ItemsUpdateOwnershipRequestBuilder extends AbstractBuilder {
+ private final UUID toHoldingsRecordId;
+ private final JsonArray itemIds;
+ private final String tenantId;
+
+ public ItemsUpdateOwnershipRequestBuilder(UUID toHoldingsRecordId, JsonArray itemIds, String tenantId) {
+ this.toHoldingsRecordId = toHoldingsRecordId;
+ this.itemIds = itemIds;
+ this.tenantId = tenantId;
+ }
+
+ public JsonObject create() {
+ final var itemsUpdateOwnershipRequest = new JsonObject();
+
+ includeWhenPresent(itemsUpdateOwnershipRequest, TO_HOLDINGS_RECORD_ID, toHoldingsRecordId);
+ includeWhenPresent(itemsUpdateOwnershipRequest, ITEM_IDS, itemIds);
+ includeWhenPresent(itemsUpdateOwnershipRequest, TENANT_ID, tenantId);
+
+ return itemsUpdateOwnershipRequest;
+ }
+}
diff --git a/src/test/java/support/fakes/FakeStorageModule.java b/src/test/java/support/fakes/FakeStorageModule.java
index 33d543037..84e20583b 100644
--- a/src/test/java/support/fakes/FakeStorageModule.java
+++ b/src/test/java/support/fakes/FakeStorageModule.java
@@ -47,7 +47,7 @@ class FakeStorageModule extends AbstractVerticle {
FakeStorageModule(
String rootPath,
String collectionPropertyName,
- String tenantId,
+ List tenants,
Collection requiredProperties,
boolean hasCollectionDelete,
String recordTypeName,
@@ -69,7 +69,7 @@ class FakeStorageModule extends AbstractVerticle {
this.defaultProperties = defaultPropertiesWithId;
storedResourcesByTenant = new HashMap<>();
- storedResourcesByTenant.put(tenantId, new HashMap<>());
+ tenants.forEach(tenant -> storedResourcesByTenant.put(tenant, new HashMap<>()));
this.recordPreProcessors = recordPreProcessors;
}
diff --git a/src/test/java/support/fakes/FakeStorageModuleBuilder.java b/src/test/java/support/fakes/FakeStorageModuleBuilder.java
index 89d8bd45e..2ed59cf63 100644
--- a/src/test/java/support/fakes/FakeStorageModuleBuilder.java
+++ b/src/test/java/support/fakes/FakeStorageModuleBuilder.java
@@ -15,7 +15,7 @@
public class FakeStorageModuleBuilder {
private final String rootPath;
private final String collectionPropertyName;
- private final String tenantId;
+ private final List tenants;
private final Collection requiredProperties;
private final Collection uniqueProperties;
private final Map> defaultProperties;
@@ -24,14 +24,14 @@ public class FakeStorageModuleBuilder {
private final List recordPreProcessors;
FakeStorageModuleBuilder() {
- this(null, null, ApiTestSuite.TENANT_ID, new ArrayList<>(), true, "",
+ this(null, null, List.of(ApiTestSuite.TENANT_ID, ApiTestSuite.CONSORTIA_TENANT_ID), new ArrayList<>(), true, "",
new ArrayList<>(), new HashMap<>(), Collections.emptyList());
}
private FakeStorageModuleBuilder(
String rootPath,
String collectionPropertyName,
- String tenantId,
+ List tenants,
Collection requiredProperties,
boolean hasCollectionDelete,
String recordName,
@@ -41,7 +41,7 @@ private FakeStorageModuleBuilder(
this.rootPath = rootPath;
this.collectionPropertyName = collectionPropertyName;
- this.tenantId = tenantId;
+ this.tenants = tenants;
this.requiredProperties = requiredProperties;
this.hasCollectionDelete = hasCollectionDelete;
this.recordName = recordName;
@@ -51,7 +51,7 @@ private FakeStorageModuleBuilder(
}
public FakeStorageModule create() {
- return new FakeStorageModule(rootPath, collectionPropertyName, tenantId,
+ return new FakeStorageModule(rootPath, collectionPropertyName, tenants,
requiredProperties, hasCollectionDelete, recordName, uniqueProperties,
defaultProperties, recordPreProcessors);
}
@@ -64,7 +64,7 @@ FakeStorageModuleBuilder withRootPath(String rootPath) {
return new FakeStorageModuleBuilder(
rootPath,
newCollectionPropertyName,
- this.tenantId,
+ this.tenants,
this.requiredProperties,
this.hasCollectionDelete,
this.recordName,
@@ -77,7 +77,7 @@ FakeStorageModuleBuilder withCollectionPropertyName(String collectionPropertyNam
return new FakeStorageModuleBuilder(
this.rootPath,
collectionPropertyName,
- this.tenantId,
+ this.tenants,
this.requiredProperties,
this.hasCollectionDelete,
this.recordName,
@@ -90,7 +90,7 @@ FakeStorageModuleBuilder withRecordName(String recordName) {
return new FakeStorageModuleBuilder(
this.rootPath,
this.collectionPropertyName,
- this.tenantId,
+ this.tenants,
this.requiredProperties,
this.hasCollectionDelete,
recordName,
@@ -105,7 +105,7 @@ private FakeStorageModuleBuilder withRequiredProperties(
return new FakeStorageModuleBuilder(
this.rootPath,
this.collectionPropertyName,
- this.tenantId,
+ this.tenants,
requiredProperties,
this.hasCollectionDelete,
this.recordName,
@@ -126,7 +126,7 @@ FakeStorageModuleBuilder withDefault(String property, Object value) {
return new FakeStorageModuleBuilder(
this.rootPath,
this.collectionPropertyName,
- this.tenantId,
+ this.tenants,
this.requiredProperties,
this.hasCollectionDelete,
this.recordName,
@@ -139,7 +139,7 @@ final FakeStorageModuleBuilder withRecordPreProcessors(RecordPreProcessor... pre
return new FakeStorageModuleBuilder(
this.rootPath,
this.collectionPropertyName,
- this.tenantId,
+ this.tenants,
this.requiredProperties,
this.hasCollectionDelete,
this.recordName,