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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.folio.linked.data.model.entity.Resource;
import org.folio.linked.data.model.entity.ResourceEdge;
import org.folio.linked.data.model.entity.ResourceTypeEntity;
import org.folio.linked.data.test.resource.FingerprintRuleGraphValidator;
import org.folio.linked.data.test.resource.ResourceTestService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -43,13 +44,19 @@ public abstract class PostResourceIT extends ITBase {
private Environment env;
@Autowired
private ResourceTestService resourceService;
@Autowired
private FingerprintRuleGraphValidator fingerprintRuleGraphValidator;

protected abstract String postPayload();

protected abstract void validateApiResponse(ResultActions apiResponse);

protected abstract void validateGraph(Resource resource);

protected Set<Set<String>> excludedFingerprintValidationTypes() {
return Set.of();
}

@Test
void testPostRequest() throws Exception {
// given
Expand All @@ -68,6 +75,7 @@ void testPostRequest() throws Exception {
validateApiResponse(postResponse);
var resourceId = getResourceId(postResponse);
var resource = resourceService.getResourceById(resourceId, RESOURCE_FETCH_DEPTH);
fingerprintRuleGraphValidator.validateFingerprintRuleExists(resource, excludedFingerprintValidationTypes());
validateGraph(resource);

var getRequest = get(RESOURCE_URL + "/" + resourceId)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
package org.folio.linked.data.e2e.mappings.instance.publicationfrequency;

import static org.assertj.core.api.Assertions.assertThat;
import static org.folio.ld.dictionary.ResourceTypeDictionary.FREQUENCY;
import static org.folio.linked.data.test.TestUtil.TEST_JSON_MAPPER;

import java.util.Set;
import lombok.SneakyThrows;
import org.folio.linked.data.e2e.mappings.PostResourceIT;
import org.folio.linked.data.model.entity.Resource;
import org.springframework.test.web.servlet.ResultActions;

public class PublicationFrequencyIT extends PostResourceIT {

@Override
protected Set<Set<String>> excludedFingerprintValidationTypes() {
return Set.of(Set.of(FREQUENCY.name())); // use all properties for fingerprinting
}

@Override
protected String postPayload() {
return """
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,12 @@
import org.folio.linked.data.model.entity.Resource;
import org.folio.linked.data.model.entity.ResourceEdge;
import org.folio.linked.data.model.entity.ResourceTypeEntity;
import org.folio.linked.data.test.resource.FingerprintRuleGraphValidator;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.ResultActions;
Expand All @@ -265,6 +267,8 @@ abstract class ResourceControllerITBase extends ITBase {
private SpecClient specClient;

private LookupResources lookupResources;
@Autowired
private FingerprintRuleGraphValidator fingerprintRuleGraphValidator;

@BeforeEach
@Override
Expand Down Expand Up @@ -299,6 +303,8 @@ void createInstanceWithWorkRef_shouldSaveEntityCorrectly() throws Exception {
var resourceResponse = TEST_JSON_MAPPER.readValue(response, ResourceResponseDto.class);
var instanceResponse = ((InstanceResponseField) resourceResponse.getResource()).getInstance();
var instanceResource = resourceTestService.getResourceById(instanceResponse.getId(), 4);
var excludedFingerprintTypes = Set.of(Set.of(ResourceTypeDictionary.SUPPLEMENTARY_CONTENT.name()));
fingerprintRuleGraphValidator.validateFingerprintRuleExists(instanceResource, excludedFingerprintTypes);
assertThat(instanceResource.getFolioMetadata().getSource()).isEqualTo(LINKED_DATA);
validateInstance(instanceResource, true);
var workId = instanceResponse.getWorkReference().getFirst().getId();
Expand Down Expand Up @@ -335,6 +341,7 @@ void createWorkWithInstanceRef_shouldSaveEntityCorrectly() throws Exception {
var resourceResponse = TEST_JSON_MAPPER.readValue(response, ResourceResponseDto.class);
var id = ((WorkResponseField) resourceResponse.getResource()).getWork().getId();
var workResource = resourceTestService.getResourceById(id, 4);
fingerprintRuleGraphValidator.validateFingerprintRuleExists(workResource);
validateWork(workResource, true);
checkSearchIndexMessage(workResource.getId(), CREATE);
checkIndexDate(workResource.getId().toString());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package org.folio.linked.data.test.resource;

import static java.util.function.Predicate.not;
import static org.assertj.core.api.Assertions.assertThat;
import static org.folio.ld.dictionary.ResourceTypeDictionary.BOOKS;
import static org.folio.ld.dictionary.ResourceTypeDictionary.WORK;

import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.folio.ld.dictionary.ResourceTypeDictionary;
import org.folio.ld.fingerprint.config.FingerprintRules;
import org.folio.linked.data.model.entity.Resource;
import org.folio.linked.data.model.entity.ResourceEdge;
import org.folio.linked.data.model.entity.ResourceTypeEntity;
import org.springframework.stereotype.Service;

@Service
public class FingerprintRuleGraphValidator {

private static final Set<Set<String>> EXCLUDED_TYPE_COMBINATIONS = Set.of(
Set.of(WORK.name(), BOOKS.name()) // covered by partialTypeMatch rule in fingerprint library
);

private final Set<Set<String>> fingerprintTypeCombinations;

public FingerprintRuleGraphValidator(FingerprintRules fingerprintRules) {
this.fingerprintTypeCombinations = fingerprintRules.getRules().stream()
.map(FingerprintRules.FingerprintRule::types)
.map(this::normalizeTypes)
.collect(Collectors.toCollection(LinkedHashSet::new));
}

public void validateFingerprintRuleExists(Resource rootResource) {
validateFingerprintRuleExists(rootResource, Set.of());
}

public void validateFingerprintRuleExists(Resource rootResource, Set<Set<String>> excludedTypes) {
var excludedTypeCombinations = new LinkedHashSet<>(EXCLUDED_TYPE_COMBINATIONS);
excludedTypeCombinations.addAll(excludedTypes);

var resourcesWithMissingRules = collectOutgoingResources(rootResource).stream()
.map(this::toNormalizedTypeCombination)
.filter(not(excludedTypeCombinations::contains))
.filter(not(fingerprintTypeCombinations::contains))
.collect(Collectors.toCollection(LinkedHashSet::new));

assertThat(resourcesWithMissingRules)
.withFailMessage(() -> "FingerprintRules does not contain type combinations: " + resourcesWithMissingRules)
.isEmpty();
}

private Set<Resource> collectOutgoingResources(Resource rootResource) {
var visitedResourceIds = new LinkedHashSet<Long>();
var resources = new LinkedHashSet<Resource>();
collectOutgoingResources(rootResource, visitedResourceIds, resources);

return resources;
}

private void collectOutgoingResources(Resource resource, Set<Long> visitedResourceIds, Set<Resource> resources) {
if (resource == null || !visitedResourceIds.add(resource.getId())) {
return;
}

resources.add(resource);
resource.getOutgoingEdges().stream()
.map(ResourceEdge::getTarget)
.filter(Objects::nonNull)
.forEach(target -> collectOutgoingResources(target, visitedResourceIds, resources));
}

private Set<String> toNormalizedTypeCombination(Resource resource) {
return normalizeTypes(resource.getTypes().stream()
.map(ResourceTypeEntity::getUri)
.collect(Collectors.toCollection(LinkedHashSet::new)));
}

private Set<String> normalizeTypes(Collection<?> types) {
return types.stream()
.filter(Objects::nonNull)
.map(this::toTypeName)
.map(String::trim)
.filter(not(String::isBlank))
.collect(Collectors.toCollection(LinkedHashSet::new));
}

private String toTypeName(Object type) {
if (type instanceof Enum<?> enumType) {
return enumType.name();
}

var typeName = String.valueOf(type);
return ResourceTypeDictionary.fromUri(typeName)
.map(Enum::name)
.orElse(typeName);
}
}