diff --git a/.run/LinkedDataApplication [Local-Standalone].run.xml b/.run/LinkedDataApplication [Local-Standalone].run.xml
index a1699103..b07b7131 100644
--- a/.run/LinkedDataApplication [Local-Standalone].run.xml
+++ b/.run/LinkedDataApplication [Local-Standalone].run.xml
@@ -13,4 +13,4 @@
-
\ No newline at end of file
+
diff --git a/README.md b/README.md
index cbe5005d..0ec53963 100644
--- a/README.md
+++ b/README.md
@@ -61,6 +61,7 @@ To run mod-linked-data in standalone mode, set the value of the environment vari
| KAFKA_WORK_SEARCH_INDEX_TOPIC_PARTITIONS`*` | 1 | Custom Work Search Index topic partitions number |
| KAFKA_WORK_SEARCH_INDEX_TOPIC_REPLICATION_FACTOR`*` | - | Custom Work Search Index topic replication factor |
| KAFKA_INVENTORY_INSTANCE_INGRESS_EVENT_TOPIC`*` | inventory.instance_ingress | Custom Inventory Instance Ingress Event topic name |
+| CACHE_TTL_SPEC_RULES_MS | 18000000 | Specifies time to live for `spec-rules` cache |
* Applicable only in FOLIO mode
## REST API
Full list of APIs are documented in [src/main/resources/swagger.api/mod-linked-data.yaml](https://github.com/folio-org/mod-linked-data/blob/master/src/main/resources/swagger.api/mod-linked-data.yaml).
diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json
index 283465bf..c0e982d0 100644
--- a/descriptors/ModuleDescriptor-template.json
+++ b/descriptors/ModuleDescriptor-template.json
@@ -25,7 +25,9 @@
"source-storage.snapshots.post",
"source-storage.records.formatted.item.get",
"source-storage.records.post",
- "source-storage.records.put"
+ "source-storage.records.put",
+ "specification-storage.specifications.collection.get",
+ "specification-storage.specification.rules.collection.get"
]
},
{
@@ -65,7 +67,9 @@
"source-storage.snapshots.post",
"source-storage.records.formatted.item.get",
"source-storage.records.post",
- "source-storage.records.put"
+ "source-storage.records.put",
+ "specification-storage.specifications.collection.get",
+ "specification-storage.specification.rules.collection.get"
]
},
{
@@ -172,6 +176,10 @@
{
"id": "source-storage-records",
"version": "3.2 3.3"
+ },
+ {
+ "id": "specification-storage",
+ "version": "1.0"
}
],
"permissionSets": [
@@ -277,7 +285,8 @@
{ "name": "KAFKA_WORK_SEARCH_INDEX_TOPIC", "value": "linked-data.work" },
{ "name": "KAFKA_WORK_SEARCH_INDEX_TOPIC_PARTITIONS", "value": "1" },
{ "name": "KAFKA_WORK_SEARCH_INDEX_TOPIC_REPLICATION_FACTOR", "value": "" },
- { "name": "KAFKA_INVENTORY_INSTANCE_INGRESS_EVENT_TOPIC", "value": "" }
+ { "name": "KAFKA_INVENTORY_INSTANCE_INGRESS_EVENT_TOPIC", "value": "" },
+ { "name": "CACHE_TTL_SPEC_RULES_MS", "value": "18000000" }
]
}
}
diff --git a/pom.xml b/pom.xml
index 0061791b..9bac90be 100644
--- a/pom.xml
+++ b/pom.xml
@@ -48,6 +48,7 @@
1.0.2-SNAPSHOT
1.0.1-SNAPSHOT
5.9.1
+ 1.1.0-SNAPSHOT
10.20.1
@@ -102,6 +103,11 @@
mod-source-record-storage-client
${mod-source-record-storage-client.version}
+
+ org.folio
+ mod-record-specifications-dto
+ ${mod-record-specifications-dto.version}
+
org.folio
folio-kafka-wrapper
diff --git a/src/main/java/org/folio/linked/data/LinkedDataApplication.java b/src/main/java/org/folio/linked/data/LinkedDataApplication.java
index dc06287a..584494a2 100644
--- a/src/main/java/org/folio/linked/data/LinkedDataApplication.java
+++ b/src/main/java/org/folio/linked/data/LinkedDataApplication.java
@@ -9,10 +9,12 @@
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
@EnableCaching
@EnableAsync
@EnableFeignClients
+@EnableScheduling
@SpringBootApplication
@ComponentScan(value = "org.folio", excludeFilters = @Filter(type = REGEX, pattern =
{"org.folio.spring.tools.systemuser.*", "org.folio.spring.tools.batch.*"}))
diff --git a/src/main/java/org/folio/linked/data/client/SpecClient.java b/src/main/java/org/folio/linked/data/client/SpecClient.java
new file mode 100644
index 00000000..e4170bc5
--- /dev/null
+++ b/src/main/java/org/folio/linked/data/client/SpecClient.java
@@ -0,0 +1,24 @@
+package org.folio.linked.data.client;
+
+import static org.folio.linked.data.util.Constants.Cache.SPEC_RULES;
+
+import java.util.UUID;
+import org.folio.rspec.domain.dto.SpecificationDtoCollection;
+import org.folio.rspec.domain.dto.SpecificationRuleDtoCollection;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+
+@FeignClient(name = "specification-storage")
+public interface SpecClient {
+
+ @Cacheable(cacheNames = "bib-marc-specs")
+ @GetMapping(value = "/specifications?profile=bibliographic&family=MARC")
+ ResponseEntity getBibMarcSpecs();
+
+ @Cacheable(cacheNames = SPEC_RULES, key = "#specId")
+ @GetMapping(value = "/specifications/{specId}/rules")
+ ResponseEntity getSpecRules(@PathVariable("specId") UUID specId);
+}
diff --git a/src/main/java/org/folio/linked/data/job/CacheCleaningJob.java b/src/main/java/org/folio/linked/data/job/CacheCleaningJob.java
new file mode 100644
index 00000000..4ed8eb1c
--- /dev/null
+++ b/src/main/java/org/folio/linked/data/job/CacheCleaningJob.java
@@ -0,0 +1,19 @@
+package org.folio.linked.data.job;
+
+import static org.folio.linked.data.util.Constants.Cache.SPEC_RULES;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Component
+@Slf4j
+public class CacheCleaningJob {
+
+ @CacheEvict(value = SPEC_RULES, allEntries = true)
+ @Scheduled(fixedRateString = "${mod-linked-data.cache.ttl.spec-rules}")
+ public void emptySpecRules() {
+ log.info("Emptying {} cache", SPEC_RULES);
+ }
+}
diff --git a/src/main/java/org/folio/linked/data/util/Constants.java b/src/main/java/org/folio/linked/data/util/Constants.java
index 166f8012..56766cf6 100644
--- a/src/main/java/org/folio/linked/data/util/Constants.java
+++ b/src/main/java/org/folio/linked/data/util/Constants.java
@@ -21,4 +21,8 @@ public class Constants {
public static final String MSG_NOT_FOUND_IN = "{} with {}: {} was not found in {}";
public static final String LINKED_DATA_STORAGE = "Linked Data storage";
+ @UtilityClass
+ public static class Cache {
+ public static final String SPEC_RULES = "spec-rules";
+ }
}
diff --git a/src/main/java/org/folio/linked/data/validation/dto/LccnPatternValidator.java b/src/main/java/org/folio/linked/data/validation/dto/LccnPatternValidator.java
index dcb87fc8..e994b1a0 100644
--- a/src/main/java/org/folio/linked/data/validation/dto/LccnPatternValidator.java
+++ b/src/main/java/org/folio/linked/data/validation/dto/LccnPatternValidator.java
@@ -5,17 +5,25 @@
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import java.util.regex.Pattern;
+import lombok.RequiredArgsConstructor;
import org.folio.linked.data.domain.dto.LccnRequest;
import org.folio.linked.data.validation.LccnPatternConstraint;
+import org.folio.linked.data.validation.spec.SpecProvider;
+import org.folio.rspec.domain.dto.SpecificationRuleDto;
+@RequiredArgsConstructor
public class LccnPatternValidator implements ConstraintValidator {
+ public static final String CODE = "invalidLccnSubfieldValue";
+
private static final Pattern LCCN_STRUCTURE_A_PATTERN = Pattern.compile("( {3}|[a-z][ |a-z]{2})\\d{8} ");
private static final Pattern LCCN_STRUCTURE_B_PATTERN = Pattern.compile("( {2}|[a-z][ |a-z])\\d{10}");
+ private final SpecProvider specProvider;
+
@Override
public boolean isValid(LccnRequest lccnRequest, ConstraintValidatorContext constraintValidatorContext) {
- if (isCurrent(lccnRequest)) {
+ if (isCurrent(lccnRequest) && isLccnFormatValidationEnabled()) {
return lccnRequest.getValue()
.stream()
.anyMatch(this::hasValidPattern);
@@ -31,6 +39,15 @@ private boolean isCurrent(LccnRequest lccnRequest) {
.anyMatch(link -> link.endsWith("current"));
}
+ private boolean isLccnFormatValidationEnabled() {
+ return specProvider.getSpecRules()
+ .stream()
+ .filter(rule -> CODE.equals(rule.getCode()))
+ .findFirst()
+ .map(SpecificationRuleDto::getEnabled)
+ .orElse(false);
+ }
+
private boolean hasValidPattern(String lccnValue) {
return LCCN_STRUCTURE_A_PATTERN.matcher(lccnValue).matches()
|| LCCN_STRUCTURE_B_PATTERN.matcher(lccnValue).matches();
diff --git a/src/main/java/org/folio/linked/data/validation/spec/SpecProvider.java b/src/main/java/org/folio/linked/data/validation/spec/SpecProvider.java
new file mode 100644
index 00000000..04c101d3
--- /dev/null
+++ b/src/main/java/org/folio/linked/data/validation/spec/SpecProvider.java
@@ -0,0 +1,9 @@
+package org.folio.linked.data.validation.spec;
+
+import java.util.List;
+import org.folio.rspec.domain.dto.SpecificationRuleDto;
+
+public interface SpecProvider {
+
+ List getSpecRules();
+}
diff --git a/src/main/java/org/folio/linked/data/validation/spec/impl/SpecProviderImpl.java b/src/main/java/org/folio/linked/data/validation/spec/impl/SpecProviderImpl.java
new file mode 100644
index 00000000..03752689
--- /dev/null
+++ b/src/main/java/org/folio/linked/data/validation/spec/impl/SpecProviderImpl.java
@@ -0,0 +1,46 @@
+package org.folio.linked.data.validation.spec.impl;
+
+import feign.FeignException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.folio.linked.data.client.SpecClient;
+import org.folio.linked.data.validation.spec.SpecProvider;
+import org.folio.rspec.domain.dto.SpecificationDto;
+import org.folio.rspec.domain.dto.SpecificationDtoCollection;
+import org.folio.rspec.domain.dto.SpecificationRuleDto;
+import org.folio.rspec.domain.dto.SpecificationRuleDtoCollection;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+@Slf4j
+public class SpecProviderImpl implements SpecProvider {
+
+ private final SpecClient client;
+
+ @Override
+ public List getSpecRules() {
+ try {
+ return Optional.ofNullable(client.getBibMarcSpecs().getBody())
+ .map(SpecificationDtoCollection::getSpecifications)
+ .stream()
+ .flatMap(Collection::stream)
+ .findFirst()
+ .map(SpecificationDto::getId)
+ .map(client::getSpecRules)
+ .map(ResponseEntity::getBody)
+ .map(SpecificationRuleDtoCollection::getRules)
+ .stream()
+ .flatMap(Collection::stream)
+ .toList();
+ } catch (FeignException e) {
+ log.error("Unexpected exception during specification rules retrieval", e);
+ return Collections.emptyList();
+ }
+ }
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 15a706e0..e612557f 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -79,6 +79,9 @@ mod-linked-data:
topic:
work-search-index: ${KAFKA_WORK_SEARCH_INDEX_TOPIC:linked-data.work}
instance-ingress: ${KAFKA_INVENTORY_INSTANCE_INGRESS_EVENT_TOPIC:inventory.instance_ingress}
+ cache:
+ ttl:
+ spec-rules: ${CACHE_TTL_SPEC_RULES:18000000}
management:
endpoints:
diff --git a/src/test/java/org/folio/linked/data/e2e/resource/ResourceControllerITBase.java b/src/test/java/org/folio/linked/data/e2e/resource/ResourceControllerITBase.java
index 9bed359a..34294b7b 100644
--- a/src/test/java/org/folio/linked/data/e2e/resource/ResourceControllerITBase.java
+++ b/src/test/java/org/folio/linked/data/e2e/resource/ResourceControllerITBase.java
@@ -4,6 +4,7 @@
import static java.lang.String.join;
import static java.util.Spliterator.ORDERED;
import static java.util.Spliterators.spliteratorUnknownSize;
+import static java.util.UUID.randomUUID;
import static java.util.stream.StreamSupport.stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.folio.ld.dictionary.PredicateDictionary.ACCESS_LOCATION;
@@ -154,6 +155,7 @@
import org.folio.ld.dictionary.PredicateDictionary;
import org.folio.ld.dictionary.PropertyDictionary;
import org.folio.ld.dictionary.ResourceTypeDictionary;
+import org.folio.linked.data.client.SpecClient;
import org.folio.linked.data.client.SrsClient;
import org.folio.linked.data.domain.dto.InstanceResponseField;
import org.folio.linked.data.domain.dto.ResourceIndexEventType;
@@ -167,8 +169,13 @@
import org.folio.linked.data.service.resource.hash.HashService;
import org.folio.linked.data.test.ResourceTestService;
import org.folio.linked.data.test.TestUtil;
+import org.folio.linked.data.validation.dto.LccnPatternValidator;
import org.folio.rest.jaxrs.model.ParsedRecord;
import org.folio.rest.jaxrs.model.Record;
+import org.folio.rspec.domain.dto.SpecificationDto;
+import org.folio.rspec.domain.dto.SpecificationDtoCollection;
+import org.folio.rspec.domain.dto.SpecificationRuleDto;
+import org.folio.rspec.domain.dto.SpecificationRuleDtoCollection;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -214,6 +221,8 @@ public abstract class ResourceControllerITBase {
private HashService hashService;
@MockBean
private SrsClient srsClient;
+ @MockBean
+ private SpecClient specClient;
@BeforeEach
public void beforeEach() {
@@ -256,6 +265,10 @@ void createInstanceWithWorkRef_shouldSaveEntityCorrectly() throws Exception {
@Test
void createInstanceWithWorkRef_shouldReturn400_ifLccnIsInvalid() throws Exception {
// given
+ var specRuleId = randomUUID();
+ when(specClient.getBibMarcSpecs()).thenReturn(ResponseEntity.ok().body(createSpecifications(specRuleId)));
+ when(specClient.getSpecRules(specRuleId)).thenReturn(ResponseEntity.ok().body(createSpecRules()));
+
var work = getSampleWork(null);
setExistingResourcesIds(work);
resourceTestService.saveGraph(work);
@@ -376,6 +389,10 @@ void createWorkWithInstanceRef_shouldSaveEntityCorrectly() throws Exception {
@Test
void update_shouldReturnCorrectlyUpdatedInstanceWithWorkRef_deleteOldOne_sendMessages() throws Exception {
// given
+ var specRuleId = randomUUID();
+ when(specClient.getBibMarcSpecs()).thenReturn(ResponseEntity.ok().body(createSpecifications(specRuleId)));
+ when(specClient.getSpecRules(specRuleId)).thenReturn(ResponseEntity.ok().body(createSpecRules()));
+
var work = getSampleWork(null);
var originalInstance = resourceTestService.saveGraph(getSampleInstanceResource(null, work));
var updateDto = getSampleInstanceDtoMap();
@@ -430,6 +447,10 @@ void update_shouldReturnCorrectlyUpdatedInstanceWithWorkRef_deleteOldOne_sendMes
@Test
void update_shouldReturn400_ifLccnIsInvalid() throws Exception {
// given
+ var specRuleId = randomUUID();
+ when(specClient.getBibMarcSpecs()).thenReturn(ResponseEntity.ok().body(createSpecifications(specRuleId)));
+ when(specClient.getSpecRules(specRuleId)).thenReturn(ResponseEntity.ok().body(createSpecRules()));
+
var updateDto = getSampleInstanceDtoMap();
var instance = (LinkedHashMap) ((LinkedHashMap) updateDto.get("resource")).get(INSTANCE.getUri());
instance.remove("inventoryId");
@@ -2064,6 +2085,21 @@ private ArrayList getStatus(LinkedHashMap instance) {
return (ArrayList) lccn.get(STATUS.getUri());
}
+ private SpecificationDtoCollection createSpecifications(UUID specRuleId) {
+ var specifications = new SpecificationDtoCollection();
+ specifications.setSpecifications(List.of(new SpecificationDto().id(specRuleId)));
+ return specifications;
+ }
+
+ private SpecificationRuleDtoCollection createSpecRules() {
+ var specRules = new SpecificationRuleDtoCollection();
+ var specRule = new SpecificationRuleDto();
+ specRule.setCode(LccnPatternValidator.CODE);
+ specRule.setEnabled(true);
+ specRules.setRules(List.of(specRule));
+ return specRules;
+ }
+
private record LookupResources(
List subjects,
List geographicCoverages,
diff --git a/src/test/java/org/folio/linked/data/validation/dto/LccnPatternValidatorTest.java b/src/test/java/org/folio/linked/data/validation/dto/LccnPatternValidatorTest.java
index 66f03b6e..17714e7c 100644
--- a/src/test/java/org/folio/linked/data/validation/dto/LccnPatternValidatorTest.java
+++ b/src/test/java/org/folio/linked/data/validation/dto/LccnPatternValidatorTest.java
@@ -4,85 +4,105 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.params.provider.Arguments.arguments;
+import static org.mockito.Mockito.doReturn;
import java.util.List;
import java.util.stream.Stream;
import org.folio.linked.data.domain.dto.LccnRequest;
import org.folio.linked.data.domain.dto.Status;
+import org.folio.linked.data.validation.spec.SpecProvider;
+import org.folio.rspec.domain.dto.SpecificationRuleDto;
import org.folio.spring.testing.type.UnitTest;
+import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
@UnitTest
+@ExtendWith(MockitoExtension.class)
class LccnPatternValidatorTest {
- private final LccnPatternValidator validator = new LccnPatternValidator();
+ @Mock
+ private SpecProvider specProvider;
+
+ @InjectMocks
+ private LccnPatternValidator validator;
@ParameterizedTest
- @MethodSource("validLccnProvider")
- void shouldReturnTrue_ifLccnIsValid(String value, String link) {
+ @MethodSource("positiveCaseProvider")
+ void shouldReturnTrue(String value, String link, List specRules) {
// given
var lccnRequest = createLccnRequest(value, link);
+ if (!"http://id.loc.gov/vocabulary/mstatus/cancinv".equals(link)) {
+ doReturn(specRules).when(specProvider).getSpecRules();
+ }
// expect
assertTrue(validator.isValid(lccnRequest, null));
}
- private static Stream validLccnProvider() {
+ private static Stream positiveCaseProvider() {
return Stream.of(
// structure A
- arguments(" 12345678 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("n 12345678 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("nn 12345678 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("nnn12345678 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("nnn12345678 ", null),
- arguments(" nnn123456789 ", "http://id.loc.gov/vocabulary/mstatus/cancinv"),
+ arguments(" 12345678 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("n 12345678 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("nn 12345678 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("nnn12345678 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("nnn12345678 ", null, createSpecRules(true)),
+ arguments(" nnn123456789 ", "http://id.loc.gov/vocabulary/mstatus/cancinv", createSpecRules(true)),
+ arguments(" nnn123456789 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(false)),
+ arguments(" nnn123456789 ", "http://id.loc.gov/vocabulary/mstatus/current", List.of()),
// structure B
- arguments(" 0123456789", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("n 0123456781", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("nn0123456789", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("nn0123456789", null),
- arguments("mmm0123456789", "http://id.loc.gov/vocabulary/mstatus/cancinv"));
+ arguments(" 0123456789", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("n 0123456781", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("nn0123456789", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("nn0123456789", null, createSpecRules(true)),
+ arguments("mmm0123456789", "http://id.loc.gov/vocabulary/mstatus/cancinv", createSpecRules(true)),
+ arguments("mmm0123456789", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(false)),
+ arguments("mmm0123456789", "http://id.loc.gov/vocabulary/mstatus/current", List.of()));
}
@ParameterizedTest
- @MethodSource("invalidLccnProvider")
- void shouldReturnFalse_ifLccnIsInvalid(String value, String link) {
+ @MethodSource("negativeCaseProvider")
+ void shouldReturnFalse(String value, String link, List specRules) {
// given
var lccnRequest = createLccnRequest(value, link);
+ doReturn(specRules).when(specProvider).getSpecRules();
// expect
assertFalse(validator.isValid(lccnRequest, null));
}
- public static Stream invalidLccnProvider() {
+ public static Stream negativeCaseProvider() {
return Stream.of(
// structure A
- arguments(" 12345678 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments(" 12345678 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments(" n 12345678 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments(" n12345678 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments(" 1234567 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments(" 12345678", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments(" nnn123456789 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("nnn123456789 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("nnnn12345678 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("nNn12345678 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("nNn12345678 ", null),
- arguments("n-n12345678 ", "http://id.loc.gov/vocabulary/mstatus/current"),
+ arguments(" 12345678 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments(" 12345678 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments(" n 12345678 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments(" n12345678 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments(" 1234567 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments(" 12345678", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments(" nnn123456789 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("nnn123456789 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("nnnn12345678 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("nNn12345678 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("nNn12345678 ", null, createSpecRules(true)),
+ arguments("n-n12345678 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
// structure B
- arguments(" 0123456789", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments(" m123456789", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments(" m123456789 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments(" mm123456789 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("mm123456789 ", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("mmm0123456789", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("nN0123456789", "http://id.loc.gov/vocabulary/mstatus/current"),
- arguments("nN0123456789", null),
- arguments("n-0123456789", "http://id.loc.gov/vocabulary/mstatus/current"));
+ arguments(" 0123456789", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments(" m123456789", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments(" m123456789 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments(" mm123456789 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("mm123456789 ", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("mmm0123456789", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("nN0123456789", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)),
+ arguments("nN0123456789", null, createSpecRules(true)),
+ arguments("n-0123456789", "http://id.loc.gov/vocabulary/mstatus/current", createSpecRules(true)));
}
private LccnRequest createLccnRequest(String value, String link) {
@@ -90,4 +110,11 @@ private LccnRequest createLccnRequest(String value, String link) {
.value(List.of(value))
.status(link == null ? emptyList() : List.of(new Status().link(List.of(link))));
}
+
+ private static List createSpecRules(boolean enabled) {
+ var specRule = new SpecificationRuleDto();
+ specRule.setCode(LccnPatternValidator.CODE);
+ specRule.setEnabled(enabled);
+ return List.of(specRule);
+ }
}
diff --git a/src/test/java/org/folio/linked/data/validation/spec/impl/SpecProviderImplTest.java b/src/test/java/org/folio/linked/data/validation/spec/impl/SpecProviderImplTest.java
new file mode 100644
index 00000000..72140a69
--- /dev/null
+++ b/src/test/java/org/folio/linked/data/validation/spec/impl/SpecProviderImplTest.java
@@ -0,0 +1,58 @@
+package org.folio.linked.data.validation.spec.impl;
+
+import static java.util.Collections.emptyList;
+import static java.util.UUID.randomUUID;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import feign.FeignException;
+import java.util.List;
+import org.folio.linked.data.client.SpecClient;
+import org.folio.rspec.domain.dto.SpecificationDto;
+import org.folio.rspec.domain.dto.SpecificationDtoCollection;
+import org.folio.rspec.domain.dto.SpecificationRuleDto;
+import org.folio.rspec.domain.dto.SpecificationRuleDtoCollection;
+import org.folio.spring.testing.type.UnitTest;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.http.ResponseEntity;
+
+@UnitTest
+@ExtendWith(MockitoExtension.class)
+class SpecProviderImplTest {
+
+ @Mock
+ private SpecClient client;
+
+ @InjectMocks
+ private SpecProviderImpl specProvider;
+
+ @Test
+ void shouldReturn_specRules_whenSpecificationStorageIsAvailable() {
+ //given
+ var specRuleId = randomUUID();
+ var specifications = new SpecificationDtoCollection();
+ var specRule = new SpecificationRuleDto();
+ var specRules = new SpecificationRuleDtoCollection();
+ specRules.setRules(List.of(specRule));
+ specifications.setSpecifications(List.of(new SpecificationDto().id(specRuleId)));
+ doReturn(ResponseEntity.ok().body(specifications)).when(client).getBibMarcSpecs();
+ doReturn(ResponseEntity.ok().body(specRules)).when(client).getSpecRules(specRuleId);
+
+ //expect
+ assertEquals(List.of(specRule), specProvider.getSpecRules());
+ }
+
+ @Test
+ void shouldReturn_emptyList_whenSpecificationStorageIsNotAvailable() {
+ //given
+ when(client.getBibMarcSpecs()).thenThrow(FeignException.class);
+
+ //expect
+ assertEquals(emptyList(), specProvider.getSpecRules());
+ }
+}