Skip to content
8 changes: 8 additions & 0 deletions src/main/java/org/folio/circulation/domain/RequestStatus.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ public enum RequestStatus {
private static final EnumSet<RequestStatus> OPEN_STATUSES = EnumSet.of(
OPEN_NOT_YET_FILLED, OPEN_AWAITING_PICKUP, OPEN_IN_TRANSIT, OPEN_AWAITING_DELIVERY);

private static final EnumSet<RequestStatus> CLOSED_STATUSES = EnumSet.of(
CLOSED_FILLED, CLOSED_CANCELLED, CLOSED_UNFILLED, CLOSED_PICKUP_EXPIRED);

private final String value;

public static String invalidStatusErrorMessage() {
Expand Down Expand Up @@ -60,6 +63,11 @@ public static List<String> openStates() {
.collect(Collectors.toList());
}

public static List<String> closedStates() {
return CLOSED_STATUSES.stream().map(RequestStatus::getValue)
.toList();
}

public boolean isValid() {
return this != NONE;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package org.folio.circulation.infrastructure.storage.requests;

import static java.util.concurrent.CompletableFuture.completedFuture;
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 org.folio.circulation.storage.RequestBatch;

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.requestsBatchStorage();
}

private static ResponseInterpreter<RequestAnonymizationRecords>
createStorageRequestResponseInterpreter(RequestAnonymizationRecords records) {

Function<Response, Result<RequestAnonymizationRecords>> mapper = mapUsingJson(
response -> records.withAnonymizedRequests(
toStream(response, "anonymizedRequests").toList()));

return new ResponseInterpreter<RequestAnonymizationRecords>()
.on(201, Result.of(() -> records))
.otherwise(forwardOnFailure());
}

public CompletableFuture<Result<RequestAnonymizationRecords>>
postAnonymizeStorageRequests(RequestAnonymizationRecords records) {

if (records.getAnonymizedRequestIds().isEmpty()) {
return completedFuture(succeeded(records));
}
RequestBatch requestBatch = new RequestBatch(records.getAnonymizedRequests());

return requestStorageClient.post(requestBatch.toJson())
.thenApply(createStorageRequestResponseInterpreter(records)::flatMap);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
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;
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;
Expand All @@ -17,6 +17,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;
Expand Down Expand Up @@ -317,6 +319,36 @@ public CompletableFuture<Result<Collection<Request>>> fetchRequests(Collection<S
.thenApply(mapResult(MultipleRecords::getRecords));
}

public CompletableFuture<Result<MultipleRecords<Request>>> findRequestsToAnonymize(
PageLimit pageLimit) {

log.debug("findRequestsToAnonymize:: parameters pageLimit: {}", pageLimit);

Result<CqlQuery> cqlQuery = exactMatchAny("status", closedStates())
.combine(CqlQuery.hasValue("requesterId"), CqlQuery::and);

return queryRequestStorage(cqlQuery, pageLimit);
}

public CompletableFuture<Result<MultipleRecords<Request>>> findClosedRequests(
String userId, PageLimit pageLimit) {

log.debug("findClosedRequests:: parameters userId: {}, pageLimit: {}", userId, pageLimit);

Result<CqlQuery> userQuery = exactMatch("requesterId", userId);
Result<CqlQuery> statusQuery = exactMatchAny("status", closedStates());

return queryRequestStorage(statusQuery.combine(userQuery, CqlQuery::and), pageLimit);
}

private CompletableFuture<Result<MultipleRecords<Request>>> queryRequestStorage(
Result<CqlQuery> cqlQuery, PageLimit pageLimit) {

return cqlQuery
.after(q -> requestsStorageClient.getMany(q, pageLimit))
.thenApply(result -> result.next(this::mapResponseToRequests));
}

private CompletableFuture<Result<Request>> fetchRequester(Result<Request> result) {
log.debug("fetchRequester:: parameters result: {}", ()-> resultAsString(result));
return result.combineAfter(request ->
Expand Down
32 changes: 32 additions & 0 deletions src/test/java/org/folio/circulation/domain/RequestStatusTest.java
Original file line number Diff line number Diff line change
@@ -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<String> 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<String> 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"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
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.junit.jupiter.api.Assertions.assertTrue;
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.requestsBatchStorage()).thenReturn(client);

AnonymizeStorageRequestsRepository repository =
new AnonymizeStorageRequestsRepository(clients);

RequestAnonymizationRecords emptyRecords = new RequestAnonymizationRecords();

Result<RequestAnonymizationRecords> result =
repository.postAnonymizeStorageRequests(emptyRecords).join();

assertTrue(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.requestsBatchStorage()).thenReturn(client);

Response response = mock(Response.class);
when(response.getStatusCode()).thenReturn(201);

JsonObject responseBody = new JsonObject();

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<RequestAnonymizationRecords> result =
repository.postAnonymizeStorageRequests(records).join();

assertTrue(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.requestsBatchStorage()).thenReturn(client);

Response response = mock(Response.class);
when(response.getStatusCode()).thenReturn(201);
when(response.getJson()).thenReturn(new JsonObject());

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"));

Result<RequestAnonymizationRecords> result =
repository.postAnonymizeStorageRequests(records).join();

assertTrue(result.succeeded());
verify(client).post(any(JsonObject.class));
}
}
Original file line number Diff line number Diff line change
@@ -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<MultipleRecords<Request>> 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<MultipleRecords<Request>> 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<MultipleRecords<Request>> 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<MultipleRecords<Request>> 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;
}
}