From 37cfe77b53097572d99a9a022c7d1cdf9c57e745 Mon Sep 17 00:00:00 2001 From: "Long.Vo(Connor)" Date: Wed, 28 Jan 2026 11:52:50 -0500 Subject: [PATCH 1/7] CIRC-2531: Implement storage layer for request anonymization --- .../circulation/domain/RequestStatus.java | 8 +++ .../AnonymizeStorageRequestsRepository.java | 56 +++++++++++++++++++ .../storage/requests/RequestRepository.java | 31 ++++++++++ .../folio/circulation/support/Clients.java | 5 ++ 4 files changed, 100 insertions(+) create mode 100644 src/main/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepository.java diff --git a/src/main/java/org/folio/circulation/domain/RequestStatus.java b/src/main/java/org/folio/circulation/domain/RequestStatus.java index a1c6721dcc..6742e34484 100644 --- a/src/main/java/org/folio/circulation/domain/RequestStatus.java +++ b/src/main/java/org/folio/circulation/domain/RequestStatus.java @@ -30,6 +30,9 @@ public enum RequestStatus { private static final EnumSet OPEN_STATUSES = EnumSet.of( OPEN_NOT_YET_FILLED, OPEN_AWAITING_PICKUP, OPEN_IN_TRANSIT, OPEN_AWAITING_DELIVERY); + private static final EnumSet CLOSED_STATUSES = EnumSet.of( + CLOSED_FILLED, CLOSED_CANCELLED, CLOSED_UNFILLED, CLOSED_PICKUP_EXPIRED); + private final String value; public static String invalidStatusErrorMessage() { @@ -60,6 +63,11 @@ public static List openStates() { .collect(Collectors.toList()); } + public static List closedStates() { + return CLOSED_STATUSES.stream().map(RequestStatus::getValue) + .collect(Collectors.toList()); + } + public boolean isValid() { return this != NONE; } diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepository.java new file mode 100644 index 0000000000..89d660ad5f --- /dev/null +++ b/src/main/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepository.java @@ -0,0 +1,56 @@ +package org.folio.circulation.infrastructure.storage.requests; + +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.stream.Collectors.toList; +import static org.folio.circulation.support.http.ResponseMapping.forwardOnFailure; +import static org.folio.circulation.support.http.ResponseMapping.mapUsingJson; +import static org.folio.circulation.support.json.JsonStringArrayPropertyFetcher.toStream; +import static org.folio.circulation.support.results.Result.succeeded; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +import org.folio.circulation.domain.anonymization.RequestAnonymizationRecords; +import org.folio.circulation.support.Clients; +import org.folio.circulation.support.CollectionResourceClient; +import org.folio.circulation.support.http.client.Response; +import org.folio.circulation.support.http.client.ResponseInterpreter; +import org.folio.circulation.support.results.Result; + +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; + +public class AnonymizeStorageRequestsRepository { + private final CollectionResourceClient requestStorageClient; + + public AnonymizeStorageRequestsRepository(Clients clients) { + requestStorageClient = clients.anonymizeStorageRequestsClient(); + } + + private static ResponseInterpreter + createStorageRequestResponseInterpreter(RequestAnonymizationRecords records) { + + Function> mapper = mapUsingJson( + response -> records.withAnonymizedRequests( + toStream(response, "anonymizedRequests").collect(toList()))); + + return new ResponseInterpreter().flatMapOn(200, mapper) + .otherwise(forwardOnFailure()); + } + + private static JsonObject createRequestPayload(RequestAnonymizationRecords records) { + JsonObject jsonObject = new JsonObject(); + jsonObject.put("requestIds", new JsonArray(records.getAnonymizedRequestIds())); + return jsonObject; + } + + public CompletableFuture> + postAnonymizeStorageRequests(RequestAnonymizationRecords records) { + + if (records.getAnonymizedRequestIds().isEmpty()) { + return completedFuture(succeeded(records)); + } + return requestStorageClient.post(createRequestPayload(records)) + .thenApply(createStorageRequestResponseInterpreter(records)::flatMap); + } +} diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/requests/RequestRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/requests/RequestRepository.java index 6d564b3c4a..8fe817910c 100644 --- a/src/main/java/org/folio/circulation/infrastructure/storage/requests/RequestRepository.java +++ b/src/main/java/org/folio/circulation/infrastructure/storage/requests/RequestRepository.java @@ -3,6 +3,7 @@ import static java.util.Objects.isNull; import static java.util.function.Function.identity; import static org.folio.circulation.domain.RequestStatus.openStates; +import static org.folio.circulation.domain.RequestStatus.closedStates; import static org.folio.circulation.support.CqlSortBy.ascending; import static org.folio.circulation.support.fetching.RecordFetching.findWithMultipleCqlIndexValues; import static org.folio.circulation.support.http.ResponseMapping.forwardOnFailure; @@ -319,6 +320,36 @@ public CompletableFuture>> fetchRequests(Collection>> findRequestsToAnonymize( + PageLimit pageLimit) { + + log.debug("findRequestsToAnonymize:: parameters pageLimit: {}", pageLimit); + + Result cqlQuery = exactMatchAny("status", closedStates()) + .combine(CqlQuery.hasValue("requesterId"), CqlQuery::and); + + return queryRequestStorage(cqlQuery, pageLimit); + } + + public CompletableFuture>> findClosedRequests( + String userId, PageLimit pageLimit) { + + log.debug("findClosedRequests:: parameters userId: {}, pageLimit: {}", userId, pageLimit); + + Result userQuery = exactMatch("requesterId", userId); + Result statusQuery = exactMatchAny("status", closedStates()); + + return queryRequestStorage(statusQuery.combine(userQuery, CqlQuery::and), pageLimit); + } + + private CompletableFuture>> queryRequestStorage( + Result cqlQuery, PageLimit pageLimit) { + + return cqlQuery + .after(q -> requestsStorageClient.getMany(q, pageLimit)) + .thenApply(result -> result.next(this::mapResponseToRequests)); + } + private CompletableFuture> fetchRequester(Result result) { log.debug("fetchRequester:: parameters result: {}", ()-> resultAsString(result)); return result.combineAfter(request -> diff --git a/src/main/java/org/folio/circulation/support/Clients.java b/src/main/java/org/folio/circulation/support/Clients.java index e556709c47..f84390fd05 100644 --- a/src/main/java/org/folio/circulation/support/Clients.java +++ b/src/main/java/org/folio/circulation/support/Clients.java @@ -221,6 +221,11 @@ public CollectionResourceClient anonymizeStorageLoansClient() { return anonymizeStorageLoansClient; } + public CollectionResourceClient anonymizeStorageRequestsClient() { + return createCollectionResourceClient("/request-storage/requests/anonymize", + RequestAnonymizationRecords::new); + } + public CollectionResourceClient locationsStorage() { return locationsStorageClient; } From cdfc98034b8417ff76c54d82bf1382b33d250817 Mon Sep 17 00:00:00 2001 From: "Long.Vo(Connor)" Date: Wed, 28 Jan 2026 13:59:13 -0500 Subject: [PATCH 2/7] CIRC-2531: Fix compilation errors --- .../storage/requests/RequestRepository.java | 2 ++ .../org/folio/circulation/support/Clients.java | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/requests/RequestRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/requests/RequestRepository.java index 8fe817910c..71f8b2eae5 100644 --- a/src/main/java/org/folio/circulation/infrastructure/storage/requests/RequestRepository.java +++ b/src/main/java/org/folio/circulation/infrastructure/storage/requests/RequestRepository.java @@ -18,6 +18,8 @@ import static org.folio.circulation.support.utils.LogUtil.collectionAsString; import static org.folio.circulation.support.utils.LogUtil.multipleRecordsAsString; import static org.folio.circulation.support.utils.LogUtil.resultAsString; +import static org.folio.circulation.support.http.client.CqlQuery.exactMatch; +import static org.folio.circulation.support.http.client.CqlQuery.exactMatchAny; import java.lang.invoke.MethodHandles; import java.util.Collection; diff --git a/src/main/java/org/folio/circulation/support/Clients.java b/src/main/java/org/folio/circulation/support/Clients.java index f84390fd05..d1c6780908 100644 --- a/src/main/java/org/folio/circulation/support/Clients.java +++ b/src/main/java/org/folio/circulation/support/Clients.java @@ -10,6 +10,7 @@ import org.folio.circulation.support.http.client.OkapiHttpClient; import org.folio.circulation.support.http.client.QueryParameter; import org.folio.circulation.support.http.server.WebContext; +import org.folio.circulation.domain.anonymization.RequestAnonymizationRecords; import io.vertx.core.http.HttpClient; @@ -60,6 +61,7 @@ public class Clients { private final CollectionResourceClient feeFineOwnerStorageClient; private final CollectionResourceClient feeFineStorageClient; private final CollectionResourceClient anonymizeStorageLoansClient; + private final CollectionResourceClient anonymizeStorageRequestsClient; private final CollectionResourceClient patronActionSessionsStorageClient; private final CollectionResourceClient patronExpiredSessionsStorageClient; private final GetManyRecordsClient userManualBlocksStorageClient; @@ -108,6 +110,7 @@ private Clients(OkapiHttpClient client, WebContext context) { lostItemPoliciesStorageClient = createLostItemPoliciesStorageClient(client, context); locationsStorageClient = createLocationsStorageClient(client, context); anonymizeStorageLoansClient = createAnonymizeStorageLoansClient(client, context); + anonymizeStorageRequestsClient = createAnonymizeStorageRequestsClient(client, context); institutionsStorageClient = createInstitutionsStorageClient(client, context); campusesStorageClient = createCampusesStorageClient(client, context); librariesStorageClient = createLibrariesStorageClient(client, context); @@ -222,8 +225,7 @@ public CollectionResourceClient anonymizeStorageLoansClient() { } public CollectionResourceClient anonymizeStorageRequestsClient() { - return createCollectionResourceClient("/request-storage/requests/anonymize", - RequestAnonymizationRecords::new); + return anonymizeStorageRequestsClient; } public CollectionResourceClient locationsStorage() { @@ -588,6 +590,14 @@ private static CollectionResourceClient createAnonymizeStorageLoansClient( "/anonymize-storage-loans"); } + private static CollectionResourceClient createAnonymizeStorageRequestsClient( + OkapiHttpClient client, WebContext context) + throws MalformedURLException { + + return getCollectionResourceClient(client, context, + "/request-storage/requests/anonymize"); + } + private static CollectionResourceClient createLocationsStorageClient( OkapiHttpClient client, WebContext context) throws MalformedURLException { From 6869f2e4dae507cef100ab98c04cecd21e13d8c2 Mon Sep 17 00:00:00 2001 From: "Long.Vo(Connor)" Date: Tue, 3 Feb 2026 15:28:27 -0500 Subject: [PATCH 3/7] Add unit tests --- .../circulation/domain/RequestStatusTest.java | 32 +++++ ...nonymizeStorageRequestsRepositoryTest.java | 123 ++++++++++++++++++ .../requests/RequestRepositoryTest.java | 109 ++++++++++++++++ 3 files changed, 264 insertions(+) create mode 100644 src/test/java/org/folio/circulation/domain/RequestStatusTest.java create mode 100644 src/test/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepositoryTest.java create mode 100644 src/test/java/org/folio/circulation/infrastructure/storage/requests/RequestRepositoryTest.java diff --git a/src/test/java/org/folio/circulation/domain/RequestStatusTest.java b/src/test/java/org/folio/circulation/domain/RequestStatusTest.java new file mode 100644 index 0000000000..433912a1cf --- /dev/null +++ b/src/test/java/org/folio/circulation/domain/RequestStatusTest.java @@ -0,0 +1,32 @@ +package org.folio.circulation.domain; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +class RequestStatusTest { + + @Test + void closedStatesReturnsAllClosedStatuses() { + List closedStates = RequestStatus.closedStates(); + + assertEquals(4, closedStates.size()); + assertTrue(closedStates.contains("Closed - Filled")); + assertTrue(closedStates.contains("Closed - Cancelled")); + assertTrue(closedStates.contains("Closed - Unfilled")); + assertTrue(closedStates.contains("Closed - Pickup expired")); + } + + @Test + void closedStatesDoesNotContainOpenStatuses() { + List closedStates = RequestStatus.closedStates(); + + assertTrue(!closedStates.contains("Open - Not yet filled")); + assertTrue(!closedStates.contains("Open - Awaiting pickup")); + assertTrue(!closedStates.contains("Open - In transit")); + assertTrue(!closedStates.contains("Open - Awaiting delivery")); + } +} diff --git a/src/test/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepositoryTest.java b/src/test/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepositoryTest.java new file mode 100644 index 0000000000..2dcb2adaee --- /dev/null +++ b/src/test/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepositoryTest.java @@ -0,0 +1,123 @@ +package org.folio.circulation.infrastructure.storage.requests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collections; +import java.util.concurrent.CompletableFuture; + +import org.folio.circulation.domain.anonymization.RequestAnonymizationRecords; +import org.folio.circulation.support.Clients; +import org.folio.circulation.support.CollectionResourceClient; +import org.folio.circulation.support.http.client.Response; +import org.folio.circulation.support.results.Result; +import org.junit.jupiter.api.Test; + +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; + +class AnonymizeStorageRequestsRepositoryTest { + + @Test + void shouldReturnSuccessWhenRequestIdsAreEmpty() { + Clients clients = mock(Clients.class); + CollectionResourceClient client = mock(CollectionResourceClient.class); + + when(clients.anonymizeStorageRequestsClient()).thenReturn(client); + + AnonymizeStorageRequestsRepository repository = + new AnonymizeStorageRequestsRepository(clients); + + RequestAnonymizationRecords emptyRecords = new RequestAnonymizationRecords(); + + Result result = + repository.postAnonymizeStorageRequests(emptyRecords).join(); + + assertEquals(true, result.succeeded()); + assertEquals(0, result.value().getAnonymizedRequestIds().size()); + verify(client, times(0)).post(any(JsonObject.class)); + } + + @Test + void shouldPostToStorageWhenRequestIdsExist() { + Clients clients = mock(Clients.class); + CollectionResourceClient client = mock(CollectionResourceClient.class); + + when(clients.anonymizeStorageRequestsClient()).thenReturn(client); + + Response response = mock(Response.class); + JsonObject responseBody = new JsonObject() + .put("anonymizedRequests", new JsonArray() + .add("request-id-1") + .add("request-id-2")); + + when(response.getJson()).thenReturn(responseBody); + when(client.post(any(JsonObject.class))) + .thenReturn(CompletableFuture.completedFuture(Result.succeeded(response))); + + AnonymizeStorageRequestsRepository repository = + new AnonymizeStorageRequestsRepository(clients); + + RequestAnonymizationRecords records = new RequestAnonymizationRecords() + .withAnonymizedRequests(Arrays.asList("request-id-1", "request-id-2")); + + Result result = + repository.postAnonymizeStorageRequests(records).join(); + + assertEquals(true, result.succeeded()); + assertNotNull(result.value()); + verify(client, times(1)).post(any(JsonObject.class)); + } + + @Test + void shouldCreateCorrectPayload() { + Clients clients = mock(Clients.class); + CollectionResourceClient client = mock(CollectionResourceClient.class); + + when(clients.anonymizeStorageRequestsClient()).thenReturn(client); + + Response response = mock(Response.class); + when(response.getJson()).thenReturn(new JsonObject() + .put("anonymizedRequests", new JsonArray().add("request-1"))); + + when(client.post(any(JsonObject.class))) + .thenReturn(CompletableFuture.completedFuture(Result.succeeded(response))); + + AnonymizeStorageRequestsRepository repository = + new AnonymizeStorageRequestsRepository(clients); + + RequestAnonymizationRecords records = new RequestAnonymizationRecords() + .withAnonymizedRequests(Collections.singletonList("request-1")); + + repository.postAnonymizeStorageRequests(records).join(); + + verify(client).post(any(JsonObject.class)); + } + + @Test + void shouldHandleNullResponseGracefully() { + Clients clients = mock(Clients.class); + CollectionResourceClient client = mock(CollectionResourceClient.class); + + when(clients.anonymizeStorageRequestsClient()).thenReturn(client); + when(client.post(any(JsonObject.class))) + .thenReturn(CompletableFuture.completedFuture(Result.succeeded(null))); + + AnonymizeStorageRequestsRepository repository = + new AnonymizeStorageRequestsRepository(clients); + + RequestAnonymizationRecords records = new RequestAnonymizationRecords() + .withAnonymizedRequests(Collections.singletonList("request-1")); + + Result result = + repository.postAnonymizeStorageRequests(records).join(); + + assertNotNull(result); + } +} diff --git a/src/test/java/org/folio/circulation/infrastructure/storage/requests/RequestRepositoryTest.java b/src/test/java/org/folio/circulation/infrastructure/storage/requests/RequestRepositoryTest.java new file mode 100644 index 0000000000..3a17cb17e5 --- /dev/null +++ b/src/test/java/org/folio/circulation/infrastructure/storage/requests/RequestRepositoryTest.java @@ -0,0 +1,109 @@ +package org.folio.circulation.infrastructure.storage.requests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.concurrent.CompletableFuture; + +import org.folio.circulation.domain.MultipleRecords; +import org.folio.circulation.domain.Request; +import org.folio.circulation.support.Clients; +import org.folio.circulation.support.CollectionResourceClient; +import org.folio.circulation.support.http.client.CqlQuery; +import org.folio.circulation.support.http.client.PageLimit; +import org.folio.circulation.support.http.client.Response; +import org.folio.circulation.support.results.Result; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; + +class RequestRepositoryTest { + + private RequestRepository repository; + private CollectionResourceClient requestsStorageClient; + + @BeforeEach + void setUp() { + Clients clients = mock(Clients.class); + requestsStorageClient = mock(CollectionResourceClient.class); + + when(clients.requestsStorage()).thenReturn(requestsStorageClient); + when(clients.requestsBatchStorage()).thenReturn(mock(CollectionResourceClient.class)); + when(clients.cancellationReasonStorage()).thenReturn(mock(CollectionResourceClient.class)); + + repository = new RequestRepository(clients); + } + + @Test + void findRequestsToAnonymizeQueriesClosedRequests() { + Response response = createMockResponse(); + when(requestsStorageClient.getMany(any(CqlQuery.class), any(PageLimit.class))) + .thenReturn(CompletableFuture.completedFuture(Result.succeeded(response))); + + PageLimit pageLimit = PageLimit.limit(100); + Result> result = + repository.findRequestsToAnonymize(pageLimit).join(); + + assertTrue(result.succeeded()); + verify(requestsStorageClient).getMany(any(CqlQuery.class), eq(pageLimit)); + } + + @Test + void findClosedRequestsQueriesForSpecificUser() { + Response response = createMockResponse(); + when(requestsStorageClient.getMany(any(CqlQuery.class), any(PageLimit.class))) + .thenReturn(CompletableFuture.completedFuture(Result.succeeded(response))); + + String userId = "user-123"; + PageLimit pageLimit = PageLimit.limit(50); + + Result> result = + repository.findClosedRequests(userId, pageLimit).join(); + + assertTrue(result.succeeded()); + verify(requestsStorageClient).getMany(any(CqlQuery.class), eq(pageLimit)); + } + + @Test + void findRequestsToAnonymizeReturnsEmptyWhenNoRequests() { + Response response = createMockResponse(); + when(requestsStorageClient.getMany(any(CqlQuery.class), any(PageLimit.class))) + .thenReturn(CompletableFuture.completedFuture(Result.succeeded(response))); + + Result> result = + repository.findRequestsToAnonymize(PageLimit.limit(10)).join(); + + assertTrue(result.succeeded()); + assertEquals(0, result.value().getTotalRecords()); + } + + @Test + void findClosedRequestsReturnsEmptyWhenNoRequestsForUser() { + Response response = createMockResponse(); + when(requestsStorageClient.getMany(any(CqlQuery.class), any(PageLimit.class))) + .thenReturn(CompletableFuture.completedFuture(Result.succeeded(response))); + + Result> result = + repository.findClosedRequests("user-456", PageLimit.limit(10)).join(); + + assertTrue(result.succeeded()); + assertEquals(0, result.value().getTotalRecords()); + } + + private Response createMockResponse() { + Response response = mock(Response.class); + JsonObject body = new JsonObject() + .put("requests", new JsonArray()) + .put("totalRecords", 0); + when(response.getJson()).thenReturn(body); + when(response.getStatusCode()).thenReturn(200); + return response; + } +} From 504340e4ec13d511044b8689001c5f0cb14f126e Mon Sep 17 00:00:00 2001 From: "Long.Vo(Connor)" Date: Tue, 3 Feb 2026 16:00:56 -0500 Subject: [PATCH 4/7] Unit test update --- ...nonymizeStorageRequestsRepositoryTest.java | 31 +++++-------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/src/test/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepositoryTest.java b/src/test/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepositoryTest.java index 2dcb2adaee..145c4a1b38 100644 --- a/src/test/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepositoryTest.java +++ b/src/test/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepositoryTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -39,7 +40,7 @@ void shouldReturnSuccessWhenRequestIdsAreEmpty() { Result result = repository.postAnonymizeStorageRequests(emptyRecords).join(); - assertEquals(true, result.succeeded()); + assertTrue(result.succeeded()); assertEquals(0, result.value().getAnonymizedRequestIds().size()); verify(client, times(0)).post(any(JsonObject.class)); } @@ -52,6 +53,8 @@ void shouldPostToStorageWhenRequestIdsExist() { when(clients.anonymizeStorageRequestsClient()).thenReturn(client); Response response = mock(Response.class); + when(response.getStatusCode()).thenReturn(200); + JsonObject responseBody = new JsonObject() .put("anonymizedRequests", new JsonArray() .add("request-id-1") @@ -70,7 +73,7 @@ void shouldPostToStorageWhenRequestIdsExist() { Result result = repository.postAnonymizeStorageRequests(records).join(); - assertEquals(true, result.succeeded()); + assertTrue(result.succeeded()); assertNotNull(result.value()); verify(client, times(1)).post(any(JsonObject.class)); } @@ -83,6 +86,7 @@ void shouldCreateCorrectPayload() { when(clients.anonymizeStorageRequestsClient()).thenReturn(client); Response response = mock(Response.class); + when(response.getStatusCode()).thenReturn(200); when(response.getJson()).thenReturn(new JsonObject() .put("anonymizedRequests", new JsonArray().add("request-1"))); @@ -95,29 +99,10 @@ void shouldCreateCorrectPayload() { RequestAnonymizationRecords records = new RequestAnonymizationRecords() .withAnonymizedRequests(Collections.singletonList("request-1")); - repository.postAnonymizeStorageRequests(records).join(); - - verify(client).post(any(JsonObject.class)); - } - - @Test - void shouldHandleNullResponseGracefully() { - Clients clients = mock(Clients.class); - CollectionResourceClient client = mock(CollectionResourceClient.class); - - when(clients.anonymizeStorageRequestsClient()).thenReturn(client); - when(client.post(any(JsonObject.class))) - .thenReturn(CompletableFuture.completedFuture(Result.succeeded(null))); - - AnonymizeStorageRequestsRepository repository = - new AnonymizeStorageRequestsRepository(clients); - - RequestAnonymizationRecords records = new RequestAnonymizationRecords() - .withAnonymizedRequests(Collections.singletonList("request-1")); - Result result = repository.postAnonymizeStorageRequests(records).join(); - assertNotNull(result); + assertTrue(result.succeeded()); + verify(client).post(any(JsonObject.class)); } } From caeaacc54261b73ccf359a5b143193809f896765 Mon Sep 17 00:00:00 2001 From: "Long.Vo(Connor)" Date: Tue, 24 Feb 2026 14:25:19 -0500 Subject: [PATCH 5/7] Use RequestBatch similar to other functions and fixes --- .../folio/circulation/domain/RequestStatus.java | 2 +- .../AnonymizeStorageRequestsRepository.java | 12 ++++++++---- .../storage/requests/RequestRepository.java | 1 - .../org/folio/circulation/support/Clients.java | 15 --------------- 4 files changed, 9 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/folio/circulation/domain/RequestStatus.java b/src/main/java/org/folio/circulation/domain/RequestStatus.java index 6742e34484..1d747a7eeb 100644 --- a/src/main/java/org/folio/circulation/domain/RequestStatus.java +++ b/src/main/java/org/folio/circulation/domain/RequestStatus.java @@ -65,7 +65,7 @@ public static List openStates() { public static List closedStates() { return CLOSED_STATUSES.stream().map(RequestStatus::getValue) - .collect(Collectors.toList()); + .toList(); } public boolean isValid() { diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepository.java index 89d660ad5f..a378e69744 100644 --- a/src/main/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepository.java +++ b/src/main/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepository.java @@ -16,6 +16,7 @@ import org.folio.circulation.support.http.client.Response; import org.folio.circulation.support.http.client.ResponseInterpreter; import org.folio.circulation.support.results.Result; +import org.folio.circulation.storage.RequestBatch; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; @@ -24,7 +25,7 @@ public class AnonymizeStorageRequestsRepository { private final CollectionResourceClient requestStorageClient; public AnonymizeStorageRequestsRepository(Clients clients) { - requestStorageClient = clients.anonymizeStorageRequestsClient(); + requestStorageClient = clients.requestsBatchStorage(); } private static ResponseInterpreter @@ -32,9 +33,10 @@ public AnonymizeStorageRequestsRepository(Clients clients) { Function> mapper = mapUsingJson( response -> records.withAnonymizedRequests( - toStream(response, "anonymizedRequests").collect(toList()))); + toStream(response, "anonymizedRequests").toList())); - return new ResponseInterpreter().flatMapOn(200, mapper) + return new ResponseInterpreter() + .on(201, Result.of(() -> records)) .otherwise(forwardOnFailure()); } @@ -50,7 +52,9 @@ private static JsonObject createRequestPayload(RequestAnonymizationRecords recor if (records.getAnonymizedRequestIds().isEmpty()) { return completedFuture(succeeded(records)); } - return requestStorageClient.post(createRequestPayload(records)) + RequestBatch requestBatch = new RequestBatch(records.getAnonymizedRequests()); + + return requestStorageClient.post(requestBatch.toJson()) .thenApply(createStorageRequestResponseInterpreter(records)::flatMap); } } diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/requests/RequestRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/requests/RequestRepository.java index 71f8b2eae5..ceb8bed69d 100644 --- a/src/main/java/org/folio/circulation/infrastructure/storage/requests/RequestRepository.java +++ b/src/main/java/org/folio/circulation/infrastructure/storage/requests/RequestRepository.java @@ -8,7 +8,6 @@ import static org.folio.circulation.support.fetching.RecordFetching.findWithMultipleCqlIndexValues; import static org.folio.circulation.support.http.ResponseMapping.forwardOnFailure; import static org.folio.circulation.support.http.ResponseMapping.mapUsingJson; -import static org.folio.circulation.support.http.client.CqlQuery.exactMatchAny; import static org.folio.circulation.support.results.Result.failed; import static org.folio.circulation.support.results.Result.of; import static org.folio.circulation.support.results.Result.ofAsync; diff --git a/src/main/java/org/folio/circulation/support/Clients.java b/src/main/java/org/folio/circulation/support/Clients.java index d1c6780908..e556709c47 100644 --- a/src/main/java/org/folio/circulation/support/Clients.java +++ b/src/main/java/org/folio/circulation/support/Clients.java @@ -10,7 +10,6 @@ import org.folio.circulation.support.http.client.OkapiHttpClient; import org.folio.circulation.support.http.client.QueryParameter; import org.folio.circulation.support.http.server.WebContext; -import org.folio.circulation.domain.anonymization.RequestAnonymizationRecords; import io.vertx.core.http.HttpClient; @@ -61,7 +60,6 @@ public class Clients { private final CollectionResourceClient feeFineOwnerStorageClient; private final CollectionResourceClient feeFineStorageClient; private final CollectionResourceClient anonymizeStorageLoansClient; - private final CollectionResourceClient anonymizeStorageRequestsClient; private final CollectionResourceClient patronActionSessionsStorageClient; private final CollectionResourceClient patronExpiredSessionsStorageClient; private final GetManyRecordsClient userManualBlocksStorageClient; @@ -110,7 +108,6 @@ private Clients(OkapiHttpClient client, WebContext context) { lostItemPoliciesStorageClient = createLostItemPoliciesStorageClient(client, context); locationsStorageClient = createLocationsStorageClient(client, context); anonymizeStorageLoansClient = createAnonymizeStorageLoansClient(client, context); - anonymizeStorageRequestsClient = createAnonymizeStorageRequestsClient(client, context); institutionsStorageClient = createInstitutionsStorageClient(client, context); campusesStorageClient = createCampusesStorageClient(client, context); librariesStorageClient = createLibrariesStorageClient(client, context); @@ -224,10 +221,6 @@ public CollectionResourceClient anonymizeStorageLoansClient() { return anonymizeStorageLoansClient; } - public CollectionResourceClient anonymizeStorageRequestsClient() { - return anonymizeStorageRequestsClient; - } - public CollectionResourceClient locationsStorage() { return locationsStorageClient; } @@ -590,14 +583,6 @@ private static CollectionResourceClient createAnonymizeStorageLoansClient( "/anonymize-storage-loans"); } - private static CollectionResourceClient createAnonymizeStorageRequestsClient( - OkapiHttpClient client, WebContext context) - throws MalformedURLException { - - return getCollectionResourceClient(client, context, - "/request-storage/requests/anonymize"); - } - private static CollectionResourceClient createLocationsStorageClient( OkapiHttpClient client, WebContext context) throws MalformedURLException { From 27e6ca1e23a9dd96fcbc43ff1a8a4c853db0c90b Mon Sep 17 00:00:00 2001 From: "Long.Vo(Connor)" Date: Tue, 24 Feb 2026 14:42:33 -0500 Subject: [PATCH 6/7] Update test file --- ...AnonymizeStorageRequestsRepositoryTest.java | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/test/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepositoryTest.java b/src/test/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepositoryTest.java index 145c4a1b38..859bc98d50 100644 --- a/src/test/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepositoryTest.java +++ b/src/test/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepositoryTest.java @@ -30,7 +30,7 @@ void shouldReturnSuccessWhenRequestIdsAreEmpty() { Clients clients = mock(Clients.class); CollectionResourceClient client = mock(CollectionResourceClient.class); - when(clients.anonymizeStorageRequestsClient()).thenReturn(client); + when(clients.requestsBatchStorage()).thenReturn(client); AnonymizeStorageRequestsRepository repository = new AnonymizeStorageRequestsRepository(clients); @@ -50,15 +50,12 @@ void shouldPostToStorageWhenRequestIdsExist() { Clients clients = mock(Clients.class); CollectionResourceClient client = mock(CollectionResourceClient.class); - when(clients.anonymizeStorageRequestsClient()).thenReturn(client); + when(clients.requestsBatchStorage()).thenReturn(client); Response response = mock(Response.class); - when(response.getStatusCode()).thenReturn(200); + when(response.getStatusCode()).thenReturn(201); - JsonObject responseBody = new JsonObject() - .put("anonymizedRequests", new JsonArray() - .add("request-id-1") - .add("request-id-2")); + JsonObject responseBody = new JsonObject(); when(response.getJson()).thenReturn(responseBody); when(client.post(any(JsonObject.class))) @@ -83,12 +80,11 @@ void shouldCreateCorrectPayload() { Clients clients = mock(Clients.class); CollectionResourceClient client = mock(CollectionResourceClient.class); - when(clients.anonymizeStorageRequestsClient()).thenReturn(client); + when(clients.requestsBatchStorage()).thenReturn(client); Response response = mock(Response.class); - when(response.getStatusCode()).thenReturn(200); - when(response.getJson()).thenReturn(new JsonObject() - .put("anonymizedRequests", new JsonArray().add("request-1"))); + when(response.getStatusCode()).thenReturn(201); + when(response.getJson()).thenReturn(new JsonObject()); when(client.post(any(JsonObject.class))) .thenReturn(CompletableFuture.completedFuture(Result.succeeded(response))); From 97e64eb0ff388c0579fbb5cd645ecff04efd466a Mon Sep 17 00:00:00 2001 From: "Long.Vo(Connor)" Date: Tue, 3 Mar 2026 12:33:43 -0500 Subject: [PATCH 7/7] Remove unneeded code --- .../requests/AnonymizeStorageRequestsRepository.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepository.java index a378e69744..4e85411b35 100644 --- a/src/main/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepository.java +++ b/src/main/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepository.java @@ -1,7 +1,6 @@ package org.folio.circulation.infrastructure.storage.requests; import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.stream.Collectors.toList; import static org.folio.circulation.support.http.ResponseMapping.forwardOnFailure; import static org.folio.circulation.support.http.ResponseMapping.mapUsingJson; import static org.folio.circulation.support.json.JsonStringArrayPropertyFetcher.toStream; @@ -40,12 +39,6 @@ public AnonymizeStorageRequestsRepository(Clients clients) { .otherwise(forwardOnFailure()); } - private static JsonObject createRequestPayload(RequestAnonymizationRecords records) { - JsonObject jsonObject = new JsonObject(); - jsonObject.put("requestIds", new JsonArray(records.getAnonymizedRequestIds())); - return jsonObject; - } - public CompletableFuture> postAnonymizeStorageRequests(RequestAnonymizationRecords records) {