From ec49d423767ece997b86b011bbebbbe8f4a4785d Mon Sep 17 00:00:00 2001 From: pbobylev Date: Fri, 7 Jun 2024 17:15:23 +0500 Subject: [PATCH] MODINV-986: unit test + fixes --- .../actions/CreateInstanceEventHandler.java | 2 +- .../dataimport/util/AdditionalFieldsUtil.java | 1 + .../CreateInstanceIngressEventHandler.java | 117 ++++-- .../handler/InstanceIngressEventHandler.java | 4 +- .../InstanceIngressUpdateEventHandler.java | 3 +- .../java/org/folio/inventory/TestUtil.java | 7 + .../MarcInstanceSharingHandlerImplTest.java | 16 +- .../util/RestDataImportHelperTest.java | 33 +- .../CreateInstanceEventHandlerTest.java | 19 +- .../ReplaceInstanceEventHandlerTest.java | 5 +- ...teInstanceIngressEventHandlerUnitTest.java | 375 ++++++++++++++++++ 11 files changed, 490 insertions(+), 92 deletions(-) create mode 100644 src/test/java/org/folio/inventory/instanceingress/handler/CreateInstanceIngressEventHandlerUnitTest.java diff --git a/src/main/java/org/folio/inventory/dataimport/handlers/actions/CreateInstanceEventHandler.java b/src/main/java/org/folio/inventory/dataimport/handlers/actions/CreateInstanceEventHandler.java index 5dd8d8120..caa1f7373 100644 --- a/src/main/java/org/folio/inventory/dataimport/handlers/actions/CreateInstanceEventHandler.java +++ b/src/main/java/org/folio/inventory/dataimport/handlers/actions/CreateInstanceEventHandler.java @@ -177,7 +177,7 @@ public CompletableFuture handle(DataImportEventPayload d return future; } - private String getInstanceId(Record record) { + protected String getInstanceId(Record record) { String subfield999ffi = ParsedRecordUtil.getAdditionalSubfieldValue(record.getParsedRecord(), ParsedRecordUtil.AdditionalSubfields.I); return isEmpty(subfield999ffi) ? UUID.randomUUID().toString() : subfield999ffi; } diff --git a/src/main/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtil.java b/src/main/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtil.java index 94da11dab..2c4bbcba3 100644 --- a/src/main/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtil.java +++ b/src/main/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtil.java @@ -72,6 +72,7 @@ public final class AdditionalFieldsUtil { private static final char TAG_035_IND = ' '; private static final String ANY_STRING = "*"; private static final char INDICATOR = 'f'; + public static final char SUBFIELD_B = 'b'; public static final char SUBFIELD_I = 'i'; private static final String HR_ID_FIELD = "hrid"; private static final CacheLoader parsedRecordContentCacheLoader; diff --git a/src/main/java/org/folio/inventory/instanceingress/handler/CreateInstanceIngressEventHandler.java b/src/main/java/org/folio/inventory/instanceingress/handler/CreateInstanceIngressEventHandler.java index 14c92ce9a..7640a0d25 100644 --- a/src/main/java/org/folio/inventory/instanceingress/handler/CreateInstanceIngressEventHandler.java +++ b/src/main/java/org/folio/inventory/instanceingress/handler/CreateInstanceIngressEventHandler.java @@ -2,8 +2,12 @@ import static java.lang.String.format; import static java.util.Objects.isNull; +import static java.util.Optional.ofNullable; +import static org.apache.logging.log4j.LogManager.getLogger; +import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.SUBFIELD_B; import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.TAG_035; import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.TAG_035_SUB; +import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.TAG_999; import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.reorderMarcRecordFields; import static org.folio.inventory.dataimport.util.MappingConstants.INSTANCE_REQUIRED_FIELDS; import static org.folio.inventory.dataimport.util.MappingConstants.MARC_BIB_RECORD_FORMAT; @@ -19,9 +23,10 @@ import io.vertx.core.json.JsonObject; import java.util.Date; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.CompletableFuture; -import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.folio.HttpStatus; import org.folio.MappingMetadataDto; @@ -37,11 +42,13 @@ import org.folio.inventory.instanceingress.InstanceIngressEventConsumer; import org.folio.inventory.services.IdStorageService; import org.folio.inventory.storage.Storage; +import org.folio.kafka.exception.DuplicateEventException; import org.folio.processing.exceptions.EventProcessingException; import org.folio.processing.mapping.defaultmapper.RecordMapper; import org.folio.processing.mapping.defaultmapper.RecordMapperBuilder; import org.folio.processing.mapping.defaultmapper.processor.parameters.MappingParameters; import org.folio.rest.jaxrs.model.InstanceIngressEvent; +import org.folio.rest.jaxrs.model.InstanceIngressPayload; import org.folio.rest.jaxrs.model.ParsedRecord; import org.folio.rest.jaxrs.model.RawRecord; import org.folio.rest.jaxrs.model.Record; @@ -49,8 +56,10 @@ public class CreateInstanceIngressEventHandler extends CreateInstanceEventHandler implements InstanceIngressEventHandler { - private static final Logger LOGGER = LogManager.getLogger(CreateInstanceIngressEventHandler.class); + public static final String BIBFRAME_ID = "bibframeId"; + private static final Logger LOGGER = getLogger(CreateInstanceIngressEventHandler.class); private static final String BIBFRAME = "(bibframe) "; + private static final String FAILURE = "Failed to process InstanceIngressEvent with id {}"; private final Context context; private final InstanceCollection instanceCollection; @@ -66,54 +75,69 @@ public CreateInstanceIngressEventHandler(PrecedingSucceedingTitlesHelper precedi } @Override - public CompletableFuture handle(InstanceIngressEvent event) { + public CompletableFuture handle(InstanceIngressEvent event) { try { LOGGER.info("Processing InstanceIngressEvent with id '{}' for instance creation", event.getId()); + var future = new CompletableFuture(); if (eventContainsNoData(event)) { var message = format("InstanceIngressEvent message does not contain required data to create Instance for eventId: '%s'", event.getId()); LOGGER.error(message); return CompletableFuture.failedFuture(new EventProcessingException(message)); } - super.getMappingMetadataCache().getByRecordType(InstanceIngressEventConsumer.class.getSimpleName(), context, MARC_BIB_RECORD_TYPE) - .map(metadataOptional -> metadataOptional.orElseThrow(() -> new EventProcessingException("MappingMetadata was not found for marc-bib record type"))) - .compose(mappingMetadataDto -> prepareAndExecuteMapping(mappingMetadataDto, event)) - .compose(instance -> validateInstance(instance, event)) - .compose(instance -> saveInstance(instance, event)); - return CompletableFuture.completedFuture(event); + var targetRecord = constructMarcBibRecord(event.getEventPayload()); + var instanceId = ofNullable(event.getEventPayload().getSourceRecordIdentifier()).orElseGet(() -> getInstanceId(targetRecord)); + idStorageService.store(targetRecord.getId(), instanceId, context.getTenantId()) + .compose(res -> super.getMappingMetadataCache().getByRecordType(InstanceIngressEventConsumer.class.getSimpleName(), context, MARC_BIB_RECORD_TYPE)) + .compose(metadataOptional -> metadataOptional.map(metadata -> prepareAndExecuteMapping(metadata, targetRecord, event, instanceId)) + .orElseGet(() -> Future.failedFuture("MappingMetadata was not found for marc-bib record type"))) + .compose(instance -> validateInstance(instance, event)) + .compose(instance -> saveInstance(instance, event)) + .onFailure(e -> { + if (!(e instanceof DuplicateEventException)) { + LOGGER.error(FAILURE, event.getId(), e); + } + future.completeExceptionally(e); + }) + .onComplete(ar -> future.complete(ar.result())); + return future; } catch (Exception e) { - LOGGER.error("Failed to process InstanceIngressEvent with id {}", event.getId(), e); + LOGGER.error(FAILURE, event.getId(), e); return CompletableFuture.failedFuture(e); } } - private static boolean eventContainsNoData(InstanceIngressEvent event) { - return isNull(event.getEventPayload()) || isNull(event.getEventPayload().getSourceRecordObject()); + private boolean eventContainsNoData(InstanceIngressEvent event) { + return isNull(event.getEventPayload()) + || isNull(event.getEventPayload().getSourceRecordObject()) + || isNull(event.getEventPayload().getSourceType()); } - private Future prepareAndExecuteMapping(MappingMetadataDto mappingMetadata, InstanceIngressEvent event) { + private Future prepareAndExecuteMapping(MappingMetadataDto mappingMetadata, + Record targetRecord, + InstanceIngressEvent event, + String instanceId) { return postSnapshotInSrsAndHandleResponse(event.getId()) .compose(snapshot -> { try { - var marcBibRecord = constructMarcBibRecord(event); - LOGGER.info("Manipulating fields of a Record from InstanceIngressEvent with id '{}'", event.getId()); var mappingParameters = Json.decodeValue(mappingMetadata.getMappingParams(), MappingParameters.class); - AdditionalFieldsUtil.updateLatestTransactionDate(marcBibRecord, mappingParameters); - AdditionalFieldsUtil.move001To035(marcBibRecord); - AdditionalFieldsUtil.normalize035(marcBibRecord); - AdditionalFieldsUtil.addFieldToMarcRecord(marcBibRecord, TAG_035, TAG_035_SUB, - BIBFRAME + event.getEventPayload().getSourceRecordIdentifier()); + AdditionalFieldsUtil.updateLatestTransactionDate(targetRecord, mappingParameters); + AdditionalFieldsUtil.move001To035(targetRecord); + AdditionalFieldsUtil.normalize035(targetRecord); + if (event.getEventPayload().getAdditionalProperties().containsKey(BIBFRAME_ID)) { + AdditionalFieldsUtil.addFieldToMarcRecord(targetRecord, TAG_035, TAG_035_SUB, + BIBFRAME + event.getEventPayload().getAdditionalProperties().get(BIBFRAME_ID)); + } LOGGER.info("Mapping a Record from InstanceIngressEvent with id '{}' into an Instance", event.getId()); - var parsedRecord = new JsonObject((String) marcBibRecord.getParsedRecord().getContent()); + var parsedRecord = new JsonObject((String) targetRecord.getParsedRecord().getContent()); RecordMapper recordMapper = RecordMapperBuilder.buildMapper(MARC_BIB_RECORD_FORMAT); var instance = recordMapper.mapRecord(parsedRecord, mappingParameters, new JsonObject(mappingMetadata.getMappingRules())); + instance.setId(instanceId); instance.setSource(event.getEventPayload().getSourceType().value()); LOGGER.info("Mapped Instance from InstanceIngressEvent with id '{}': {}", event.getId(), instance); - return super.idStorageService.store(marcBibRecord.getId(), instance.getId(), context.getTenantId()) - .map(r -> instance) - .onFailure(e -> LOGGER.error("Error creating relationship of inventory recordId '{} and instanceId '{}'", event.getId(), instance.getId())); + return Future.succeededFuture(instance); } catch (Exception e) { LOGGER.warn("Error during preparing and executing mapping:", e); return Future.failedFuture(e); @@ -121,21 +145,21 @@ private Future prepareAndExecuteMapping(MappingMetadataDto m }); } - private Record constructMarcBibRecord(InstanceIngressEvent event) { - LOGGER.info("Constructing a Record from InstanceIngressEvent with id '{}'", event.getId()); + private Record constructMarcBibRecord(InstanceIngressPayload eventPayload) { + var recordId = UUID.randomUUID().toString(); var marcBibRecord = new org.folio.rest.jaxrs.model.Record() - .withId(event.getId()) + .withId(recordId) .withRecordType(MARC_BIB) - .withSnapshotId(event.getId()) + .withSnapshotId(recordId) .withRawRecord(new RawRecord() - .withId(event.getId()) - .withContent(event.getEventPayload().getSourceRecordObject()) + .withId(recordId) + .withContent(eventPayload.getSourceRecordObject()) ) .withParsedRecord(new ParsedRecord() - .withId(event.getId()) - .withContent(event.getEventPayload().getSourceRecordObject()) + .withId(recordId) + .withContent(eventPayload.getSourceRecordObject()) ); - event.getEventPayload() + eventPayload .withAdditionalProperty(MARC_BIBLIOGRAPHIC.value(), marcBibRecord); return marcBibRecord; } @@ -149,40 +173,46 @@ private Future validateInstance(org.folio.Instance instance, InstanceI .orElseGet(() -> { var mappedInstance = Instance.fromJson(instanceAsJson); var uuidErrors = ValidationUtil.validateUUIDs(mappedInstance); - return failIfErrors(uuidErrors, event.getId()) - .orElseGet(() -> Future.succeededFuture(mappedInstance)); + return failIfErrors(uuidErrors, event.getId()).orElseGet(() -> Future.succeededFuture(mappedInstance)); }); } catch (Exception e) { return Future.failedFuture(e); } } - private static Optional> failIfErrors(List errors, String eventId) { + private Optional> failIfErrors(List errors, String eventId) { if (errors.isEmpty()) { return Optional.empty(); } - var msg = format("Mapped Instance is invalid: %s, from InstanceIngressEvent with id '%s' ", errors, eventId); + var msg = format("Mapped Instance is invalid: %s, from InstanceIngressEvent with id '%s'", errors, eventId); LOGGER.warn(msg); return Optional.of(Future.failedFuture(msg)); } - private Future saveInstance(Instance instance, InstanceIngressEvent event) { + private Future saveInstance(Instance instance, InstanceIngressEvent event) { LOGGER.info("Saving Instance from InstanceIngressEvent with id '{}': {}", event.getId(), instance); var targetRecord = (Record) event.getEventPayload().getAdditionalProperties().get(MARC_BIBLIOGRAPHIC.value()); var sourceContent = targetRecord.getParsedRecord().getContent().toString(); - super.addInstance(instance, instanceCollection) + return super.addInstance(instance, instanceCollection) .compose(createdInstance -> getPrecedingSucceedingTitlesHelper().createPrecedingSucceedingTitles(instance, context).map(createdInstance)) - .compose(createdInstance -> executeFieldsManipulation(createdInstance, targetRecord)) + .compose(createdInstance -> executeFieldsManipulation(createdInstance, targetRecord, event.getEventPayload().getAdditionalProperties())) .compose(createdInstance -> { var targetContent = targetRecord.getParsedRecord().getContent().toString(); var content = reorderMarcRecordFields(sourceContent, targetContent); targetRecord.setParsedRecord(targetRecord.getParsedRecord().withContent(content)); return saveRecordInSrsAndHandleResponse(event, targetRecord, createdInstance); }); - return Future.succeededFuture(); } - protected Future saveRecordInSrsAndHandleResponse(InstanceIngressEvent event, Record srcRecord, Instance instance) { + private Future executeFieldsManipulation(Instance instance, Record srcRecord, + Map eventProperties) { + if (eventProperties.containsKey(BIBFRAME_ID)) { + AdditionalFieldsUtil.addFieldToMarcRecord(srcRecord, TAG_999, SUBFIELD_B, (String) eventProperties.get(BIBFRAME_ID)); + } + return super.executeFieldsManipulation(instance, srcRecord); + } + + private Future saveRecordInSrsAndHandleResponse(InstanceIngressEvent event, Record srcRecord, Instance instance) { LOGGER.info("Saving record in SRS and handling a response for an Instance with id '{}':", instance.getId()); Promise promise = Promise.promise(); getSourceStorageRecordsClient(context.getOkapiLocation(), context.getToken(), context.getTenantId()) @@ -209,7 +239,8 @@ private Future postSnapshotInSrsAndHandleResponse(String id) { .withJobExecutionId(id) .withProcessingStartedDate(new Date()) .withStatus(PROCESSING_IN_PROGRESS); - return super.postSnapshotInSrsAndHandleResponse(context.getOkapiLocation(), context.getToken(), snapshot, context.getTenantId()); + return super.postSnapshotInSrsAndHandleResponse(context.getOkapiLocation(), + context.getToken(), snapshot, context.getTenantId()); } } diff --git a/src/main/java/org/folio/inventory/instanceingress/handler/InstanceIngressEventHandler.java b/src/main/java/org/folio/inventory/instanceingress/handler/InstanceIngressEventHandler.java index 85187d9c0..5d15b87da 100644 --- a/src/main/java/org/folio/inventory/instanceingress/handler/InstanceIngressEventHandler.java +++ b/src/main/java/org/folio/inventory/instanceingress/handler/InstanceIngressEventHandler.java @@ -1,11 +1,11 @@ package org.folio.inventory.instanceingress.handler; import java.util.concurrent.CompletableFuture; +import org.folio.inventory.domain.instances.Instance; import org.folio.rest.jaxrs.model.InstanceIngressEvent; -import org.folio.rest.jaxrs.model.InstanceIngressPayload; public interface InstanceIngressEventHandler { - CompletableFuture handle(InstanceIngressEvent instanceIngressEvent); + CompletableFuture handle(InstanceIngressEvent instanceIngressEvent); } diff --git a/src/main/java/org/folio/inventory/instanceingress/handler/InstanceIngressUpdateEventHandler.java b/src/main/java/org/folio/inventory/instanceingress/handler/InstanceIngressUpdateEventHandler.java index 49de12174..f871ab228 100644 --- a/src/main/java/org/folio/inventory/instanceingress/handler/InstanceIngressUpdateEventHandler.java +++ b/src/main/java/org/folio/inventory/instanceingress/handler/InstanceIngressUpdateEventHandler.java @@ -1,12 +1,13 @@ package org.folio.inventory.instanceingress.handler; import java.util.concurrent.CompletableFuture; +import org.folio.inventory.domain.instances.Instance; import org.folio.rest.jaxrs.model.InstanceIngressEvent; public class InstanceIngressUpdateEventHandler implements InstanceIngressEventHandler { @Override - public CompletableFuture handle(InstanceIngressEvent instanceIngressEvent) { + public CompletableFuture handle(InstanceIngressEvent instanceIngressEvent) { // to be implemented in MODINV-1008 return CompletableFuture.failedFuture(new UnsupportedOperationException()); } diff --git a/src/test/java/org/folio/inventory/TestUtil.java b/src/test/java/org/folio/inventory/TestUtil.java index 50e576f4e..bb5a68536 100644 --- a/src/test/java/org/folio/inventory/TestUtil.java +++ b/src/test/java/org/folio/inventory/TestUtil.java @@ -1,5 +1,7 @@ package org.folio.inventory; +import io.vertx.core.buffer.Buffer; +import io.vertx.ext.web.client.impl.HttpResponseImpl; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -12,4 +14,9 @@ public final class TestUtil { public static String readFileFromPath(String path) throws IOException { return Files.readString(Path.of(path)); } + + public static HttpResponseImpl buildHttpResponseWithBuffer(Buffer buffer, int httpStatus) { + return new HttpResponseImpl<>(null, httpStatus, "", + null, null, null, buffer, null); + } } diff --git a/src/test/java/org/folio/inventory/consortium/handlers/MarcInstanceSharingHandlerImplTest.java b/src/test/java/org/folio/inventory/consortium/handlers/MarcInstanceSharingHandlerImplTest.java index a6ecf5024..6f2bb7f70 100644 --- a/src/test/java/org/folio/inventory/consortium/handlers/MarcInstanceSharingHandlerImplTest.java +++ b/src/test/java/org/folio/inventory/consortium/handlers/MarcInstanceSharingHandlerImplTest.java @@ -52,8 +52,8 @@ import static org.folio.HttpStatus.HTTP_INTERNAL_SERVER_ERROR; import static org.folio.HttpStatus.HTTP_NO_CONTENT; +import static org.folio.inventory.TestUtil.buildHttpResponseWithBuffer; import static org.folio.inventory.consortium.handlers.MarcInstanceSharingHandlerImpl.SRS_RECORD_ID_TYPE; -import static org.folio.inventory.consortium.util.RestDataImportHelperTest.buildHttpResponseWithBuffer; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyString; @@ -138,7 +138,7 @@ public void setUp() throws IOException { } private final HttpResponse sourceStorageRecordsResponseBuffer = - buildHttpResponseWithBuffer(HttpStatus.HTTP_OK, BufferImpl.buffer(recordJson)); + buildHttpResponseWithBuffer(BufferImpl.buffer(recordJson), HttpStatus.HTTP_OK.toInt()); @Test public void publishInstanceTest(TestContext testContext) { @@ -192,8 +192,8 @@ public void publishInstanceAndRelinkAuthoritiesTest(TestContext testContext) thr String instanceId = "eb89b292-d2b7-4c36-9bfc-f816d6f96418"; String targetInstanceHrid = "consin0000000000101"; - Record record = buildHttpResponseWithBuffer(HttpStatus.HTTP_OK, - BufferImpl.buffer(recordJsonWithLinkedAuthorities)).bodyAsJson(Record.class); + Record record = buildHttpResponseWithBuffer(BufferImpl.buffer(recordJsonWithLinkedAuthorities), HttpStatus.HTTP_OK.toInt()) + .bodyAsJson(Record.class); Authority authority1 = new Authority().withId(AUTHORITY_ID_1).withSource(Authority.Source.MARC); Authority authority2 = new Authority().withId(AUTHORITY_ID_2).withSource(Authority.Source.CONSORTIUM_MARC); @@ -267,7 +267,7 @@ public void shouldReturnFailedFutureIfErrorDuringRetrievingAuthoritiesDuringUnli String instanceId = "eb89b292-d2b7-4c36-9bfc-f816d6f96418"; - Record record = buildHttpResponseWithBuffer(HttpStatus.HTTP_OK, BufferImpl.buffer(recordJsonWithLinkedAuthorities)).bodyAsJson(Record.class); + Record record = buildHttpResponseWithBuffer(BufferImpl.buffer(recordJsonWithLinkedAuthorities), HttpStatus.HTTP_OK.toInt()).bodyAsJson(Record.class); //given marcHandler = spy(new MarcInstanceSharingHandlerImpl(instanceOperationsHelper, storage, vertx, httpClient)); @@ -311,7 +311,7 @@ public void shouldNotPutLinkInstanceAuthoritiesIfInstanceNotLinkedToSharedAuthor String instanceId = "eb89b292-d2b7-4c36-9bfc-f816d6f96418"; String targetInstanceHrid = "consin0000000000101"; - Record record = buildHttpResponseWithBuffer(HttpStatus.HTTP_OK, BufferImpl.buffer(recordJsonWithLinkedAuthorities)).bodyAsJson(Record.class); + Record record = buildHttpResponseWithBuffer(BufferImpl.buffer(recordJsonWithLinkedAuthorities), HttpStatus.HTTP_OK.toInt()).bodyAsJson(Record.class); Authority authority1 = new Authority().withId(AUTHORITY_ID_1).withSource(Authority.Source.MARC); Authority authority2 = new Authority().withId(AUTHORITY_ID_2).withSource(Authority.Source.MARC); @@ -380,7 +380,7 @@ public void publishInstanceAndNotModifyMarcRecordIfLocalAuthoritiesNotLinkedToMa String instanceId = "eb89b292-d2b7-4c36-9bfc-f816d6f96418"; String targetInstanceHrid = "consin0000000000101"; - Record record = buildHttpResponseWithBuffer(HttpStatus.HTTP_OK, BufferImpl.buffer(recordJsonWithLinkedAuthorities)).bodyAsJson(Record.class); + Record record = buildHttpResponseWithBuffer(BufferImpl.buffer(recordJsonWithLinkedAuthorities), HttpStatus.HTTP_OK.toInt()).bodyAsJson(Record.class); Authority authority1 = new Authority().withId(AUTHORITY_ID_1).withSource(Authority.Source.CONSORTIUM_MARC); Authority authority2 = new Authority().withId(AUTHORITY_ID_2).withSource(Authority.Source.CONSORTIUM_MARC); @@ -523,7 +523,7 @@ public void deleteSourceRecordByInstanceIdFailedTest() { MarcInstanceSharingHandlerImpl handler = new MarcInstanceSharingHandlerImpl(instanceOperationsHelper, null, vertx, httpClient); handler.deleteSourceRecordByRecordId(recordId, instanceId, tenant, sourceStorageClient) .onComplete(result -> assertTrue(result.failed())); - + verify(sourceStorageClient, times(1)).deleteSourceStorageRecordsById(recordId, SRS_RECORD_ID_TYPE); } diff --git a/src/test/java/org/folio/inventory/consortium/util/RestDataImportHelperTest.java b/src/test/java/org/folio/inventory/consortium/util/RestDataImportHelperTest.java index 680cfc533..e31ed819e 100644 --- a/src/test/java/org/folio/inventory/consortium/util/RestDataImportHelperTest.java +++ b/src/test/java/org/folio/inventory/consortium/util/RestDataImportHelperTest.java @@ -20,11 +20,11 @@ import org.junit.Test; import org.junit.runner.RunWith; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.UUID; +import static org.folio.inventory.TestUtil.buildHttpResponseWithBuffer; import static org.folio.inventory.consortium.util.RestDataImportHelper.FIELD_JOB_EXECUTIONS; import static org.folio.inventory.consortium.util.RestDataImportHelper.STATUS_COMMITTED; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -63,7 +63,7 @@ public void initJobExecutionTest() { .put(FIELD_JOB_EXECUTIONS, new JsonArray().add(new JsonObject().put("id", expectedJobExecutionId))); HttpResponseImpl jobExecutionResponse = - buildHttpResponseWithBuffer(HttpStatus.HTTP_CREATED, BufferImpl.buffer(responseBody.encode())); + buildHttpResponseWithBuffer(BufferImpl.buffer(responseBody.encode()), HttpStatus.HTTP_CREATED.toInt()); Future> futureResponse = Future.succeededFuture(jobExecutionResponse); doAnswer(invocation -> { @@ -88,7 +88,7 @@ public void initJobExecutionFailedInternalServerErrorTest() { String expectedJobExecutionId = UUID.randomUUID().toString(); Map kafkaHeaders = new HashMap<>(); HttpResponseImpl jobExecutionResponse = - buildHttpResponseWithBuffer(HttpStatus.HTTP_INTERNAL_SERVER_ERROR, null); + buildHttpResponseWithBuffer(null, HttpStatus.HTTP_INTERNAL_SERVER_ERROR.toInt()); Future> futureResponse = Future.succeededFuture(jobExecutionResponse); doAnswer(invocation -> { @@ -115,7 +115,7 @@ public void initJobExecutionFailedWithoutJobExecutionsArrayTest() { JsonObject responseBody = new JsonObject().put("jobExecutions", new JsonArray().add("")); HttpResponseImpl jobExecutionResponse = - buildHttpResponseWithBuffer(HttpStatus.HTTP_CREATED, BufferImpl.buffer(responseBody.encode())); + buildHttpResponseWithBuffer(BufferImpl.buffer(responseBody.encode()), HttpStatus.HTTP_CREATED.toInt()); Future> futureResponse = Future.succeededFuture(jobExecutionResponse); doAnswer(invocation -> { @@ -140,7 +140,7 @@ public void initJobExecutionFailedWithJobExecutionsEmptyArrayTest() { String expectedJobExecutionId = UUID.randomUUID().toString(); Map kafkaHeaders = new HashMap<>(); HttpResponseImpl jobExecutionResponse = - buildHttpResponseWithBuffer(HttpStatus.HTTP_CREATED, BufferImpl.buffer("{\"jobExecutions\":[]}")); + buildHttpResponseWithBuffer(BufferImpl.buffer("{\"jobExecutions\":[]}"), HttpStatus.HTTP_CREATED.toInt()); Future> futureResponse = Future.succeededFuture(jobExecutionResponse); doAnswer(invocation -> { @@ -166,7 +166,7 @@ public void setDefaultJobProfileToJobExecutionTest() { String expectedJobExecutionId = UUID.randomUUID().toString(); HttpResponseImpl jobExecutionResponse = - buildHttpResponseWithBuffer(HttpStatus.HTTP_OK, null); + buildHttpResponseWithBuffer(null, HttpStatus.HTTP_OK.toInt()); Future> futureResponse = Future.succeededFuture(jobExecutionResponse); doAnswer(invocation -> { @@ -191,7 +191,7 @@ public void setDefaultJobProfileToJobExecutionFailedInternalServerErrorTest() { String expectedJobExecutionId = UUID.randomUUID().toString(); HttpResponseImpl jobExecutionResponse = - buildHttpResponseWithBuffer(HttpStatus.HTTP_INTERNAL_SERVER_ERROR, null); + buildHttpResponseWithBuffer(null, HttpStatus.HTTP_INTERNAL_SERVER_ERROR.toInt()); Future> futureResponse = Future.succeededFuture(jobExecutionResponse); doAnswer(invocation -> { @@ -217,7 +217,7 @@ public void postChunkTest() { String expectedJobExecutionId = UUID.randomUUID().toString(); HttpResponseImpl jobExecutionResponse = - buildHttpResponseWithBuffer(HttpStatus.HTTP_NO_CONTENT, null); + buildHttpResponseWithBuffer(null, HttpStatus.HTTP_NO_CONTENT.toInt()); Future> futureResponse = Future.succeededFuture(jobExecutionResponse); RawRecordsDto rawRecordsDto = new RawRecordsDto() @@ -252,7 +252,7 @@ public void getJobExecutionStatusByJobExecutionId() { String expectedJobExecutionId = UUID.randomUUID().toString(); HttpResponseImpl jobExecutionResponse = - buildHttpResponseWithBuffer(HttpStatus.HTTP_OK, BufferImpl.buffer("{\"status\":\"" + STATUS_COMMITTED + "\"}")); + buildHttpResponseWithBuffer(BufferImpl.buffer("{\"status\":\"" + STATUS_COMMITTED + "\"}"), HttpStatus.HTTP_OK.toInt()); Future> futureResponse = Future.succeededFuture(jobExecutionResponse); doAnswer(invocation -> { @@ -277,7 +277,7 @@ public void getJobExecutionStatusByJobExecutionIdFailedWithEmptyResponseBodyTest String expectedJobExecutionId = UUID.randomUUID().toString(); HttpResponseImpl jobExecutionResponse = - buildHttpResponseWithBuffer(HttpStatus.HTTP_OK, null); + buildHttpResponseWithBuffer(null, HttpStatus.HTTP_OK.toInt()); Future> futureResponse = Future.succeededFuture(jobExecutionResponse); doAnswer(invocation -> { @@ -303,7 +303,7 @@ public void getJobExecutionStatusByJobExecutionIdFailedInternalServerErrorTest() String expectedJobExecutionId = UUID.randomUUID().toString(); HttpResponseImpl jobExecutionResponse = - buildHttpResponseWithBuffer(HttpStatus.HTTP_INTERNAL_SERVER_ERROR, null); + buildHttpResponseWithBuffer(null, HttpStatus.HTTP_INTERNAL_SERVER_ERROR.toInt()); Future> futureResponse = Future.succeededFuture(jobExecutionResponse); doAnswer(invocation -> { @@ -322,15 +322,4 @@ public void getJobExecutionStatusByJobExecutionIdFailedInternalServerErrorTest() }); } - public static HttpResponseImpl buildHttpResponseWithBuffer(HttpStatus httpStatus, Buffer buffer) { - return new HttpResponseImpl( - null, - httpStatus.toInt(), - "Ok", - null, - null, - null, - buffer, - new ArrayList()); - } } diff --git a/src/test/java/org/folio/inventory/dataimport/handlers/actions/CreateInstanceEventHandlerTest.java b/src/test/java/org/folio/inventory/dataimport/handlers/actions/CreateInstanceEventHandlerTest.java index 2971eefd8..4ecf2d953 100644 --- a/src/test/java/org/folio/inventory/dataimport/handlers/actions/CreateInstanceEventHandlerTest.java +++ b/src/test/java/org/folio/inventory/dataimport/handlers/actions/CreateInstanceEventHandlerTest.java @@ -78,11 +78,13 @@ import static com.github.tomakehurst.wiremock.client.WireMock.get; import static java.util.concurrent.CompletableFuture.completedStage; +import static org.apache.http.HttpStatus.SC_CREATED; import static org.folio.ActionProfile.FolioRecord.INSTANCE; import static org.folio.ActionProfile.FolioRecord.MARC_BIBLIOGRAPHIC; import static org.folio.DataImportEventTypes.DI_INVENTORY_INSTANCE_CREATED; import static org.folio.DataImportEventTypes.DI_INVENTORY_INSTANCE_CREATED_READY_FOR_POST_PROCESSING; import static org.folio.DataImportEventTypes.DI_INCOMING_MARC_BIB_RECORD_PARSED; +import static org.folio.inventory.TestUtil.buildHttpResponseWithBuffer; import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.TAG_005; import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.dateTime005Formatter; import static org.folio.inventory.dataimport.util.DataImportConstants.UNIQUE_ID_ERROR_MESSAGE; @@ -287,7 +289,7 @@ public void shouldProcessEvent(String content, String acceptInstanceId) throws I "\"id\":\"990fad8b-64ec-4de4-978c-9f8bbed4c6d3\"," + "\"content\":\"{\\\"leader\\\":\\\"00574nam 22001211a 4500\\\",\\\"fields\\\":[{\\\"035\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"(in001)ybp7406411\\\"}],\\\"ind1\\\":\\\" \\\",\\\"ind2\\\":\\\" \\\"}},{\\\"245\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"titleValue\\\"}],\\\"ind1\\\":\\\"1\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"336\\\":{\\\"subfields\\\":[{\\\"b\\\":\\\"b6698d38-149f-11ec-82a8-0242ac130003\\\"}],\\\"ind1\\\":\\\"1\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"780\\\":{\\\"subfields\\\":[{\\\"t\\\":\\\"Houston oil directory\\\"}],\\\"ind1\\\":\\\"0\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"785\\\":{\\\"subfields\\\":[{\\\"t\\\":\\\"SAIS review of international affairs\\\"},{\\\"x\\\":\\\"1945-4724\\\"}],\\\"ind1\\\":\\\"0\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"500\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"Adaptation of Xi xiang ji by Wang Shifu.\\\"}],\\\"ind1\\\":\\\" \\\",\\\"ind2\\\":\\\" \\\"}},{\\\"520\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"Ben shu miao shu le cui ying ying he zhang sheng wei zheng qu hun yin zi you li jin qu zhe jian xin zhi hou, zhong cheng juan shu de ai qing gu shi. jie lu le bao ban hun yin he feng jian li jiao de zui e.\\\"}],\\\"ind1\\\":\\\" \\\",\\\"ind2\\\":\\\" \\\"}},{\\\"999\\\":{\\\"subfields\\\":[{\\\"i\\\":\\\"4d4545df-b5ba-4031-a031-70b1c1b2fc5d\\\"}],\\\"ind1\\\":\\\"f\\\",\\\"ind2\\\":\\\"f\\\"}}]}\"" + "}}"); - HttpResponse resp = buildHttpResponseWithBuffer(buffer); + HttpResponse resp = buildHttpResponseWithBuffer(buffer, SC_CREATED); when(sourceStorageClient.postSourceStorageRecords(any())).thenReturn(Future.succeededFuture(resp)); DataImportEventPayload dataImportEventPayload = new DataImportEventPayload() @@ -340,7 +342,7 @@ public void shouldProcessEventAndUpdate005Field() throws InterruptedException, E MappingManager.registerReaderFactory(fakeReaderFactory); MappingManager.registerWriterFactory(new InstanceWriterFactory()); - HttpResponse resp = buildHttpResponseWithBuffer(BufferImpl.buffer("{}")); + HttpResponse resp = buildHttpResponseWithBuffer(BufferImpl.buffer("{}"), SC_CREATED); ArgumentCaptor recordCaptor = ArgumentCaptor.forClass(Record.class); when(sourceStorageClient.postSourceStorageRecords(any())).thenReturn(Future.succeededFuture(resp)); @@ -461,7 +463,7 @@ public void shouldProcessConsortiumEvent() throws InterruptedException, Executio context.put(MARC_BIBLIOGRAPHIC.value(), Json.encode(record)); Buffer buffer = BufferImpl.buffer("{\"id\": \"567859ad-505a-400d-a699-0028a1fdbf84\",\"parsedRecord\": {\"content\": \"{\\\"leader\\\":\\\"00567nam 22001211a 4500\\\",\\\"fields\\\":[{\\\"035\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"ybp7406411\\\"}],\\\"ind1\\\":\\\" \\\",\\\"ind2\\\":\\\" \\\"}},{\\\"245\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"titleValue\\\"}],\\\"ind1\\\":\\\"1\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"336\\\":{\\\"subfields\\\":[{\\\"b\\\":\\\"b6698d38-149f-11ec-82a8-0242ac130003\\\"}],\\\"ind1\\\":\\\"1\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"780\\\":{\\\"subfields\\\":[{\\\"t\\\":\\\"Houston oil directory\\\"}],\\\"ind1\\\":\\\"0\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"785\\\":{\\\"subfields\\\":[{\\\"t\\\":\\\"SAIS review of international affairs\\\"},{\\\"x\\\":\\\"1945-4724\\\"}],\\\"ind1\\\":\\\"0\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"500\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"Adaptation of Xi xiang ji by Wang Shifu.\\\"}],\\\"ind1\\\":\\\" \\\",\\\"ind2\\\":\\\" \\\"}},{\\\"520\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"Ben shu miao shu le cui ying ying he zhang sheng wei zheng qu hun yin zi you li jin qu zhe jian xin zhi hou, zhong cheng juan shu de ai qing gu shi. jie lu le bao ban hun yin he feng jian li jiao de zui e.\\\"}],\\\"ind1\\\":\\\" \\\",\\\"ind2\\\":\\\" \\\"}},{\\\"999\\\":{\\\"subfields\\\":[{\\\"i\\\":\\\"957985c6-97e3-4038-b0e7-343ecd0b8120\\\"}],\\\"ind1\\\":\\\"f\\\",\\\"ind2\\\":\\\"f\\\"}}]}\"},\"deleted\": false,\"order\": 0,\"externalIdsHolder\": {\"instanceId\": \"b5e25bc3-a5a5-474a-8333-4a728d2f3485\",\"instanceHrid\": \"in00000000028\"},\"state\": \"ACTUAL\"}"); - HttpResponse resp = buildHttpResponseWithBuffer(buffer); + HttpResponse resp = buildHttpResponseWithBuffer(buffer, SC_CREATED); when(sourceStorageClient.postSourceStorageRecords(any())).thenReturn(Future.succeededFuture(resp)); DataImportEventPayload dataImportEventPayload = new DataImportEventPayload() @@ -677,7 +679,7 @@ public void shouldNotProcessEventIfNatureContentFieldIsNotUUID() throws Interrup "\"id\":\"990fad8b-64ec-4de4-978c-9f8bbed4c6d3\"," + "\"content\":\"{\\\"leader\\\":\\\"00574nam 22001211a 4500\\\",\\\"fields\\\":[{\\\"035\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"(in001)ybp7406411\\\"}],\\\"ind1\\\":\\\" \\\",\\\"ind2\\\":\\\" \\\"}},{\\\"245\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"titleValue\\\"}],\\\"ind1\\\":\\\"1\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"336\\\":{\\\"subfields\\\":[{\\\"b\\\":\\\"b6698d38-149f-11ec-82a8-0242ac130003\\\"}],\\\"ind1\\\":\\\"1\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"780\\\":{\\\"subfields\\\":[{\\\"t\\\":\\\"Houston oil directory\\\"}],\\\"ind1\\\":\\\"0\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"785\\\":{\\\"subfields\\\":[{\\\"t\\\":\\\"SAIS review of international affairs\\\"},{\\\"x\\\":\\\"1945-4724\\\"}],\\\"ind1\\\":\\\"0\\\",\\\"ind2\\\":\\\"0\\\"}},{\\\"500\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"Adaptation of Xi xiang ji by Wang Shifu.\\\"}],\\\"ind1\\\":\\\" \\\",\\\"ind2\\\":\\\" \\\"}},{\\\"520\\\":{\\\"subfields\\\":[{\\\"a\\\":\\\"Ben shu miao shu le cui ying ying he zhang sheng wei zheng qu hun yin zi you li jin qu zhe jian xin zhi hou, zhong cheng juan shu de ai qing gu shi. jie lu le bao ban hun yin he feng jian li jiao de zui e.\\\"}],\\\"ind1\\\":\\\" \\\",\\\"ind2\\\":\\\" \\\"}},{\\\"999\\\":{\\\"subfields\\\":[{\\\"i\\\":\\\"4d4545df-b5ba-4031-a031-70b1c1b2fc5d\\\"}],\\\"ind1\\\":\\\"f\\\",\\\"ind2\\\":\\\"f\\\"}}]}\"" + "}}"); - HttpResponse resp = buildHttpResponseWithBuffer(buffer); + HttpResponse resp = buildHttpResponseWithBuffer(buffer, SC_CREATED); when(sourceStorageClient.postSourceStorageRecords(any())).thenReturn(Future.succeededFuture(resp)); DataImportEventPayload dataImportEventPayload = new DataImportEventPayload() @@ -698,7 +700,7 @@ public void shouldNotProcessEventIfNatureContentFieldIsNotUUID() throws Interrup public void shouldNotProcessEventIfRecordContains999field() throws InterruptedException, ExecutionException, TimeoutException { var recordId = UUID.randomUUID().toString(); - HttpResponse resp = buildHttpResponseWithBuffer(BufferImpl.buffer("{}")); + HttpResponse resp = buildHttpResponseWithBuffer(BufferImpl.buffer("{}"), SC_CREATED); when(sourceStorageClient.postSourceStorageRecords(any())).thenReturn(Future.succeededFuture(resp)); var context = new HashMap(); @@ -920,12 +922,7 @@ public void shouldNotProcessEventEvenIfInventoryStorageErrorExists() throws Inte future.get(5, TimeUnit.SECONDS); } - private static HttpResponseImpl buildHttpResponseWithBuffer(Buffer buffer) { - return new HttpResponseImpl<>(null, HttpStatus.SC_CREATED, "", - null, null, null, buffer, null); - } - private Response createdResponse() { - return new Response(HttpStatus.SC_CREATED, null, null, null); + return new Response(SC_CREATED, null, null, null); } } diff --git a/src/test/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandlerTest.java b/src/test/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandlerTest.java index ac2bc6e2b..568067c89 100644 --- a/src/test/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandlerTest.java +++ b/src/test/java/org/folio/inventory/dataimport/handlers/actions/ReplaceInstanceEventHandlerTest.java @@ -89,6 +89,7 @@ import static org.folio.DataImportEventTypes.DI_INVENTORY_INSTANCE_MATCHED; import static org.folio.DataImportEventTypes.DI_INVENTORY_INSTANCE_UPDATED; import static org.folio.DataImportEventTypes.DI_INVENTORY_INSTANCE_UPDATED_READY_FOR_POST_PROCESSING; +import static org.folio.inventory.TestUtil.buildHttpResponseWithBuffer; import static org.folio.inventory.dataimport.handlers.actions.ReplaceInstanceEventHandler.ACTION_HAS_NO_MAPPING_MSG; import static org.folio.inventory.dataimport.handlers.actions.ReplaceInstanceEventHandler.MARC_BIB_RECORD_CREATED; import static org.folio.inventory.domain.instances.InstanceSource.CONSORTIUM_MARC; @@ -1309,8 +1310,4 @@ private void mockInstance(String sourceType) { }).when(instanceRecordCollection).findById(anyString(), any(Consumer.class), any(Consumer.class)); } - private static HttpResponseImpl buildHttpResponseWithBuffer(Buffer buffer, int httpStatus) { - return new HttpResponseImpl<>(null, httpStatus, "", - null, null, null, buffer, null); - } } diff --git a/src/test/java/org/folio/inventory/instanceingress/handler/CreateInstanceIngressEventHandlerUnitTest.java b/src/test/java/org/folio/inventory/instanceingress/handler/CreateInstanceIngressEventHandlerUnitTest.java new file mode 100644 index 000000000..250569f92 --- /dev/null +++ b/src/test/java/org/folio/inventory/instanceingress/handler/CreateInstanceIngressEventHandlerUnitTest.java @@ -0,0 +1,375 @@ +package org.folio.inventory.instanceingress.handler; + +import static io.vertx.core.Future.failedFuture; +import static io.vertx.core.Future.succeededFuture; +import static io.vertx.core.buffer.impl.BufferImpl.buffer; +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static org.folio.inventory.TestUtil.buildHttpResponseWithBuffer; +import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.SUBFIELD_B; +import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.SUBFIELD_I; +import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.TAG_999; +import static org.folio.inventory.dataimport.util.MappingConstants.MARC_BIB_RECORD_TYPE; +import static org.folio.rest.jaxrs.model.InstanceIngressPayload.SourceType.BIBFRAME; +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import io.vertx.core.http.HttpClient; +import io.vertx.core.json.Json; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.unit.junit.VertxUnitRunner; +import java.io.IOException; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.function.Consumer; +import org.apache.http.HttpStatus; +import org.folio.MappingMetadataDto; +import org.folio.inventory.TestUtil; +import org.folio.inventory.common.Context; +import org.folio.inventory.common.domain.Failure; +import org.folio.inventory.common.domain.Success; +import org.folio.inventory.dataimport.cache.MappingMetadataCache; +import org.folio.inventory.dataimport.handlers.actions.PrecedingSucceedingTitlesHelper; +import org.folio.inventory.dataimport.util.AdditionalFieldsUtil; +import org.folio.inventory.domain.instances.Instance; +import org.folio.inventory.domain.instances.InstanceCollection; +import org.folio.inventory.instanceingress.InstanceIngressEventConsumer; +import org.folio.inventory.services.IdStorageService; +import org.folio.inventory.storage.Storage; +import org.folio.processing.mapping.defaultmapper.processor.parameters.MappingParameters; +import org.folio.rest.client.SourceStorageRecordsClient; +import org.folio.rest.client.SourceStorageSnapshotsClient; +import org.folio.rest.jaxrs.model.InstanceIngressEvent; +import org.folio.rest.jaxrs.model.InstanceIngressPayload; +import org.folio.rest.jaxrs.model.Record; +import org.folio.rest.jaxrs.model.Snapshot; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@RunWith(VertxUnitRunner.class) +public class CreateInstanceIngressEventHandlerUnitTest { + private static final String MAPPING_RULES_PATH = "src/test/resources/handlers/bib-rules.json"; + private static final String BIB_RECORD_PATH = "src/test/resources/handlers/bib-record.json"; + + @Rule + public MockitoRule initRule = MockitoJUnit.rule(); + @Mock + private SourceStorageRecordsClient sourceStorageClient; + @Mock + private SourceStorageSnapshotsClient sourceStorageSnapshotsClient; + @Mock + private PrecedingSucceedingTitlesHelper precedingSucceedingTitlesHelper; + @Mock + private MappingMetadataCache mappingMetadataCache; + @Mock + private IdStorageService idStorageService; + @Mock + private HttpClient httpClient; + @Mock + private Context context; + @Mock + private Storage storage; + @Mock + private InstanceCollection instanceCollection; + private CreateInstanceIngressEventHandler handler; + + @Before + public void setUp() throws Exception { + doReturn("tenant").when(context).getTenantId(); + doReturn("okapiUrl").when(context).getOkapiLocation(); + doReturn("token").when(context).getToken(); + doReturn(instanceCollection).when(storage).getInstanceCollection(context); + handler = spy(new CreateInstanceIngressEventHandler(precedingSucceedingTitlesHelper, + mappingMetadataCache, idStorageService, httpClient, context, storage)); + } + + @Test + public void shouldReturnFailedFuture_ifEventDoesNotContainData() { + // given + var event = new InstanceIngressEvent() + .withId(UUID.randomUUID().toString()); + var expectedMessage = format("InstanceIngressEvent message does not contain " + + "required data to create Instance for eventId: '%s'", event.getId()); + + // when + var future = handler.handle(event); + + // then + var exception = Assert.assertThrows(ExecutionException.class, future::get); + assertEquals(expectedMessage, exception.getCause().getMessage()); + } + + @Test + public void shouldReturnFailedFuture_ifIdStorageServiceStoreFails() { + // given + var event = new InstanceIngressEvent() + .withId(UUID.randomUUID().toString()) + .withEventPayload(new InstanceIngressPayload() + .withSourceRecordObject("{}") + .withSourceType(BIBFRAME) + ); + var expectedMessage = "idStorageService failure"; + doReturn(failedFuture(expectedMessage)).when(idStorageService).store(anyString(), anyString(), anyString()); + + // when + var future = handler.handle(event); + + // then + var exception = Assert.assertThrows(ExecutionException.class, future::get); + assertThat(exception.getCause().getMessage()).startsWith(expectedMessage); + } + + @Test + public void shouldReturnFailedFuture_ifMappingMetadataWasNotFound() { + // given + var event = new InstanceIngressEvent() + .withId(UUID.randomUUID().toString()) + .withEventPayload(new InstanceIngressPayload() + .withSourceRecordObject("{}") + .withSourceType(BIBFRAME) + ); + doReturn(succeededFuture(null)).when(idStorageService).store(anyString(), anyString(), anyString()); + doReturn(succeededFuture(Optional.empty())).when(mappingMetadataCache) + .getByRecordType(InstanceIngressEventConsumer.class.getSimpleName(), context, MARC_BIB_RECORD_TYPE); + var expectedMessage = "MappingMetadata was not found for marc-bib record type"; + + // when + var future = handler.handle(event); + + // then + var exception = Assert.assertThrows(ExecutionException.class, future::get); + assertThat(exception.getCause().getMessage()).startsWith(expectedMessage); + } + + @Test + public void shouldReturnFailedFuture_ifSourceStorageSnapshotsClientReturnsError() throws IOException { + // given + var event = new InstanceIngressEvent() + .withId(UUID.randomUUID().toString()) + .withEventPayload(new InstanceIngressPayload() + .withSourceRecordObject("{}") + .withSourceType(BIBFRAME) + ); + doReturn(succeededFuture(null)).when(idStorageService).store(anyString(), anyString(), anyString()); + var mappingRules = new JsonObject(TestUtil.readFileFromPath(MAPPING_RULES_PATH)); + doReturn(succeededFuture(Optional.of(new MappingMetadataDto() + .withMappingRules(mappingRules.encode()) + .withMappingParams(Json.encode(new MappingParameters()))))) + .when(mappingMetadataCache).getByRecordType(InstanceIngressEventConsumer.class.getSimpleName(), context, MARC_BIB_RECORD_TYPE); + doReturn(sourceStorageSnapshotsClient).when(handler).getSourceStorageSnapshotsClient(any(), any(), any()); + var snapshotHttpResponse = buildHttpResponseWithBuffer(null, HttpStatus.SC_BAD_REQUEST); + doReturn(succeededFuture(snapshotHttpResponse)).when(sourceStorageSnapshotsClient).postSourceStorageSnapshots(any()); + + var expectedMessage = "Failed to create snapshot in SRS, snapshot id: "; + + // when + var future = handler.handle(event); + + // then + var exception = Assert.assertThrows(ExecutionException.class, future::get); + assertThat(exception.getCause().getMessage()).startsWith(expectedMessage); + } + + @Test + public void shouldReturnFailedFuture_ifInstanceValidationFails() throws IOException { + // given + var event = new InstanceIngressEvent() + .withId(UUID.randomUUID().toString()) + .withEventPayload(new InstanceIngressPayload() + .withSourceRecordObject("{}") + .withSourceType(BIBFRAME) + ); + doReturn(succeededFuture(null)).when(idStorageService).store(anyString(), anyString(), anyString()); + var mappingRules = new JsonObject(TestUtil.readFileFromPath(MAPPING_RULES_PATH)); + doReturn(succeededFuture(Optional.of(new MappingMetadataDto() + .withMappingRules(mappingRules.encode()) + .withMappingParams(Json.encode(new MappingParameters()))))) + .when(mappingMetadataCache).getByRecordType(InstanceIngressEventConsumer.class.getSimpleName(), context, MARC_BIB_RECORD_TYPE); + + doReturn(sourceStorageSnapshotsClient).when(handler).getSourceStorageSnapshotsClient(any(), any(), any()); + var snapshotHttpResponse = buildHttpResponseWithBuffer(buffer(Json.encode(new Snapshot())), HttpStatus.SC_CREATED); + doReturn(succeededFuture(snapshotHttpResponse)).when(sourceStorageSnapshotsClient).postSourceStorageSnapshots(any()); + + var expectedMessage = "Mapped Instance is invalid: [Field 'title' is a required field and can not be null, " + + "Field 'instanceTypeId' is a required field and can not be null], from InstanceIngressEvent with id '" + event.getId() + "'"; + + // when + var future = handler.handle(event); + + // then + var exception = Assert.assertThrows(ExecutionException.class, future::get); + assertThat(exception.getCause().getMessage()).isEqualTo(expectedMessage); + } + + @Test + public void shouldReturnFailedFuture_ifInstanceSavingFailed() throws IOException { + // given + var event = new InstanceIngressEvent() + .withId(UUID.randomUUID().toString()) + .withEventPayload(new InstanceIngressPayload() + .withSourceRecordObject(TestUtil.readFileFromPath(BIB_RECORD_PATH)) + .withSourceType(BIBFRAME) + ); + doReturn(succeededFuture(null)).when(idStorageService).store(anyString(), anyString(), anyString()); + var mappingRules = new JsonObject(TestUtil.readFileFromPath(MAPPING_RULES_PATH)); + doReturn(succeededFuture(Optional.of(new MappingMetadataDto() + .withMappingRules(mappingRules.encode()) + .withMappingParams(Json.encode(new MappingParameters()))))) + .when(mappingMetadataCache).getByRecordType(InstanceIngressEventConsumer.class.getSimpleName(), context, MARC_BIB_RECORD_TYPE); + doReturn(sourceStorageSnapshotsClient).when(handler).getSourceStorageSnapshotsClient(any(), any(), any()); + var snapshotHttpResponse = buildHttpResponseWithBuffer(buffer(Json.encode(new Snapshot())), HttpStatus.SC_CREATED); + doReturn(succeededFuture(snapshotHttpResponse)).when(sourceStorageSnapshotsClient).postSourceStorageSnapshots(any()); + + var expectedMessage = "Some failure"; + doAnswer(i -> { + Consumer failureHandler = i.getArgument(2); + failureHandler.accept(new Failure(expectedMessage, 400)); + return null; + }).when(instanceCollection).add(any(), any(), any()); + + // when + var future = handler.handle(event); + + // then + var exception = Assert.assertThrows(ExecutionException.class, future::get); + assertThat(exception.getCause().getMessage()).isEqualTo(expectedMessage); + } + + @Test + public void shouldReturnFailedFuture_ifCreatePrecedingSucceedingTitlesFailed() throws IOException { + // given + var event = new InstanceIngressEvent() + .withId(UUID.randomUUID().toString()) + .withEventPayload(new InstanceIngressPayload() + .withSourceRecordObject(TestUtil.readFileFromPath(BIB_RECORD_PATH)) + .withSourceType(BIBFRAME) + ); + doReturn(succeededFuture(null)).when(idStorageService).store(anyString(), anyString(), anyString()); + var mappingRules = new JsonObject(TestUtil.readFileFromPath(MAPPING_RULES_PATH)); + doReturn(succeededFuture(Optional.of(new MappingMetadataDto() + .withMappingRules(mappingRules.encode()) + .withMappingParams(Json.encode(new MappingParameters()))))) + .when(mappingMetadataCache).getByRecordType(InstanceIngressEventConsumer.class.getSimpleName(), context, MARC_BIB_RECORD_TYPE); + doReturn(sourceStorageSnapshotsClient).when(handler).getSourceStorageSnapshotsClient(any(), any(), any()); + var snapshotHttpResponse = buildHttpResponseWithBuffer(buffer(Json.encode(new Snapshot())), HttpStatus.SC_CREATED); + doReturn(succeededFuture(snapshotHttpResponse)).when(sourceStorageSnapshotsClient).postSourceStorageSnapshots(any()); + doAnswer(i -> { + Consumer> sucessHandler = i.getArgument(1); + sucessHandler.accept(new Success<>(i.getArgument(0))); + return null; + }).when(instanceCollection).add(any(), any(), any()); + var expectedMessage = "Some failure"; + doReturn(failedFuture(expectedMessage)).when(precedingSucceedingTitlesHelper).createPrecedingSucceedingTitles(any(), any()); + + // when + var future = handler.handle(event); + + // then + var exception = Assert.assertThrows(ExecutionException.class, future::get); + assertThat(exception.getCause().getMessage()).isEqualTo(expectedMessage); + } + + @Test + public void shouldReturnFailedFuture_ifItsFailedToCreateMarcRecordInSrs() throws IOException { + // given + var event = new InstanceIngressEvent() + .withId(UUID.randomUUID().toString()) + .withEventPayload(new InstanceIngressPayload() + .withSourceRecordObject(TestUtil.readFileFromPath(BIB_RECORD_PATH)) + .withSourceType(BIBFRAME) + ); + doReturn(succeededFuture(null)).when(idStorageService).store(anyString(), anyString(), anyString()); + var mappingRules = new JsonObject(TestUtil.readFileFromPath(MAPPING_RULES_PATH)); + doReturn(succeededFuture(Optional.of(new MappingMetadataDto() + .withMappingRules(mappingRules.encode()) + .withMappingParams(Json.encode(new MappingParameters()))))) + .when(mappingMetadataCache).getByRecordType(InstanceIngressEventConsumer.class.getSimpleName(), context, MARC_BIB_RECORD_TYPE); + doReturn(sourceStorageSnapshotsClient).when(handler).getSourceStorageSnapshotsClient(any(), any(), any()); + var snapshotHttpResponse = buildHttpResponseWithBuffer(buffer(Json.encode(new Snapshot())), HttpStatus.SC_CREATED); + doReturn(succeededFuture(snapshotHttpResponse)).when(sourceStorageSnapshotsClient).postSourceStorageSnapshots(any()); + doAnswer(i -> { + Consumer> sucessHandler = i.getArgument(1); + sucessHandler.accept(new Success<>(i.getArgument(0))); + return null; + }).when(instanceCollection).add(any(), any(), any()); + doReturn(succeededFuture()).when(precedingSucceedingTitlesHelper).createPrecedingSucceedingTitles(any(), any()); + doReturn(sourceStorageClient).when(handler).getSourceStorageRecordsClient(any(), any(), any()); + var sourceStorageHttpResponse = buildHttpResponseWithBuffer(null, HttpStatus.SC_BAD_REQUEST); + doReturn(succeededFuture(sourceStorageHttpResponse)).when(sourceStorageClient).postSourceStorageRecords(any()); + + var expectedMessage = "Failed to create MARC record in SRS, instanceId: "; + + // when + var future = handler.handle(event); + + // then + var exception = Assert.assertThrows(ExecutionException.class, future::get); + assertThat(exception.getCause().getMessage()).startsWith(expectedMessage); + } + + @Test + public void shouldReturnSucceededFuture_ifProcessFinishedCorrectly() throws IOException, ExecutionException, InterruptedException { + // given + var bibframeId = "someBibframeId"; + var event = new InstanceIngressEvent() + .withId(UUID.randomUUID().toString()) + .withEventPayload(new InstanceIngressPayload() + .withSourceRecordObject(TestUtil.readFileFromPath(BIB_RECORD_PATH)) + .withSourceType(BIBFRAME) + .withSourceRecordIdentifier(UUID.randomUUID().toString()) + .withAdditionalProperty("bibframeId", bibframeId) + ); + doReturn(succeededFuture(null)).when(idStorageService).store(anyString(), anyString(), anyString()); + var mappingRules = new JsonObject(TestUtil.readFileFromPath(MAPPING_RULES_PATH)); + doReturn(succeededFuture(Optional.of(new MappingMetadataDto() + .withMappingRules(mappingRules.encode()) + .withMappingParams(Json.encode(new MappingParameters()))))) + .when(mappingMetadataCache).getByRecordType(InstanceIngressEventConsumer.class.getSimpleName(), context, MARC_BIB_RECORD_TYPE); + doReturn(sourceStorageSnapshotsClient).when(handler).getSourceStorageSnapshotsClient(any(), any(), any()); + var snapshotHttpResponse = buildHttpResponseWithBuffer(buffer(Json.encode(new Snapshot())), HttpStatus.SC_CREATED); + doReturn(succeededFuture(snapshotHttpResponse)).when(sourceStorageSnapshotsClient).postSourceStorageSnapshots(any()); + doAnswer(i -> { + Consumer> sucessHandler = i.getArgument(1); + sucessHandler.accept(new Success<>(i.getArgument(0))); + return null; + }).when(instanceCollection).add(any(), any(), any()); + doReturn(succeededFuture()).when(precedingSucceedingTitlesHelper).createPrecedingSucceedingTitles(any(), any()); + doReturn(sourceStorageClient).when(handler).getSourceStorageRecordsClient(any(), any(), any()); + var sourceStorageHttpResponse = buildHttpResponseWithBuffer(buffer(Json.encode(new Record())), HttpStatus.SC_CREATED); + doReturn(succeededFuture(sourceStorageHttpResponse)).when(sourceStorageClient).postSourceStorageRecords(any()); + + // when + var future = handler.handle(event); + + // then + assertThat(future.isDone()).isTrue(); + + var instance = future.get(); + assertThat(instance.getId()).isEqualTo(event.getEventPayload().getSourceRecordIdentifier()); + assertThat(instance.getSource()).isEqualTo("BIBFRAME"); + assertThat(instance.getIdentifiers().stream().anyMatch(i -> i.value.equals("(bibframe) " + bibframeId))).isTrue(); + + var recordCaptor = ArgumentCaptor.forClass(Record.class); + verify(sourceStorageClient).postSourceStorageRecords(recordCaptor.capture()); + var recordSentToSRS = recordCaptor.getValue(); + assertThat(recordSentToSRS.getId()).isNotNull(); + assertThat(recordSentToSRS.getId()).isNotEqualTo(event.getEventPayload().getSourceRecordIdentifier()); + assertThat(recordSentToSRS.getId()).doesNotContain(bibframeId); + assertThat(recordSentToSRS.getRecordType()).isEqualTo(Record.RecordType.MARC_BIB); + assertThat(AdditionalFieldsUtil.getValue(recordSentToSRS, TAG_999, SUBFIELD_B)).hasValue(bibframeId); + assertThat(AdditionalFieldsUtil.getValue(recordSentToSRS, TAG_999, SUBFIELD_I)).hasValue(instance.getId()); + } +}