Skip to content

Commit

Permalink
feat(specification-validation) implement Invalid Indicator validation
Browse files Browse the repository at this point in the history
- Add Invalid Indicator Rule validator
- Add tests

Closes: MRSPECS-45
  • Loading branch information
viacheslavpoliakov committed Aug 12, 2024
1 parent 6fed3e5 commit c99a938
Show file tree
Hide file tree
Showing 15 changed files with 377 additions and 40 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.folio.rspec.validation.validator.marc.impl;

import org.folio.rspec.domain.dto.DefinitionType;
import org.folio.rspec.domain.dto.SeverityType;
import org.folio.rspec.domain.dto.SpecificationFieldDto;
import org.folio.rspec.domain.dto.ValidationError;
import org.folio.rspec.i18n.TranslationProvider;
import org.folio.rspec.validation.validator.SpecificationRuleValidator;
import org.folio.rspec.validation.validator.marc.model.MarcField;
import org.folio.rspec.validation.validator.marc.model.MarcIndicator;

abstract class AbstractIndicatorRuleValidator implements SpecificationRuleValidator<MarcField, SpecificationFieldDto> {

private final TranslationProvider translationProvider;

AbstractIndicatorRuleValidator(TranslationProvider translationProvider) {
this.translationProvider = translationProvider;
}

@Override
public DefinitionType definitionType() {
return DefinitionType.INDICATOR;
}

@Override
public SeverityType severity() {
return SeverityType.ERROR;
}

protected ValidationError buildError(MarcIndicator marcIndicator,
SpecificationFieldDto fieldDefinition) {
var message = translationProvider.format(ruleCode());
return ValidationError.builder()
.path(marcIndicator.reference().toString())
.definitionType(definitionType())
.definitionId(fieldDefinition.getId())
.severity(severity())
.ruleCode(ruleCode())
.message(message)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.folio.rspec.validation.validator.marc.impl;

import java.util.List;
import org.folio.rspec.domain.dto.SpecificationFieldDto;
import org.folio.rspec.domain.dto.ValidationError;
import org.folio.rspec.i18n.TranslationProvider;
import org.folio.rspec.validation.validator.SpecificationRuleCode;
import org.folio.rspec.validation.validator.marc.model.MarcDataField;
import org.folio.rspec.validation.validator.marc.model.MarcField;
import org.folio.rspec.validation.validator.marc.model.MarcRuleCode;
import org.folio.rspec.validation.validator.marc.utils.TagsMatcher;

class InvalidIndicatorRuleValidator extends AbstractIndicatorRuleValidator {

InvalidIndicatorRuleValidator(TranslationProvider translationProvider) {
super(translationProvider);
}

@Override
public List<ValidationError> validate(MarcField marcField, SpecificationFieldDto field) {
if (marcField instanceof MarcDataField) {
var indicators = ((MarcDataField) marcField).indicators();
if (indicators != null) {
return indicators.stream()
.filter(marcIndicator -> !TagsMatcher.matchesIndicator(marcIndicator.value().toString()))
.map(marcIndicator -> buildError(marcIndicator, field))
.toList();
}
}
return List.of();
}

@Override
public SpecificationRuleCode supportedRule() {
return MarcRuleCode.INVALID_INDICATOR;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ public MarcRecordRuleValidator(TranslationProvider translationProvider) {
new MarcFieldNonRepeatableRequired1xxFieldRuleValidator(translationProvider)
);
this.fieldValidators = List.of(
new MarcFieldNonRepeatableFieldRuleValidator(translationProvider)
new MarcFieldNonRepeatableFieldRuleValidator(translationProvider),
new InvalidIndicatorRuleValidator(translationProvider)
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@

public enum MarcRuleCode implements SpecificationRuleCode {

INVALID_INDICATOR("invalidIndicator"),
UNDEFINED_FIELD("undefinedField"),
MISSING_FIELD("missingField"),
INVALID_FIELD_TAG("invalidFieldTag"),
NON_REPEATABLE_1XX_FIELD("nonRepeatable1XXField"),
NON_REPEATABLE_REQUIRED_1XX_FIELD("nonRepeatableRequired1XXField"),
NON_REPEATABLE_FIELD("nonRepeatableField");
NON_REPEATABLE_FIELD("nonRepeatableField"),
INVALID_INDICATOR("invalidIndicator");

private final String code;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
public final class TagsMatcher {
public static final Pattern TAG_PATTERN = Pattern.compile("^\\d{3}$");
private static final Pattern TAG_1XX_PATTERN = Pattern.compile("^1\\d{2}$");
private static final Pattern INDICATOR_PATTERN = Pattern.compile("^[#0-9a-z]$");

private TagsMatcher() {
throw new IllegalStateException("Utility class");
Expand All @@ -17,4 +18,8 @@ public static boolean matchesValidTag(String tag) {
public static boolean matches1xx(String tag) {
return tag != null && TAG_1XX_PATTERN.matcher(tag).matches();
}

public static boolean matchesIndicator(String symbol) {
return symbol != null && INDICATOR_PATTERN.matcher(symbol).matches();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
import static org.folio.support.TestDataProvider.getSpecification;
import static org.folio.support.TestDataProvider.getSpecificationWithIndicators;
import static org.folio.support.TestDataProvider.getSpecificationWithTags;

import java.util.stream.Stream;
Expand Down Expand Up @@ -68,6 +69,32 @@ void test1xxMarcRecordValidation(String file, String[] tags, Tuple[] expected) {
.containsExactlyInAnyOrder(expected);
}

@Test
void testInvalidIndicatorValidation() {
var marc4jRecord = TestRecordProvider.getMarc4jRecord("testdata/indicators/marc-indicators-record.json");

var validationErrors = validator.validate(marc4jRecord, getSpecificationWithIndicators());

assertThat(validationErrors)
.hasSize(12)
.extracting(ValidationError::getPath, ValidationError::getRuleCode)
.containsExactlyInAnyOrder(
tuple("035[0]^2", MarcRuleCode.INVALID_INDICATOR.getCode()),
tuple("035[1]^1", MarcRuleCode.INVALID_INDICATOR.getCode()),
tuple("035[2]^1", MarcRuleCode.INVALID_INDICATOR.getCode()),
tuple("035[2]^2", MarcRuleCode.INVALID_INDICATOR.getCode()),
tuple("047[0]^2", MarcRuleCode.INVALID_INDICATOR.getCode()),
tuple("047[1]^1", MarcRuleCode.INVALID_INDICATOR.getCode()),
tuple("047[2]^1", MarcRuleCode.INVALID_INDICATOR.getCode()),
tuple("047[2]^2", MarcRuleCode.INVALID_INDICATOR.getCode()),
tuple("245[0]^1", MarcRuleCode.INVALID_INDICATOR.getCode()),
tuple("245[0]^2", MarcRuleCode.INVALID_INDICATOR.getCode()),
tuple("650[0]^1", MarcRuleCode.INVALID_INDICATOR.getCode()),
tuple("650[0]^2", MarcRuleCode.INVALID_INDICATOR.getCode())

);
}

private static Stream<Arguments> provide1xxArguments() {
return Stream.of(
// Multiple 1xx fields with same undefined tag
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,26 +43,26 @@ void testConvert_WhenCalled_ShouldReturnMarcRecord() {
.extracting(dataField -> dataField.reference().toString(), controlFieldExtractor(), dataFieldExtractor()
)
.containsExactlyInAnyOrder(
tuple("035[0]", "035[0]^1= ;035[0]^2= ", "035[0]$a[0]=(OCoLC)63611770"),
tuple("650[0]", "650[0]^1= ;650[0]^2=0", "650[0]$a[0]=Instrumental music"),
tuple("650[1]", "650[1]^1= ;650[1]^2=7",
tuple("035[0]", "035[0]^1=#;035[0]^2=#", "035[0]$a[0]=(OCoLC)63611770"),
tuple("650[0]", "650[0]^1=#;650[0]^2=0", "650[0]$a[0]=Instrumental music"),
tuple("650[1]", "650[1]^1=#;650[1]^2=7",
"650[1]$0[0]=(OCoLC)fst00974414 650[1]$a[0]=Instrumental music 650[1]$2[0]=fast"),
tuple("650[2]", "650[2]^1= ;650[2]^2=7",
tuple("650[2]", "650[2]^1=#;650[2]^2=7",
"650[2]$0[0]=(OCoLC)fst01168379 650[2]$a[0]=Vocal music 650[2]$2[0]=fast"),
tuple("100[0]", "100[0]^1=/;100[0]^2=/",
tuple("100[0]", "100[0]^1=#;100[0]^2=#",
"100[0]$0[0]=12345 100[0]$a[0]=Mozart, Wolfgang Amadeus, "
+ "100[0]$d[0]=1756-1791. 100[0]$9[0]=b9a5f035-de63-4e2c-92c2-07240c88b817"),
tuple("035[1]", "035[1]^1= ;035[1]^2= ", "035[1]$a[0]=393893"),
tuple("035[1]", "035[1]^1=#;035[1]^2=#", "035[1]$a[0]=393893"),
tuple("245[0]", "245[0]^1=1;245[0]^2=0",
"245[0]$a[0]=Neue Ausgabe samtlicher Werke, "
+ "245[0]$b[0]=in Verbindung mit den Mozartstadten, Augsburg, Salzburg und Wien. "
+ "245[0]$c[0]=Hrsg. von der Internationalen Stiftung Mozarteum, Salzburg."),
tuple("047[0]", "047[0]^1= ;047[0]^2= ",
tuple("047[0]", "047[0]^1=#;047[0]^2=#",
"047[0]$a[0]=cn 047[0]$a[1]=ct 047[0]$a[2]=co 047[0]$a[3]=df 047[0]$a[4]=dv "
+ "047[0]$a[5]=ft 047[0]$a[6]=fg 047[0]$a[7]=ms 047[0]$a[8]=mi 047[0]$a[9]=nc "
+ "047[0]$a[10]=op 047[0]$a[11]=ov 047[0]$a[12]=rq 047[0]$a[13]=sn 047[0]$a[14]=su "
+ "047[0]$a[15]=sy 047[0]$a[16]=vr 047[0]$a[17]=zz"),
tuple("010[0]", "010[0]^1= ;010[0]^2= ", "010[0]$a[0]= 2001000234 010[0]$z[0]=0001 010[0]$z[1]=0002")
tuple("010[0]", "010[0]^1=#;010[0]^2=#", "010[0]$a[0]= 2001000234 010[0]$z[0]=0001 010[0]$z[1]=0002")
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.folio.rspec.validation.validator.marc.impl;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.when;

import java.util.List;
import java.util.UUID;
import org.folio.rspec.domain.dto.SpecificationFieldDto;
import org.folio.rspec.domain.dto.ValidationError;
import org.folio.rspec.i18n.TranslationProvider;
import org.folio.rspec.validation.validator.marc.model.MarcDataField;
import org.folio.rspec.validation.validator.marc.model.MarcIndicator;
import org.folio.rspec.validation.validator.marc.model.Reference;
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;

@UnitTest
@ExtendWith(MockitoExtension.class)
class InvalidIndicatorRuleValidatorTest {

@Mock
private TranslationProvider translationProvider;

@InjectMocks
private InvalidIndicatorRuleValidator validator;

@Test
void validate_whenInvalidIndicators_shouldReturnValidationError() {
var fieldDefinition = new SpecificationFieldDto().id(UUID.randomUUID()).repeatable(false);
var marcField = new MarcDataField(
Reference.forTag("tag", 1),
getIndicators('X', 'x'),
null);

when(translationProvider.format(validator.supportedRule().getCode())).thenReturn("message");

var errors = validator.validate(marcField, fieldDefinition);

assertEquals(1, errors.size());
assertEquals(validator.definitionType(), errors.get(0).getDefinitionType());
assertEquals(validator.severity(), errors.get(0).getSeverity());
assertEquals(validator.supportedRule().getCode(), errors.get(0).getRuleCode());
assertEquals("message", errors.get(0).getMessage());
}

@Test
void validate_whenValidIndicators_shouldReturnEmptyList() {
var fieldDefinition = new SpecificationFieldDto().id(UUID.randomUUID()).repeatable(false);
var marcField = new MarcDataField(
Reference.forTag("tag", 0),
getIndicators('#', '#'),
null);

List<ValidationError> errors = validator.validate(marcField, fieldDefinition);

assertTrue(errors.isEmpty());
}

private static List<MarcIndicator> getIndicators(char ind1, char ind2) {
return List.of(
new MarcIndicator(Reference.forIndicator(Reference.forTag("ind"), 0), ind1),
new MarcIndicator(Reference.forIndicator(Reference.forTag("ind"), 1), ind2)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import java.util.UUID;
import org.folio.rspec.domain.dto.Family;
import org.folio.rspec.domain.dto.FamilyProfile;
import org.folio.rspec.domain.dto.FieldIndicatorDto;
import org.folio.rspec.domain.dto.IndicatorCodeDto;
import org.folio.rspec.domain.dto.SpecificationDto;
import org.folio.rspec.domain.dto.SpecificationFieldDto;
import org.folio.rspec.domain.dto.SpecificationRuleDto;
Expand All @@ -31,6 +33,15 @@ public static SpecificationDto getSpecificationWithTags(String... tags) {
.fields(fieldDefinitionsTags(tags));
}

public static SpecificationDto getSpecificationWithIndicators() {
return new SpecificationDto()
.id(UUID.randomUUID())
.family(Family.MARC)
.profile(FamilyProfile.BIBLIOGRAPHIC)
.rules(allEnabledRules())
.fields(indicatorsFieldDefinitions());
}

private static List<SpecificationFieldDto> commonFieldDefinitions() {
List<SpecificationFieldDto> fields = new ArrayList<>();
fields.add(requiredNonRepeatableField("000"));
Expand All @@ -43,6 +54,18 @@ private static List<SpecificationFieldDto> commonFieldDefinitions() {
return fields;
}

private static List<SpecificationFieldDto> indicatorsFieldDefinitions() {
List<SpecificationFieldDto> fields = new ArrayList<>();
fields.add(requiredNonRepeatableField("000"));
fields.add(defaultFieldWithIndicator("010"));
fields.add(defaultFieldWithIndicator("035"));
fields.add(defaultFieldWithIndicator("047"));
fields.add(defaultFieldWithIndicator("100"));
fields.add(defaultFieldWithIndicator("245"));
fields.add(defaultFieldWithIndicator("650"));
return fields;
}

private static List<SpecificationFieldDto> fieldDefinitions() {
List<SpecificationFieldDto> fields = commonFieldDefinitions();
fields.add(defaultField("035"));
Expand Down Expand Up @@ -75,6 +98,10 @@ private static SpecificationFieldDto defaultField(String tag) {
return fieldDefinition(tag, false, false, true);
}

private static SpecificationFieldDto defaultFieldWithIndicator(String tag) {
return defaultField(tag).indicators(List.of(getIndicator(), getIndicator()));
}

private static SpecificationFieldDto fieldDefinition(String tag, boolean required, boolean deprecated,
boolean repeatable) {
return new SpecificationFieldDto()
Expand All @@ -92,4 +119,8 @@ private static List<SpecificationRuleDto> allEnabledRules() {
private static SpecificationRuleDto enabledRule(MarcRuleCode ruleCode) {
return new SpecificationRuleDto().id(UUID.randomUUID()).code(ruleCode.getCode()).enabled(true);
}

private static FieldIndicatorDto getIndicator() {
return new FieldIndicatorDto().id(UUID.randomUUID()).addCodesItem(new IndicatorCodeDto().code("code"));
}
}
Loading

0 comments on commit c99a938

Please sign in to comment.