diff --git a/src/main/java/org/folio/service/claiming/ClaimingService.java b/src/main/java/org/folio/service/claiming/ClaimingService.java index 4087c9b93..9da21b2d9 100644 --- a/src/main/java/org/folio/service/claiming/ClaimingService.java +++ b/src/main/java/org/folio/service/claiming/ClaimingService.java @@ -28,6 +28,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Objects; import static java.util.stream.Collectors.collectingAndThen; @@ -55,6 +56,7 @@ public class ClaimingService { private static final String CANNOT_RETRIEVE_CONFIG_ENTRIES = "Cannot retrieve config entries"; private static final String CANNOT_GROUP_PIECES_BY_VENDOR_MESSAGE = "Cannot group pieces by vendor"; private static final String CANNOT_CREATE_JOBS_AND_UPDATE_PIECES = "Cannot create jobs and update pieces"; + private static final String CANNOT_FIND_A_PIECE_BY_ID = "Cannot find a piece by '%s' id"; private final ConfigurationEntriesCache configurationEntriesCache; private final PieceStorageService pieceStorageService; @@ -112,22 +114,31 @@ private Future>> groupPieceIdsByVendorId(List p logger.info("groupPieceIdsByVendorId:: No pieces are grouped by vendor, pieceIds is empty"); return Future.succeededFuture(); } - logger.info("groupPieceIdsByVendorId:: Grouping pieces by vendor"); + logger.info("groupPieceIdsByVendorId:: Grouping pieces by vendor, pieceIds count: {}", pieceIds.size()); return pieceStorageService.getPiecesByIds(pieceIds, requestContext) .compose(pieces -> { + if (CollectionUtils.isEmpty(pieces)) { + logger.info("groupPieceIdsByVendorId:: No pieces are found by piece ids, pieceIds: {}", pieceIds); + return Future.succeededFuture(); + } var uniquePiecePoLinePairs = pieces.stream() + .filter(Objects::nonNull).filter(piece -> Objects.nonNull(piece.getId()) & Objects.nonNull(piece.getPoLineId())) .map(piece -> Pair.of(piece.getPoLineId(), piece.getId())).distinct() .toList(); - return collectResultsOnSuccess(createPieceIdByVendorFutures(uniquePiecePoLinePairs, requestContext)) + logger.info("groupPieceIdsByVendorId:: Prepared unique piece-poLine pairs, pairs: {}", uniquePiecePoLinePairs); + return collectResultsOnSuccess(createPieceIdByVendorFutures(pieces, uniquePiecePoLinePairs, requestContext)) .map(ClaimingService::transformAndGroupPieceIdsByVendorId); }); } - private List>> createPieceIdByVendorFutures(List> uniquePiecePoLinePairs, RequestContext requestContext) { + private List>> createPieceIdByVendorFutures(List pieces, List> uniquePiecePoLinePairs, + RequestContext requestContext) { var pieceIdByVendorIdFutures = new ArrayList>>(); uniquePiecePoLinePairs.forEach(piecePoLinePairs -> { - var pieceIdByVendorIdFuture = pieceStorageService.getPieceById(piecePoLinePairs.getRight(), requestContext) - .compose(piece -> createVendorPiecePair(piecePoLinePairs, piece, requestContext)); + var foundPiece = pieces.stream() + .filter(Objects::nonNull).filter(piece -> Objects.nonNull(piece.getId())).filter(piece -> piece.getId().equals(piecePoLinePairs.getRight())) + .findFirst().orElseThrow(() -> new NoSuchElementException(String.format(CANNOT_FIND_A_PIECE_BY_ID, piecePoLinePairs.getRight()))); + var pieceIdByVendorIdFuture = createVendorPiecePair(piecePoLinePairs, foundPiece, requestContext); if (Objects.nonNull(pieceIdByVendorIdFuture)) { pieceIdByVendorIdFutures.add(pieceIdByVendorIdFuture); } @@ -135,7 +146,8 @@ private List>> createPieceIdByVendorFutures(List> createVendorPiecePair(Pair piecePoLinePairs, Piece piece, RequestContext requestContext) { + private Future> createVendorPiecePair(Pair piecePoLinePairs, + Piece piece, RequestContext requestContext) { if (Objects.nonNull(piece) && !piece.getReceivingStatus().equals(Piece.ReceivingStatus.LATE)) { logger.info("createVendorPiecePair:: Ignoring processing of a piece not in LATE state, piece id: {}", piece.getId()); return Future.succeededFuture(); @@ -166,7 +178,7 @@ private Future createJobsByVendor(JsonObject config, Map { if (CollectionUtils.isEmpty(updatedPieceLists)) { - logger.info("createJobsByVendor:: No pieces were processes for claiming"); + logger.info("createJobsByVendor:: No pieces were processed for claiming"); return new ClaimingResults().withClaimingPieceResults(createErrorClaimingResults(pieceIdsByVendorId, CANNOT_CREATE_JOBS_AND_UPDATE_PIECES)); } var successClaimingPieceResults = createSuccessClaimingResults(updatedPieceLists); @@ -175,7 +187,8 @@ private Future createJobsByVendor(JsonObject config, Map>> createUpdatePiecesAndJobFutures(JsonObject config, Map> pieceIdsByVendorId, RequestContext requestContext) { + private List>> createUpdatePiecesAndJobFutures(JsonObject config, Map> pieceIdsByVendorId, + RequestContext requestContext) { var updatePiecesAndJobFutures = new ArrayList>>(); pieceIdsByVendorId.forEach((vendorId, pieceIds) -> config.stream() .filter(entry -> isExportTypeClaimsAndCorrectVendorId(vendorId, entry) && Objects.nonNull(entry.getValue())) diff --git a/src/test/java/org/folio/TestConstants.java b/src/test/java/org/folio/TestConstants.java index b45f24343..6437f7bd7 100644 --- a/src/test/java/org/folio/TestConstants.java +++ b/src/test/java/org/folio/TestConstants.java @@ -61,6 +61,7 @@ private TestConstants() {} public static final String NON_EXIST_HOLDINGS_SOURCE_TENANT = "nonExistHoldingsSource"; public static final String COMPOSITE_PO_LINES_PREFIX = "compositePoLines[0]."; public static final String OKAPI_URL = "X-Okapi-Url"; + public static final String OKAPI_TENANT = "X-Okapi-Tenant"; public static final Header INSTANCE_TYPE_CONTAINS_CODE_AS_INSTANCE_STATUS_TENANT_HEADER = new Header(OKAPI_HEADER_TENANT, INSTANCE_TYPE_CONTAINS_CODE_AS_INSTANCE_STATUS_TENANT); public static final Header NON_EXIST_INSTANCE_STATUS_TENANT_HEADER = new Header(OKAPI_HEADER_TENANT, NON_EXIST_INSTANCE_STATUS_TENANT); diff --git a/src/test/java/org/folio/rest/impl/ClaimingApiTest.java b/src/test/java/org/folio/rest/impl/ClaimingApiTest.java index 3ed06a37e..a3c4b4d3f 100644 --- a/src/test/java/org/folio/rest/impl/ClaimingApiTest.java +++ b/src/test/java/org/folio/rest/impl/ClaimingApiTest.java @@ -2,6 +2,7 @@ import io.restassured.http.Header; import io.vertx.core.json.JsonObject; +import io.vertx.junit5.VertxExtension; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.folio.ApiTestSuite; @@ -11,13 +12,17 @@ import org.folio.rest.jaxrs.model.ClaimingPieceResult; import org.folio.rest.jaxrs.model.ClaimingResults; import org.folio.rest.jaxrs.model.CompositePoLine; +import org.folio.rest.jaxrs.model.Piece; +import org.folio.rest.jaxrs.model.PieceCollection; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import java.util.Collection; import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; @@ -36,10 +41,12 @@ import static org.folio.TestUtils.getMinimalOrder; import static org.folio.TestUtils.getMockAsJson; import static org.folio.orders.utils.ResourcePathResolver.ORGANIZATION_STORAGE; +import static org.folio.orders.utils.ResourcePathResolver.PIECES_STORAGE; import static org.folio.orders.utils.ResourcePathResolver.PO_LINES_STORAGE; import static org.folio.orders.utils.ResourcePathResolver.PURCHASE_ORDER_STORAGE; import static org.folio.rest.impl.MockServer.BASE_MOCK_DATA_PATH; import static org.folio.rest.impl.MockServer.ORGANIZATION_COLLECTION; +import static org.folio.rest.impl.MockServer.PIECES_COLLECTION; import static org.folio.rest.impl.MockServer.PO_LINES_COLLECTION; import static org.folio.rest.impl.MockServer.addMockEntry; import static org.folio.rest.impl.MockServer.getDataExportSpringJobCreations; @@ -49,6 +56,8 @@ import static org.folio.rest.impl.MockServer.getPieceUpdates; import static org.folio.rest.impl.MockServer.getPoLineSearches; import static org.folio.rest.impl.MockServer.getPurchaseOrderRetrievals; +import static org.folio.rest.jaxrs.model.ClaimingPieceResult.Status.FAILURE; +import static org.folio.rest.jaxrs.model.ClaimingPieceResult.Status.SUCCESS; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; @@ -57,6 +66,7 @@ import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; +@ExtendWith(VertxExtension.class) public class ClaimingApiTest { private static final Logger logger = LogManager.getLogger(); @@ -64,6 +74,7 @@ public class ClaimingApiTest { private static final String ORGANIZATIONS_KEY = "organizations"; private static final String PO_LINES_KEY = "poLines"; + private static final String PIECES_KEY = "pieces"; private static final String CLAIMING_MOCK_DATA_FOLDER = "claiming/"; @BeforeAll @@ -88,13 +99,11 @@ static void after() { } private static Stream testPostOrdersClaimArgs() { + var payloadFile = "send-claims-1-piece-1-vendor-1-job.json"; + var mockHitDto = new MockHitDto(2, 2, 2, 1, 1, 1, 1, 1); return Stream.of( - Arguments.of("One piece One vendor One Job", 0, 17, - new MockHitDto(3, 2, 2, 1, 1, 1, 1, 1), - "send-claims-1-piece-1-vendor-1-job.json", EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10_CLAIMS, ClaimingPieceResult.Status.SUCCESS), - Arguments.of("One piece One vendor No Job", 0, 17, - null, - "send-claims-1-piece-1-vendor-1-job.json", EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10, ClaimingPieceResult.Status.FAILURE) + Arguments.of("One piece One vendor One Job", 0, 17, 69, mockHitDto, payloadFile, EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10_CLAIMS, SUCCESS), + Arguments.of("One piece One vendor No Job", 0, 17, 69, null, payloadFile, EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10, FAILURE) ); } @@ -125,7 +134,7 @@ public MockHitDto(int pieceSearches, int polSearches, int purchaseOrderRetrieval @ParameterizedTest @MethodSource("testPostOrdersClaimArgs") - void testPostOrdersClaim(String name, int vendorIdx, int polIdx, MockHitDto dto, + void testPostOrdersClaim(String name, int vendorIdx, int poLineIdx, int pieceIdx, MockHitDto dto, String payloadFile, Header header, ClaimingPieceResult.Status expectedStatus) { logger.info("Testing postOrdersClaim, name: {}", name); @@ -134,21 +143,33 @@ void testPostOrdersClaim(String name, int vendorIdx, int polIdx, MockHitDto dto, .mapTo(Organization.class); var poLine = getMockAsJson(PO_LINES_COLLECTION) .getJsonArray(PO_LINES_KEY) - .getJsonObject(polIdx) + .getJsonObject(poLineIdx) .mapTo(CompositePoLine.class); var purchaseOrder = getMinimalOrder(poLine) .withVendor(organization.getId()); + var piece = getMockAsJson(PIECES_COLLECTION) + .getJsonArray(PIECES_KEY) + .getJsonObject(pieceIdx) + .mapTo(Piece.class); addMockEntry(ORGANIZATION_STORAGE, organization); addMockEntry(PURCHASE_ORDER_STORAGE, purchaseOrder); addMockEntry(PO_LINES_STORAGE, poLine); + addMockEntry(PIECES_STORAGE, piece); var mockDataPath = BASE_MOCK_DATA_PATH + CLAIMING_MOCK_DATA_FOLDER + payloadFile; var request = JsonObject.mapFrom(getMockAsJson(mockDataPath).mapTo(ClaimingCollection.class)).encode(); var response = verifyPostResponse(ORDERS_CLAIMING_ENDPOINT, request, prepareHeaders(header), APPLICATION_JSON, CREATED.code()) .as(ClaimingResults.class); - var pieceSearches = getPieceSearches(); + // Filter out any dummy pieces without ids that are loaded from other tests + var pieceSearches = getPieceSearches().stream() + .map(JsonObject::mapFrom).map(json -> json.mapTo(PieceCollection.class)) + .map(collection -> collection.getPieces().stream() + .filter(entry -> Objects.nonNull(entry.getId())).filter(entry -> entry.getId().equals(piece.getId())) + .toList()) + .flatMap(Collection::stream) + .toList(); var polSearches = getPoLineSearches(); var purchaseOrderRetrievals = getPurchaseOrderRetrievals(); var organizationSearches = getOrganizationSearches(); @@ -179,7 +200,7 @@ void testPostOrdersClaim(String name, int vendorIdx, int polIdx, MockHitDto dto, .forEach(result -> { assertThat(result.getPieceId(), not(nullValue())); assertThat(result.getStatus(), is(expectedStatus)); - if (expectedStatus == ClaimingPieceResult.Status.SUCCESS) { + if (expectedStatus == SUCCESS) { assertThat(result.getError(), is(nullValue())); } else { assertThat(result.getError(), is(notNullValue())); diff --git a/src/test/java/org/folio/rest/impl/MockServer.java b/src/test/java/org/folio/rest/impl/MockServer.java index 9630945d3..2f5890688 100644 --- a/src/test/java/org/folio/rest/impl/MockServer.java +++ b/src/test/java/org/folio/rest/impl/MockServer.java @@ -17,6 +17,7 @@ import static org.folio.TestConstants.EMPTY_CONFIG_TENANT; import static org.folio.TestConstants.EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_1; import static org.folio.TestConstants.EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10; +import static org.folio.TestConstants.EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10_CLAIMS; import static org.folio.TestConstants.ID; import static org.folio.TestConstants.ID_BAD_FORMAT; import static org.folio.TestConstants.ID_DOES_NOT_EXIST; @@ -37,6 +38,7 @@ import static org.folio.TestConstants.NON_EXIST_INSTANCE_TYPE_TENANT_HEADER; import static org.folio.TestConstants.NON_EXIST_LOAN_TYPE_TENANT; import static org.folio.TestConstants.NON_EXIST_LOAN_TYPE_TENANT_HEADER; +import static org.folio.TestConstants.OKAPI_TENANT; import static org.folio.TestConstants.PO_ID_GET_LINES_INTERNAL_SERVER_ERROR; import static org.folio.TestConstants.PO_LINE_NUMBER_VALUE; import static org.folio.TestConstants.PROTECTED_READ_ONLY_TENANT; @@ -1200,18 +1202,16 @@ private void handleGetItemRecordsFromStorage(RoutingContext ctx) { // Attempt to find POLine in mock server memory List itemsList = getRqRsEntries(HttpMethod.SEARCH, ITEM_RECORDS); + JsonObject items; if (!itemsList.isEmpty()) { - JsonObject items = new JsonObject() + items = new JsonObject() .put("items", itemsList) .put(TOTAL_RECORDS, itemsList.size()); - - addServerRqRsData(HttpMethod.GET, ITEM_RECORDS, items); - serverResponse(ctx, 200, APPLICATION_JSON, items.encodePrettily()); } else { - JsonObject items = new JsonObject().put("items", new JsonArray()); - addServerRqRsData(HttpMethod.GET, ITEM_RECORDS, items); - serverResponse(ctx, 200, APPLICATION_JSON, items.encodePrettily()); + items = new JsonObject().put("items", new JsonArray()); } + addServerRqRsData(HttpMethod.GET, ITEM_RECORDS, items); + serverResponse(ctx, 200, APPLICATION_JSON, items.encodePrettily()); } private void handleGetInventoryItemRecords(RoutingContext ctx) { @@ -1258,7 +1258,7 @@ private void handleGetInventoryItemRecords(RoutingContext ctx) { JsonObject item = (JsonObject) iterator.next(); if (!purchaseOrderLineIdentifier.equals(item.getString(ITEM_PURCHASE_ORDER_LINE_IDENTIFIER)) - || !holdingsRecordId.equals(item.getString(ITEM_HOLDINGS_RECORD_ID))) { + || !holdingsRecordId.equals(item.getString(ITEM_HOLDINGS_RECORD_ID))) { iterator.remove(); } } @@ -1902,11 +1902,7 @@ private void handleGetPieceById(RoutingContext ctx) { String pieceId = ctx.request().getParam(ID); logger.info("id: {}", pieceId); try { - if ("dcd0ba36-b660-4751-b9fe-c8ac61ff6f99".equals(pieceId)) { - JsonObject data = new JsonObject(getMockData(PIECE_RECORDS_MOCK_DATA_PATH + String.format("pieceRecord-%s.json", pieceId))); - logger.info("handleGetPieceById custom, data: {}", data.encodePrettily()); - serverResponse(ctx, HttpStatus.HTTP_OK.toInt(), APPLICATION_JSON, data.encodePrettily()); - } else if (ID_DOES_NOT_EXIST.equals(pieceId)) { + if (ID_DOES_NOT_EXIST.equals(pieceId)) { serverResponse(ctx, 404, APPLICATION_JSON, pieceId); } else if (ID_FOR_INTERNAL_SERVER_ERROR.equals(pieceId)) { serverResponse(ctx, 500, APPLICATION_JSON, pieceId); @@ -1919,6 +1915,7 @@ private void handleGetPieceById(RoutingContext ctx) { } else if (PIECE_POLINE_CONSISTENT_RECEIPT_STATUS_ID.equals(pieceId)) { data = new JsonObject(getMockData(PIECE_RECORDS_MOCK_DATA_PATH + "pieceRecord-received-consistent-receipt-status-5b454292-6aaa-474f-9510-b59a564e0c8d2.json")); } else { + // Load piece by id data = new JsonObject(getMockData(PIECE_RECORDS_MOCK_DATA_PATH + String.format("pieceRecord-%s.json", pieceId))); } } @@ -1941,8 +1938,8 @@ private void handleGetPieces(RoutingContext ctx) { serverResponse(ctx, 500, APPLICATION_JSON, Response.Status.INTERNAL_SERVER_ERROR.getReasonPhrase()); } else { PieceCollection pieces; - logger.info("handleGetPieces (all records), pieces present: {}", getMockEntries(PIECES_STORAGE, Piece.class).isPresent()); - if (getMockEntries(PIECES_STORAGE, Piece.class).isPresent()) { + logger.info("handleGetPieces (all records), pieces already present: {}", getMockEntries(PIECES_STORAGE, Piece.class).isPresent()); + if (!isTenantForClaiming(requestHeaders) && getMockEntries(PIECES_STORAGE, Piece.class).isPresent()) { logger.info("handleGetPieces (all records)"); try { List piecesList = getMockEntries(PIECES_STORAGE, Piece.class).get(); @@ -1999,7 +1996,6 @@ private void handleGetPieces(RoutingContext ctx) { } pieces.setTotalRecords(pieces.getPieces().size()); - } catch (Exception e) { logger.info("handleGetPieces (with empty piece collection on exception)"); pieces = new PieceCollection(); @@ -2018,6 +2014,11 @@ private void handleGetPieces(RoutingContext ctx) { } } + // Prevent loading of unnecessary pieces from other tests for tenant that was made specifically for Claiming + private boolean isTenantForClaiming(MultiMap requestHeaders) { + return requestHeaders.get(OKAPI_TENANT.toLowerCase()).equals(EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10_CLAIMS.getValue()); + } + private void handleGetTitles(RoutingContext ctx) { String query = StringUtils.trimToEmpty(ctx.request().getParam(QUERY)); addServerRqQuery(TITLES, query); @@ -2418,7 +2419,7 @@ private void handleTransactionGetEntry(RoutingContext ctx) { } private void handleUserTenantsGetEntry(RoutingContext ctx) { - String body = new JsonObject().put("userTenants", org.assertj.core.util.Lists.emptyList()).encodePrettily(); + String body = new JsonObject().put("userTenants", Collections.emptyList()).encodePrettily(); serverResponse(ctx, HttpStatus.HTTP_OK.toInt(), APPLICATION_JSON, body); addServerRqRsData(HttpMethod.GET, USER_TENANTS_ENDPOINT, new JsonObject(body)); }