Skip to content

Commit

Permalink
MODLD-642: Retain edges when resource is fetched from DB (#93)
Browse files Browse the repository at this point in the history
  • Loading branch information
pkjacob authored Jan 16, 2025
1 parent ff9d7bd commit f0252a2
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 14 deletions.
11 changes: 0 additions & 11 deletions src/main/java/org/folio/linked/data/model/entity/Resource.java
Original file line number Diff line number Diff line change
Expand Up @@ -147,17 +147,6 @@ public Resource(@NonNull Resource that) {
.orElse(null);
}

public static Resource copyWithNoEdges(@NonNull Resource that) {
return new Resource()
.setId(that.id)
.setLabel(that.label)
.setDoc((JsonNode) ofNullable(that.getDoc()).map(JsonNode::deepCopy).orElse(null))
.setIndexDate(that.indexDate)
.setTypes(new LinkedHashSet<>(that.getTypes()))
.setIncomingEdges(new LinkedHashSet<>())
.setOutgoingEdges(new LinkedHashSet<>());
}

@Override
public boolean isNew() {
return !managed;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ private Optional<Resource> fetchResourceFromRepo(Identifiable identifiable) {
.flatMap(id -> resourceRepo.findById(parseLong(id)))
.map(ResourceUtils::ensureLatestReplaced)
.or(() -> Optional.ofNullable(identifiable.getSrsId())
.flatMap(resourceRepo::findByFolioMetadataSrsId))
.map(Resource::copyWithNoEdges);
.flatMap(resourceRepo::findByFolioMetadataSrsId));
}

private Resource createResourceFromSrs(String srsId) {
Expand All @@ -83,7 +82,6 @@ private Resource createResourceFromSrs(String srsId) {
.flatMap(this::contentAsJsonString)
.flatMap(this::firstAuthorityToEntity)
.map(resourceGraphService::saveMergingGraph)
.map(Resource::copyWithNoEdges)
.orElseThrow(() -> notFoundException(srsId));
} catch (FeignException.NotFound e) {
throw notFoundException(srsId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
package org.folio.linked.data.e2e.resource;

import static org.folio.linked.data.e2e.resource.ResourceControllerITBase.RESOURCE_URL;
import static org.folio.linked.data.test.TestUtil.awaitAndAssert;
import static org.folio.linked.data.test.TestUtil.defaultHeaders;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.folio.ld.dictionary.PredicateDictionary;
import org.folio.ld.dictionary.ResourceTypeDictionary;
import org.folio.linked.data.domain.dto.InstanceIngressEvent;
import org.folio.linked.data.e2e.base.IntegrationTest;
import org.folio.linked.data.model.entity.FolioMetadata;
import org.folio.linked.data.model.entity.Resource;
import org.folio.linked.data.model.entity.ResourceEdge;
import org.folio.linked.data.service.resource.hash.HashService;
import org.folio.linked.data.test.kafka.KafkaInventoryTopicListener;
import org.folio.linked.data.test.kafka.KafkaProducerTestConfiguration;
import org.folio.linked.data.test.resource.ResourceTestService;
import org.folio.marc4ld.service.marc2ld.reader.MarcReaderProcessor;
import org.junit.jupiter.api.Test;
import org.marc4j.marc.DataField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
import org.springframework.test.web.servlet.MockMvc;

@IntegrationTest
@SpringBootTest(classes = {KafkaProducerTestConfiguration.class})
class ResourceControllerUpdateWorkIT {
@Autowired
private ResourceTestService resourceTestService;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private HashService hashService;
@Autowired
private Environment env;
@Autowired
private MockMvc mockMvc;
@Autowired
private KafkaInventoryTopicListener inventoryTopicListener;
@Autowired
private MarcReaderProcessor marcReader;

@Test
void updateWork_should_send_update_instance_event_to_inventory() throws Exception {
// given
var person = getPerson();
resourceTestService.saveGraph(person);
var work = getWork();
var instance = getInstance(work);
resourceTestService.saveGraph(instance);

// when
var workUpdateRequestDto = getWorkRequestDto(person.getId(), instance.getId());

var updateRequest = put(RESOURCE_URL + "/" + work.getId())
.contentType(APPLICATION_JSON)
.headers(defaultHeaders(env))
.content(workUpdateRequestDto);

mockMvc.perform(updateRequest).andExpect(status().isOk());

// then
awaitAndAssert(() ->
assertTrue(inventoryTopicListener.getMessages().stream()
.anyMatch(m -> isExpectedEvent(m, instance.getId()))
)
);
}

private String getWorkRequestDto(Long personId, Long instanceId) {
return """
{
"resource": {
"http://bibfra.me/vocab/lite/Work": {
"http://bibfra.me/vocab/marc/title": [
{
"http://bibfra.me/vocab/marc/Title": {
"http://bibfra.me/vocab/marc/mainTitle": [ "simple_work" ]
}
}
],
"_creatorReference": [ { "id": "%PERSON_ID%" } ],
"_instanceReference": [ { "id": "%INSTANCE_ID%"} ]
}
}
}
"""
.replace("%PERSON_ID%", personId.toString())
.replace("%INSTANCE_ID%", instanceId.toString());
}

private Resource getWork() {
var title = new Resource()
.addTypes(ResourceTypeDictionary.TITLE)
.setDoc(getDoc("""
{
"http://bibfra.me/vocab/marc/mainTitle": ["simple_work"]
}
"""))
.setLabel("simple_work");
var work = new Resource()
.addTypes(ResourceTypeDictionary.WORK)
.setDoc(getDoc("{}"))
.setLabel("simple_work");

work.addOutgoingEdge(new ResourceEdge(work, title, PredicateDictionary.TITLE));

title.setId(hashService.hash(title));
work.setId(hashService.hash(work));

return work;
}

private Resource getInstance(Resource work) {
var title = new Resource()
.addTypes(ResourceTypeDictionary.TITLE)
.setDoc(getDoc("""
{
"http://bibfra.me/vocab/marc/mainTitle": ["simple_instance"]
}
"""))
.setLabel("simple_instance");
var instance = new Resource()
.addTypes(ResourceTypeDictionary.INSTANCE)
.setDoc(getDoc("{}"))
.setLabel("simple_instance");

instance.addOutgoingEdge(new ResourceEdge(instance, title, PredicateDictionary.TITLE));
instance.addOutgoingEdge(new ResourceEdge(instance, work, PredicateDictionary.INSTANTIATES));

title.setId(hashService.hash(title));
instance.setId(hashService.hash(instance));

return instance;
}

private Resource getPerson() {
var person = new Resource()
.addTypes(ResourceTypeDictionary.PERSON)
.setDoc(getDoc("""
{
"http://bibfra.me/vocab/lite/name": ["Person name"]
}
"""))
.setLabel("Person name");

var lccn = new Resource()
.addTypes(ResourceTypeDictionary.ID_LCCN, ResourceTypeDictionary.IDENTIFIER)
.setDoc(getDoc("""
{
"http://bibfra.me/vocab/lite/link": ["n123456789"]
}
"""))
.setLabel("n123456789");

person.setFolioMetadata(new FolioMetadata(person).setInventoryId("123456789"));
person.addOutgoingEdge(new ResourceEdge(person, lccn, PredicateDictionary.MAP));

person.setId(hashService.hash(person));
lccn.setId(hashService.hash(lccn));

return person;
}

@SneakyThrows
private JsonNode getDoc(String doc) {
return objectMapper.readTree(doc);
}

@SneakyThrows
private <T> T parse(String json, Class<T> clazz) {
return objectMapper.readValue(json, clazz);
}

private boolean isExpectedEvent(String eventStr, long linkedDataId) {
var event = parse(eventStr, InstanceIngressEvent.class);
var eventPayload = event.getEventPayload();
var marc = eventPayload.getSourceRecordObject();
return event.getEventType() == InstanceIngressEvent.EventTypeEnum.UPDATE_INSTANCE
&& eventPayload.getAdditionalProperties().get("linkedDataId").equals(linkedDataId)
&& isExpectedMarc(marc);
}

private boolean isExpectedMarc(String marcStr) {
var marc = marcReader.readMarc(marcStr).toList().get(0);
var df100 = (DataField) marc.getVariableField("100");
var df245 = (DataField) marc.getVariableField("245");
var isDf100Valid = df100.getSubfields().stream().anyMatch(
sf ->
sf.getCode() == 'a' && sf.getData().equals("Person name")
|| sf.getCode() == '0' && sf.getData().equals("n123456789")
|| sf.getCode() == '9' && sf.getData().equals("123456789")
);
var isDf245Valid = df245.getSubfields().stream().anyMatch(
sf -> sf.getCode() == 'a' && sf.getData().equals("simple_instance")
);
return isDf100Valid && isDf245Valid;
}
}

0 comments on commit f0252a2

Please sign in to comment.