Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,69 @@
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 static org.folio.HttpStatus.HTTP_CREATED;

import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.lang.invoke.MethodHandles;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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;

Check warning on line 24 in src/main/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepository.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused import 'io.vertx.core.json.JsonArray'.

See more on https://sonarcloud.io/project/issues?id=org.folio%3Amod-circulation&issues=AZy03Bo2cS25RTL_8ozC&open=AZy03Bo2cS25RTL_8ozC&pullRequest=1651
import io.vertx.core.json.JsonObject;

Check warning on line 25 in src/main/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepository.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused import 'io.vertx.core.json.JsonObject'.

See more on https://sonarcloud.io/project/issues?id=org.folio%3Amod-circulation&issues=AZy03Bo2cS25RTL_8ozD&open=AZy03Bo2cS25RTL_8ozD&pullRequest=1651
Comment thread
longvo-cv marked this conversation as resolved.

public class AnonymizeStorageRequestsRepository {
Comment thread
longvo-cv marked this conversation as resolved.
private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass());

private final CollectionResourceClient requestStorageClient;

public AnonymizeStorageRequestsRepository(Clients clients) {
requestStorageClient = clients.requestsBatchStorage();
}

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

Function<Response, Result<RequestAnonymizationRecords>> mapper = mapUsingJson(

Check warning on line 39 in src/main/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepository.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused "mapper" local variable.

See more on https://sonarcloud.io/project/issues?id=org.folio%3Amod-circulation&issues=AZyROKIuaz0lYLWl_eOy&open=AZyROKIuaz0lYLWl_eOy&pullRequest=1651
response -> {
var anonymized = toStream(response, "anonymizedRequests").toList();
log.info("createStorageRequestResponseInterpreter:: successfully anonymized {} requests",
anonymized.size());
return records.withAnonymizedRequests(anonymized);
});

Check warning on line 45 in src/main/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepository.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this useless assignment to local variable "mapper".

See more on https://sonarcloud.io/project/issues?id=org.folio%3Amod-circulation&issues=AZyROKIuaz0lYLWl_eOx&open=AZyROKIuaz0lYLWl_eOx&pullRequest=1651

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

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

log.debug("postAnonymizeStorageRequests:: parameters records: {}", records);

if (records.getAnonymizedRequestIds().isEmpty()) {
log.info("postAnonymizeStorageRequests:: no requests to anonymize, skipping");
return completedFuture(succeeded(records));
}

log.info("postAnonymizeStorageRequests:: anonymizing {} requests",
records.getAnonymizedRequestIds().size());
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;
Comment thread
longvo-cv marked this conversation as resolved.

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;

Check warning on line 23 in src/test/java/org/folio/circulation/infrastructure/storage/requests/AnonymizeStorageRequestsRepositoryTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused import 'io.vertx.core.json.JsonArray'.

See more on https://sonarcloud.io/project/issues?id=org.folio%3Amod-circulation&issues=AZyROKG6az0lYLWl_eOv&open=AZyROKG6az0lYLWl_eOv&pullRequest=1651
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;
}
}
Loading