From 75b433fd93cf1e4691a8db43179f6559cabf7b18 Mon Sep 17 00:00:00 2001 From: yaroslav-epam <138673581+yaroslav-epam@users.noreply.github.com> Date: Fri, 1 Dec 2023 14:06:31 +0200 Subject: [PATCH] MODINV-921: Apply 005 logic before saving MARC Bib in SRS (#653) --- NEWS.md | 1 + .../actions/CreateInstanceEventHandler.java | 14 +- .../dataimport/util/AdditionalFieldsUtil.java | 243 ++++++++++++++++++ .../CreateInstanceEventHandlerTest.java | 72 +++++- .../util/AdditionalFieldsUtilTest.java | 39 +++ 5 files changed, 354 insertions(+), 15 deletions(-) create mode 100644 src/main/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtil.java create mode 100644 src/test/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtilTest.java diff --git a/NEWS.md b/NEWS.md index 0aaceb275..a69d5c6c7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,6 @@ ## 20.2.0-SNAPSHOT 2023-XX-XX * [MODSOURMAN-1022](https://issues.folio.org/browse/MODSOURMAN-1022) Remove step of initial saving of incoming records to SRS +* [MODINV-921](https://issues.folio.org/browse/MODINV-921) Apply 005 logic before saving MARC Bib in SRS ## 20.1.0 2023-10-13 * Update status when user attempts to update shared auth record from member tenant ([MODDATAIMP-926](https://issues.folio.org/browse/MODDATAIMP-926)) 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 47cc9ea1f..e1c002a8d 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 @@ -12,6 +12,7 @@ import org.folio.inventory.dataimport.cache.MappingMetadataCache; import org.folio.inventory.dataimport.handlers.matching.util.EventHandlingUtil; import org.folio.inventory.dataimport.services.OrderHelperService; +import org.folio.inventory.dataimport.util.AdditionalFieldsUtil; import org.folio.inventory.dataimport.util.ParsedRecordUtil; import org.folio.inventory.domain.instances.Instance; import org.folio.inventory.domain.instances.InstanceCollection; @@ -100,8 +101,12 @@ public CompletableFuture handle(DataImportEventPayload d String instanceId = res.getEntityId(); mappingMetadataCache.get(jobExecutionId, context) .compose(parametersOptional -> parametersOptional - .map(mappingMetadata -> prepareAndExecuteMapping(dataImportEventPayload, new JsonObject(mappingMetadata.getMappingRules()), - Json.decodeValue(mappingMetadata.getMappingParams(), MappingParameters.class))) + .map(mappingMetadata -> { + MappingParameters mappingParameters = Json.decodeValue(mappingMetadata.getMappingParams(), MappingParameters.class); + AdditionalFieldsUtil.updateLatestTransactionDate(targetRecord, mappingParameters); + payloadContext.put(EntityType.MARC_BIBLIOGRAPHIC.value(), Json.encode(targetRecord)); + return prepareAndExecuteMapping(dataImportEventPayload, new JsonObject(mappingMetadata.getMappingRules()), mappingParameters); + }) .orElseGet(() -> Future.failedFuture(format(MAPPING_PARAMETERS_NOT_FOUND_MSG, jobExecutionId, recordId, chunkId)))) .compose(v -> { InstanceCollection instanceCollection = storage.getInstanceCollection(context); @@ -121,10 +126,7 @@ public CompletableFuture handle(DataImportEventPayload d .onSuccess(ar -> { dataImportEventPayload.getContext().put(INSTANCE.value(), Json.encode(ar)); orderHelperService.fillPayloadForOrderPostProcessingIfNeeded(dataImportEventPayload, DI_INVENTORY_INSTANCE_CREATED, context) - .onComplete(result -> { - future.complete(dataImportEventPayload); - } - ); + .onComplete(result -> future.complete(dataImportEventPayload)); }) .onFailure(e -> { if (!(e instanceof DuplicateEventException)) { diff --git a/src/main/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtil.java b/src/main/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtil.java new file mode 100644 index 000000000..ccee11afc --- /dev/null +++ b/src/main/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtil.java @@ -0,0 +1,243 @@ +package org.folio.inventory.dataimport.util; + +import com.github.benmanes.caffeine.cache.CacheLoader; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; +import io.vertx.core.Context; +import io.vertx.core.Vertx; +import io.vertx.core.json.Json; +import io.vertx.core.json.JsonObject; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.folio.processing.exceptions.EventProcessingException; +import org.folio.processing.mapping.defaultmapper.processor.parameters.MappingParameters; +import org.folio.rest.jaxrs.model.MarcFieldProtectionSetting; +import org.folio.rest.jaxrs.model.ParsedRecord; +import org.folio.rest.jaxrs.model.Record; +import org.marc4j.MarcJsonReader; +import org.marc4j.MarcJsonWriter; +import org.marc4j.MarcReader; +import org.marc4j.MarcStreamWriter; +import org.marc4j.MarcWriter; +import org.marc4j.marc.ControlField; +import org.marc4j.marc.MarcFactory; +import org.marc4j.marc.VariableField; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.ForkJoinPool; + +import static java.lang.String.format; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +/** + * Util to work with additional fields + */ +public final class AdditionalFieldsUtil { + + private static final Logger LOGGER = LogManager.getLogger(); + public static final String TAG_005 = "005"; + public static final DateTimeFormatter dateTime005Formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss.S"); + private static final String ANY_STRING = "*"; + private static final CacheLoader parsedRecordContentCacheLoader; + private static final LoadingCache parsedRecordContentCache; + + static { + // this function is executed when creating a new item to be saved in the cache. + // In this case, this is a MARC4J Record + parsedRecordContentCacheLoader = + parsedRecordContent -> { + MarcJsonReader marcJsonReader = + new MarcJsonReader( + new ByteArrayInputStream( + parsedRecordContent.getBytes(StandardCharsets.UTF_8))); + if (marcJsonReader.hasNext()) { + return marcJsonReader.next(); + } + return null; + }; + + parsedRecordContentCache = + Caffeine.newBuilder().maximumSize(2000) + // weak keys allows parsed content strings that are used as keys to be garbage + // collected, even it is still + // referenced by the cache. + .weakKeys() + .recordStats() + .executor( + serviceExecutor -> { + // Due to the static nature and the API of this AdditionalFieldsUtil class, it is difficult to + // pass a vertx instance or assume whether a call to any of its static methods here is by a Vertx + // thread or a regular thread. The logic before is able to discern the type of thread and execute + // cache operations using the appropriate threading model. + Context context = Vertx.currentContext(); + if (context != null) { + context.runOnContext(ar -> serviceExecutor.run()); + } else { + // The common pool below is used because it is the default executor for caffeine + ForkJoinPool.commonPool().execute(serviceExecutor); + } + }) + .build(parsedRecordContentCacheLoader); + } + + private AdditionalFieldsUtil() { + } + + /** + * Updates field 005 for case when this field is not protected. + * + * @param record record to update + * @param mappingParameters mapping parameters + */ + public static void updateLatestTransactionDate(Record record, MappingParameters mappingParameters) { + if (isField005NeedToUpdate(record, mappingParameters)) { + String date = dateTime005Formatter.format(ZonedDateTime.ofInstant(Instant.now(), ZoneId.systemDefault())); + boolean isLatestTransactionDateUpdated = addControlledFieldToMarcRecord(record, TAG_005, date); + if (!isLatestTransactionDateUpdated) { + throw new EventProcessingException(format("Failed to update field '005' to record with id '%s'", + record != null ? record.getId() : "null")); + } + } + } + + private static boolean addControlledFieldToMarcRecord(Record record, String field, String value) { + boolean result = false; + try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { + if (record != null && record.getParsedRecord() != null && record.getParsedRecord().getContent() != null) { + MarcWriter streamWriter = new MarcStreamWriter(new ByteArrayOutputStream()); + MarcJsonWriter jsonWriter = new MarcJsonWriter(os); + + org.marc4j.marc.Record marcRecord = computeMarcRecord(record); + if (marcRecord != null) { + ControlField currentField = (ControlField) marcRecord.getVariableField(field); + if (currentField != null) { + ControlField newControlField = MarcFactory.newInstance().newControlField(field, value); + marcRecord.getControlFields().set(marcRecord.getControlFields().indexOf(currentField), newControlField); + } + // use stream writer to recalculate leader + streamWriter.write(marcRecord); + jsonWriter.write(marcRecord); + + String parsedContentString = new JsonObject(os.toString()).encode(); + // save parsed content string to cache then set it on the record + parsedRecordContentCache.put(parsedContentString, marcRecord); + record.setParsedRecord(record.getParsedRecord().withContent(parsedContentString)); + result = true; + } + } + } catch (Exception e) { + LOGGER.warn("addControlledFieldToMarcRecord:: Failed to add additional controlled field {} to record {}", field, record.getId(), e); + } + return result; + } + + /** + * Read value from controlled field in marc record + * + * @param record marc record + * @param tag tag to read + * @return value from field + */ + public static String getValueFromControlledField(Record record, String tag) { + try { + org.marc4j.marc.Record marcRecord = computeMarcRecord(record); + if (marcRecord != null) { + Optional controlField = marcRecord.getControlFields() + .stream() + .filter(field -> field.getTag().equals(tag)) + .findFirst(); + if (controlField.isPresent()) { + return controlField.get().getData(); + } + } + } catch (Exception e) { + LOGGER.warn("getValueFromControlledField:: Failed to read controlled field {} from record {}", tag, record.getId(), e); + return null; + } + return null; + } + + private static MarcReader buildMarcReader(Record record) { + String content = normalizeContent(record.getParsedRecord()); + return new MarcJsonReader(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))); + } + + /** + * Checks whether field 005 needs to be updated or this field is protected. + * + * @param record record to check + * @param mappingParameters mapping parameters + * @return true for case when field 005 have to updated + */ + private static boolean isField005NeedToUpdate(Record record, MappingParameters mappingParameters) { + boolean needToUpdate = true; + List fieldProtectionSettings = mappingParameters.getMarcFieldProtectionSettings(); + if (CollectionUtils.isNotEmpty(fieldProtectionSettings)) { + MarcReader reader = new MarcJsonReader(new ByteArrayInputStream(record.getParsedRecord().getContent().toString().getBytes())); + if (reader.hasNext()) { + org.marc4j.marc.Record marcRecord = reader.next(); + VariableField field = marcRecord.getVariableFields(TAG_005).get(0); + needToUpdate = isNotProtected(fieldProtectionSettings, (ControlField) field); + } + } + return needToUpdate; + } + + /** + * Checks is the control field is protected or not. + * + * @param fieldProtectionSettings List of MarcFieldProtectionSettings + * @param field Control field that is being checked + * @return true for case when control field isn't protected + */ + private static boolean isNotProtected(List fieldProtectionSettings, ControlField field) { + return fieldProtectionSettings.stream() + .filter(setting -> setting.getField().equals(ANY_STRING) || setting.getField().equals(field.getTag())) + .noneMatch(setting -> setting.getData().equals(ANY_STRING) || setting.getData().equals(field.getData())); + } + + private static org.marc4j.marc.Record computeMarcRecord(Record record) { + if (record != null && record.getParsedRecord() != null && isNotBlank(record.getParsedRecord().getContent().toString())) { + try { + var content = normalizeContent(record.getParsedRecord().getContent()); + return parsedRecordContentCache.get(content); + } catch (Exception e) { + LOGGER.warn("computeMarcRecord:: Error during the transformation to marc record", e); + try { + MarcReader reader = buildMarcReader(record); + if (reader.hasNext()) { + return reader.next(); + } + } catch (Exception ex) { + LOGGER.warn("computeMarcRecord:: Error during the building of MarcReader", ex); + } + return null; + } + } + return null; + } + + /** + * Normalize parsed record content of {@link ParsedRecord} is type {@link String} + * + * @param parsedRecord parsed record + * @return parsed record normalized content + */ + private static String normalizeContent(ParsedRecord parsedRecord) { + Object content = parsedRecord.getContent(); + return (content instanceof String contentStr ? new JsonObject(contentStr) : JsonObject.mapFrom(content)).encode(); + } + + private static String normalizeContent(Object content) { + return content instanceof String contentStr ? contentStr : Json.encode(content); + } +} 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 18f255eb3..70a4b2859 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 @@ -24,6 +24,7 @@ import org.folio.inventory.dataimport.InstanceWriterFactory; import org.folio.inventory.dataimport.cache.MappingMetadataCache; import org.folio.inventory.dataimport.services.OrderHelperServiceImpl; +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.domain.relationship.RecordToEntity; @@ -54,6 +55,9 @@ import java.io.IOException; import java.net.URL; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.Collections; import java.util.HashMap; import java.util.UUID; @@ -70,6 +74,8 @@ 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.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; import static org.folio.rest.jaxrs.model.ProfileSnapshotWrapper.ContentType.ACTION_PROFILE; import static org.folio.rest.jaxrs.model.ProfileSnapshotWrapper.ContentType.JOB_PROFILE; @@ -90,10 +96,9 @@ public class CreateInstanceEventHandlerTest { private static final String PARSED_CONTENT = "{\"leader\":\"01314nam 22003851a 4500\",\"fields\":[{\"001\":\"ybp7406411\"},{\"245\":{\"ind1\":\"1\",\"ind2\":\"0\",\"subfields\":[{\"a\":\"titleValue\"}]}},{\"336\":{\"ind1\":\"1\",\"ind2\":\"0\",\"subfields\":[{\"b\":\"b6698d38-149f-11ec-82a8-0242ac130003\"}]}},{\"780\":{\"ind1\":\"0\",\"ind2\":\"0\",\"subfields\":[{\"t\":\"Houston oil directory\"}]}},{\"785\":{\"ind1\":\"0\",\"ind2\":\"0\",\"subfields\":[{\"t\":\"SAIS review of international affairs\"},{\"x\":\"1945-4724\"}]}},{\"500\":{\"ind1\":\" \",\"ind2\":\" \",\"subfields\":[{\"a\":\"Adaptation of Xi xiang ji by Wang Shifu.\"}]}},{\"520\":{\"ind1\":\" \",\"ind2\":\" \",\"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.\"}]}} , {\"999\": {\"ind1\":\"f\", \"ind2\":\"f\", \"subfields\":[ { \"i\": \"957985c6-97e3-4038-b0e7-343ecd0b8120\"} ] } }]}"; - private static final String PARSED_CONTENT_999ffi = "{\"leader\":\"01314nam 22003851a 4500\",\"fields\":[{\"001\":\"ybp7406411\"},{\"245\":{\"ind1\":\"1\",\"ind2\":\"0\",\"subfields\":[{\"a\":\"titleValue\"}]}},{\"336\":{\"ind1\":\"1\",\"ind2\":\"0\",\"subfields\":[{\"b\":\"b6698d38-149f-11ec-82a8-0242ac130003\"}]}},{\"780\":{\"ind1\":\"0\",\"ind2\":\"0\",\"subfields\":[{\"t\":\"Houston oil directory\"}]}},{\"785\":{\"ind1\":\"0\",\"ind2\":\"0\",\"subfields\":[{\"t\":\"SAIS review of international affairs\"},{\"x\":\"1945-4724\"}]}},{\"500\":{\"ind1\":\" \",\"ind2\":\" \",\"subfields\":[{\"a\":\"Adaptation of Xi xiang ji by Wang Shifu.\"}]}},{\"520\":{\"ind1\":\" \",\"ind2\":\" \",\"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.\"}]}}]}"; + private static final String PARSED_CONTENT_WITH_005 = "{\"leader\":\"01314nam 22003851a 4500\",\"fields\":[{\"001\":\"ybp7406411\"},{\"005\":\"20141107001016.0\"},{\"245\":{\"ind1\":\"1\",\"ind2\":\"0\",\"subfields\":[{\"a\":\"titleValue\"}]}},{\"336\":{\"ind1\":\"1\",\"ind2\":\"0\",\"subfields\":[{\"b\":\"b6698d38-149f-11ec-82a8-0242ac130003\"}]}},{\"780\":{\"ind1\":\"0\",\"ind2\":\"0\",\"subfields\":[{\"t\":\"Houston oil directory\"}]}},{\"785\":{\"ind1\":\"0\",\"ind2\":\"0\",\"subfields\":[{\"t\":\"SAIS review of international affairs\"},{\"x\":\"1945-4724\"}]}},{\"500\":{\"ind1\":\" \",\"ind2\":\" \",\"subfields\":[{\"a\":\"Adaptation of Xi xiang ji by Wang Shifu.\"}]}},{\"520\":{\"ind1\":\" \",\"ind2\":\" \",\"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.\"}]}} , {\"999\": {\"ind1\":\"f\", \"ind2\":\"f\", \"subfields\":[ { \"i\": \"957985c6-97e3-4038-b0e7-343ecd0b8120\"} ] } }]}"; private static final String MAPPING_RULES_PATH = "src/test/resources/handlers/bib-rules.json"; - public static final String OKAPI_URL = "http://localhost"; private static final String MAPPING_METADATA_URL = "/mapping-metadata"; private static final String TENANT_ID = "diku"; private static final String TOKEN = "dummy"; @@ -246,6 +251,54 @@ public void shouldProcessEvent() throws InterruptedException, ExecutionException verify(mockedClient, times(2)).post(any(URL.class), any(JsonObject.class)); } + @Test + public void shouldProcessEventAndUpdate005Field() throws InterruptedException, ExecutionException, TimeoutException { + Reader fakeReader = Mockito.mock(Reader.class); + + String instanceTypeId = "fe19bae4-da28-472b-be90-d442e2428ead"; + String recordId = "567859ad-505a-400d-a699-0028a1fdbf84"; + String instanceId = "4d4545df-b5ba-4031-a031-70b1c1b2fc5d"; + String title = "titleValue"; + RecordToEntity recordToInstance = RecordToEntity.builder().recordId(recordId).entityId(instanceId).build(); + + String expectedDate = dateTime005Formatter.format(ZonedDateTime.ofInstant(Instant.now(), ZoneId.systemDefault())); + + when(fakeReader.read(any(MappingRule.class))).thenReturn(StringValue.of(instanceTypeId), StringValue.of(title)); + when(fakeReaderFactory.createReader()).thenReturn(fakeReader); + when(storage.getInstanceCollection(any())).thenReturn(instanceRecordCollection); + when(instanceIdStorageService.store(any(), any(), any())).thenReturn(Future.succeededFuture(recordToInstance)); + MappingManager.registerReaderFactory(fakeReaderFactory); + MappingManager.registerWriterFactory(new InstanceWriterFactory()); + + HashMap context = new HashMap<>(); + Record record = new Record().withParsedRecord(new ParsedRecord().withContent(PARSED_CONTENT_WITH_005)); + record.setId(recordId); + context.put(MARC_BIBLIOGRAPHIC.value(), Json.encode(record)); + + DataImportEventPayload dataImportEventPayload = new DataImportEventPayload() + .withEventType(DI_INVENTORY_INSTANCE_CREATED.value()) + .withContext(context) + .withCurrentNode(profileSnapshotWrapper.getChildSnapshotWrappers().get(0)) + .withTenant(TENANT_ID) + .withOkapiUrl(mockServer.baseUrl()) + .withToken(TOKEN) + .withJobExecutionId(UUID.randomUUID().toString()) + .withOkapiUrl(mockServer.baseUrl()); + + CompletableFuture future = createInstanceEventHandler.handle(dataImportEventPayload); + DataImportEventPayload actualDataImportEventPayload = future.get(20, TimeUnit.SECONDS); + + assertEquals(DI_INVENTORY_INSTANCE_CREATED.value(), actualDataImportEventPayload.getEventType()); + assertNotNull(actualDataImportEventPayload.getContext().get(INSTANCE.value())); + JsonObject createdInstance = new JsonObject(actualDataImportEventPayload.getContext().get(INSTANCE.value())); + String actualInstanceId = createdInstance.getString("id"); + assertNotNull(actualInstanceId); + assertEquals(instanceId, actualInstanceId); + String actualDate = AdditionalFieldsUtil.getValueFromControlledField( + Json.decodeValue(context.get(MARC_BIBLIOGRAPHIC.value()), Record.class), TAG_005); + assertEquals(expectedDate.substring(0, 10), actualDate.substring(0, 10)); + } + @Test public void shouldProcessConsortiumEvent() throws InterruptedException, ExecutionException, TimeoutException { Reader fakeReader = Mockito.mock(Reader.class); @@ -421,7 +474,7 @@ public void shouldNotProcessEventIfMarcBibliographicIsEmptyInContext() throws In future.get(5, TimeUnit.MILLISECONDS); } - @Test(expected = ExecutionException.class) + @Test(expected = Exception.class) public void shouldNotProcessEventIfRequiredFieldIsEmpty() throws InterruptedException, ExecutionException, TimeoutException { Reader fakeReader = Mockito.mock(Reader.class); @@ -442,6 +495,10 @@ public void shouldNotProcessEventIfRequiredFieldIsEmpty() throws InterruptedExce DataImportEventPayload dataImportEventPayload = new DataImportEventPayload() .withEventType(DI_INVENTORY_INSTANCE_CREATED.value()) .withContext(context) + .withTenant(TENANT_ID) + .withOkapiUrl(mockServer.baseUrl()) + .withToken(TOKEN) + .withJobExecutionId(UUID.randomUUID().toString()) .withCurrentNode(profileSnapshotWrapper.getChildSnapshotWrappers().get(0)); CompletableFuture future = createInstanceEventHandler.handle(dataImportEventPayload); @@ -549,8 +606,7 @@ public void shouldNotProcessEventWhenRecordToInstanceFutureFails() throws Execut .withTenant(TENANT_ID) .withOkapiUrl(mockServer.baseUrl()) .withToken(TOKEN) - .withJobExecutionId(UUID.randomUUID().toString()) - .withOkapiUrl(mockServer.baseUrl()); + .withJobExecutionId(UUID.randomUUID().toString()); CompletableFuture future = createInstanceEventHandler.handle(dataImportEventPayload); future.get(5, TimeUnit.SECONDS); @@ -596,8 +652,7 @@ public void shouldNotProcessEventEvenIfDuplicatedInventoryStorageErrorExists() t .withTenant(TENANT_ID) .withOkapiUrl(mockServer.baseUrl()) .withToken(TOKEN) - .withJobExecutionId(UUID.randomUUID().toString()) - .withOkapiUrl(mockServer.baseUrl()); + .withJobExecutionId(UUID.randomUUID().toString()); CompletableFuture future = createInstanceEventHandler.handle(dataImportEventPayload); @@ -643,8 +698,7 @@ public void shouldNotProcessEventEvenIfInventoryStorageErrorExists() throws Inte .withTenant(TENANT_ID) .withOkapiUrl(mockServer.baseUrl()) .withToken(TOKEN) - .withJobExecutionId(UUID.randomUUID().toString()) - .withOkapiUrl(mockServer.baseUrl()); + .withJobExecutionId(UUID.randomUUID().toString()); CompletableFuture future = createInstanceEventHandler.handle(dataImportEventPayload); diff --git a/src/test/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtilTest.java b/src/test/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtilTest.java new file mode 100644 index 000000000..7486b751c --- /dev/null +++ b/src/test/java/org/folio/inventory/dataimport/util/AdditionalFieldsUtilTest.java @@ -0,0 +1,39 @@ +package org.folio.inventory.dataimport.util; + +import org.folio.processing.mapping.defaultmapper.processor.parameters.MappingParameters; +import org.folio.rest.jaxrs.model.ExternalIdsHolder; +import org.folio.rest.jaxrs.model.ParsedRecord; +import org.folio.rest.jaxrs.model.Record; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.BlockJUnit4ClassRunner; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.UUID; + +import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.TAG_005; +import static org.folio.inventory.dataimport.util.AdditionalFieldsUtil.dateTime005Formatter; +import static org.junit.Assert.assertEquals; + +@RunWith(BlockJUnit4ClassRunner.class) +public class AdditionalFieldsUtilTest { + + @Test + public void shouldUpdate005Field() { + String parsedContent = "{\"leader\":\"00115nam 22000731a 4500\",\"fields\":[{\"001\":\"in001\"},{\"003\":\"qwerty\"},{\"005\":\"20141107001016.0\"},{\"507\":{\"subfields\":[{\"a\":\"data\"}],\"ind1\":\" \",\"ind2\":\" \"}},{\"500\":{\"subfields\":[{\"a\":\"data\"}],\"ind1\":\" \",\"ind2\":\" \"}}]}"; + Record record = new Record().withId(UUID.randomUUID().toString()) + .withParsedRecord(new ParsedRecord().withContent(parsedContent)) + .withGeneration(0) + .withState(Record.State.ACTUAL) + .withExternalIdsHolder(new ExternalIdsHolder().withInstanceId("001").withInstanceHrid("in001")); + + String expectedDate = dateTime005Formatter.format(ZonedDateTime.ofInstant(Instant.now(), ZoneId.systemDefault())); + + AdditionalFieldsUtil.updateLatestTransactionDate(record, new MappingParameters()); + + String actualDate = AdditionalFieldsUtil.getValueFromControlledField(record, TAG_005); + assertEquals(expectedDate.substring(0, 10), actualDate.substring(0, 10)); + } +}