This commit is contained in:
Grahame Grieve 2024-08-03 23:38:47 +08:00
commit af5eaafd34
7 changed files with 584 additions and 375 deletions

View File

@ -1286,7 +1286,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
} }
if (res != null) { if (res != null) {
updateUnsupportedCodeSystems(res, code, getCodeKey(code)); updateUnsupportedCodeSystems(res, code, getCodeKey(code));
return res; return new ValidationResult(res);
} }
List<OperationOutcomeIssueComponent> issues = new ArrayList<>(); List<OperationOutcomeIssueComponent> issues = new ArrayList<>();
@ -1566,7 +1566,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
if (cachingAllowed) { if (cachingAllowed) {
res = txCache.getValidation(cacheToken); res = txCache.getValidation(cacheToken);
if (res != null) { if (res != null) {
return res; return new ValidationResult(res);
} }
} }
for (Coding c : code.getCoding()) { for (Coding c : code.getCoding()) {

View File

@ -1,9 +1,6 @@
package org.hl7.fhir.r5.terminologies.utilities; package org.hl7.fhir.r5.terminologies.utilities;
import java.util.ArrayList; import java.util.*;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.hl7.fhir.r5.model.CodeableConcept; import org.hl7.fhir.r5.model.CodeableConcept;
import org.hl7.fhir.r5.model.Coding; import org.hl7.fhir.r5.model.Coding;
@ -35,6 +32,30 @@ public class ValidationResult {
+ errorClass + ", txLink=" + txLink + "]"; + errorClass + ", txLink=" + txLink + "]";
} }
public ValidationResult(ValidationResult validationResult) {
this.definition = validationResult.definition == null ? null : validationResult.definition.copy();
this.preferredDisplay = validationResult.preferredDisplay;
this.system = validationResult.system;
this.version = validationResult.version;
this.severity = validationResult.severity;
if (validationResult.messages != null) {
this.messages.addAll(validationResult.messages);
}
this.errorClass = validationResult.errorClass;
this.txLink = validationResult.txLink;
this.diagnostics = validationResult.diagnostics;
if (validationResult.issues != null) {
for (OperationOutcomeIssueComponent issue : validationResult.issues) {
this.issues.add(issue.copy());
}
}
this.codeableConcept = validationResult.codeableConcept == null ? null : validationResult.codeableConcept.copy();
this.unknownSystems = validationResult.unknownSystems == null ? null : new HashSet<>(validationResult.unknownSystems);
this.inactive = validationResult.inactive;
this.status = validationResult.status;
this.server = validationResult.server;
}
public ValidationResult(IssueSeverity severity, String message, List<OperationOutcomeIssueComponent> issues) { public ValidationResult(IssueSeverity severity, String message, List<OperationOutcomeIssueComponent> issues) {
this.severity = severity; this.severity = severity;
if (message != null) { if (message != null) {
@ -334,4 +355,57 @@ public class ValidationResult {
this.server = server; this.server = server;
} }
public boolean equals(Object otherObject) {
if (!(otherObject instanceof ValidationResult)) {
return false;
}
ValidationResult other = (ValidationResult) otherObject;
if (!Objects.equals(this.system, other.system)) {
return false;
}
if (!Objects.equals(this.version, other.version)) {
return false;
}
if (!Objects.equals(this.preferredDisplay, other.preferredDisplay)) {
return false;
}
if (!Objects.equals(this.severity, other.severity)) {
return false;
}
if (!Objects.equals(this.definition, other.definition)) {
return false;
}
if (!Objects.equals(this.messages, other.messages)) {
return false;
}
if (!Objects.equals(this.errorClass, other.errorClass)) {
return false;
}
if (!Objects.equals(this.txLink, other.txLink)) {
return false;
}
if (!Objects.equals(this.diagnostics, other.diagnostics)) {
return false;
}
if (!Objects.equals(this.issues, other.issues)) {
return false;
}
if (!Objects.equals(this.codeableConcept, other.codeableConcept)) {
return false;
}
if (!Objects.equals(this.unknownSystems, other.unknownSystems)) {
return false;
}
if (this.inactive != other.inactive) {
return false;
}
if (!Objects.equals(this.status, other.status)) {
return false;
}
if (!Objects.equals(this.server, other.server)) {
return false;
}
return true;
}
} }

View File

@ -1,31 +1,224 @@
package org.hl7.fhir.r5.context; package org.hl7.fhir.r5.context;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.model.CanonicalResource; import org.hl7.fhir.r5.model.*;
import org.hl7.fhir.r5.model.PackageInformation; import org.hl7.fhir.r5.terminologies.client.ITerminologyClient;
import org.hl7.fhir.r5.model.Parameters; import org.hl7.fhir.r5.terminologies.client.TerminologyClientContext;
import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpander;
import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache;
import org.hl7.fhir.r5.terminologies.utilities.ValidationResult;
import org.hl7.fhir.r5.terminologies.validation.ValueSetValidator;
import org.hl7.fhir.r5.utils.validation.IResourceValidator; import org.hl7.fhir.r5.utils.validation.IResourceValidator;
import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier;
import org.hl7.fhir.utilities.FhirPublication; import org.hl7.fhir.utilities.FhirPublication;
import org.hl7.fhir.utilities.ToolingClientLogger;
import org.hl7.fhir.utilities.npm.BasePackageCacheManager; import org.hl7.fhir.utilities.npm.BasePackageCacheManager;
import org.hl7.fhir.utilities.npm.NpmPackage; import org.hl7.fhir.utilities.npm.NpmPackage;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationOptions; import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import net.sourceforge.plantuml.tim.stdlib.GetVariableValue; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.Arrays;
import java.util.List; import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.*;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@ExtendWith(MockitoExtension.class)
public class BaseWorkerContextTests { public class BaseWorkerContextTests {
private static final String DUMMY_URL = "dummyUrl";
@Spy
BaseWorkerContext context = new BaseWorkerContext(){
@Override
public String getVersion() {
return "4.0.1";
}
@Override
public IResourceValidator newValidator() throws FHIRException {
return null;
}
@Override
public <T extends Resource> T fetchResourceRaw(Class<T> class_, String uri) {
return null;
}
@Override
public void cachePackage(PackageInformation packageInfo) {
}
@Override
public List<String> getResourceNames() {
return List.of();
}
@Override
public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader) throws FileNotFoundException, IOException, FHIRException {
return 0;
}
@Override
public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader, List<String> types) throws FileNotFoundException, IOException, FHIRException {
return 0;
}
@Override
public int loadFromPackageAndDependencies(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm) throws FileNotFoundException, IOException, FHIRException {
return 0;
}
@Override
public boolean hasPackage(String id, String ver) {
return false;
}
@Override
public boolean hasPackage(PackageInformation pack) {
return false;
}
@Override
public PackageInformation getPackage(String id, String ver) {
return null;
}
@Override
public String getSpecUrl() {
return "";
}
};
@Mock
TerminologyCache terminologyCache;
@Mock
ToolingClientLogger txLog;
@Mock
ITerminologyClient terminologyClient;
@Mock
TerminologyCache.CacheToken cacheToken;
ValidationResult cachedValidationResult = new ValidationResult(ValidationMessage.IssueSeverity.INFORMATION, "dummyMessageForCached", List.of());
ValidationResult createdValidationResult = new ValidationResult(ValidationMessage.IssueSeverity.INFORMATION, "dummyMessageForCreated", List.of());
@Mock
ValueSetExpansionOutcome expectedExpansionResult;
@Mock
ValueSetValidator valueSetCheckerSimple;
@Mock
ValueSetExpander valueSetExpanderSimple;
@Mock
Parameters pIn;
@Mock
Parameters expParameters;
public static final TerminologyCapabilities terminologyCapabilities = new TerminologyCapabilities();
static { terminologyCapabilities.getExpansion().setParameter(Arrays.asList());}
public static final CapabilityStatement.CapabilityStatementSoftwareComponent software = new CapabilityStatement.CapabilityStatementSoftwareComponent();
static { software.setVersion("dummyVersion"); }
public static final CapabilityStatement capabilitiesStatement = new CapabilityStatement();
static { capabilitiesStatement.setSoftware(software);}
private final static Parameters pInWithDependentResources = new Parameters();
static {
pInWithDependentResources.addParameter("includeDefinition", false);
pInWithDependentResources.addParameter("excludeNester", false);
pInWithDependentResources.addParameter("incomplete-ok", true);
}
public static class ValueSetMatcher implements ArgumentMatcher<ValueSet> {
private ValueSet left;
ValueSetMatcher(ValueSet left) {
this.left = left;
}
@Override
public boolean matches(ValueSet right) {
return left.getStatus().equals(right.getStatus())
&& left.getCompose().equalsDeep(right.getCompose());
}
}
public static class CodingMatcher implements ArgumentMatcher<Coding> {
final private Coding left;
CodingMatcher(Coding left) { this.left = left; }
public boolean matches(Coding right) {
return left.equalsShallow(right);
}
}
public static class ParametersMatcher implements ArgumentMatcher<Parameters> {
final private Parameters left;
ParametersMatcher(Parameters left) {
this.left = left;
}
@Override
public boolean matches(Parameters right) {
return left.equalsShallow(right);
}
}
public static class TerminologyClientContextMatcher implements ArgumentMatcher<TerminologyClientContext> {
final private TerminologyClientContext left;
TerminologyClientContextMatcher(TerminologyClientContext left) {
this.left = left;
}
@Override
public boolean matches(TerminologyClientContext argument) {
return left.getAddress().equals(argument.getAddress());
}
}
@BeforeEach
public void beforeEach() {
Mockito.doReturn(DUMMY_URL).when(terminologyClient).getAddress();
context.initTxCache(terminologyCache);
context.expParameters = expParameters;
context.terminologyClientManager.setMasterClient(terminologyClient, false);
context.txLog = txLog;
}
public BaseWorkerContextTests() throws IOException {
}
private BaseWorkerContext getBaseWorkerContext() throws IOException { private BaseWorkerContext getBaseWorkerContext() throws IOException {
BaseWorkerContext baseWorkerContext = new BaseWorkerContext() { BaseWorkerContext baseWorkerContext = new BaseWorkerContext() {
@Override @Override
@ -123,4 +316,273 @@ public class BaseWorkerContextTests {
baseWorkerContext.addServerValidationParameters(baseWorkerContext.getTxClientManager().getMaster(), new ValueSet(), pin, new ValidationOptions(FhirPublication.fromCode(baseWorkerContext.getVersion()))); baseWorkerContext.addServerValidationParameters(baseWorkerContext.getTxClientManager().getMaster(), new ValueSet(), pin, new ValidationOptions(FhirPublication.fromCode(baseWorkerContext.getVersion())));
assertNull(pin.getParameter("mode")); assertNull(pin.getParameter("mode"));
} }
@Test
public void testValidateCodingWithCache() throws IOException {
ValidationOptions validationOptions = new ValidationOptions(FhirPublication.R5).withGuessSystem().withVersionFlexible(false);
ValueSet valueSet = new ValueSet();
Coding coding = new Coding();
Mockito.doReturn(cacheToken).when(terminologyCache).generateValidationToken(validationOptions, coding, valueSet, expParameters);
Mockito.doReturn(cachedValidationResult).when(terminologyCache).getValidation(cacheToken);
ValidationContextCarrier ctxt = mock(ValidationContextCarrier.class);
ValidationResult actualValidationResult = context.validateCode(validationOptions, coding, valueSet, ctxt);
assertNotSame(cachedValidationResult, actualValidationResult);
assertEquals(cachedValidationResult, actualValidationResult);
Mockito.verify(valueSetCheckerSimple, times(0)).validateCode("Coding", coding);
Mockito.verify(terminologyCache).getValidation(cacheToken);
Mockito.verify(terminologyCache, times(0)).cacheValidation(any(), any(), anyBoolean());
}
@Test
public void testValidateCodingWithValueSetChecker() throws IOException {
ValidationOptions validationOptions = new ValidationOptions(FhirPublication.R5).withGuessSystem().withVersionFlexible(false);
ValueSet valueSet = new ValueSet();
Coding coding = new Coding();
Mockito.doReturn(cacheToken).when(terminologyCache).generateValidationToken(validationOptions, coding, valueSet, expParameters);
Mockito.doReturn(valueSetCheckerSimple).when(context).constructValueSetCheckerSimple(any(), any(), any());
Mockito.doReturn(createdValidationResult).when(valueSetCheckerSimple).validateCode(eq("Coding"), any(Coding.class));
ValidationContextCarrier ctxt = mock(ValidationContextCarrier.class);
ValidationResult actualValidationResult = context.validateCode(validationOptions, coding, valueSet, ctxt);
assertSame(createdValidationResult, actualValidationResult);
Mockito.verify(valueSetCheckerSimple).validateCode(eq("Coding"), argThat(new CodingMatcher(coding)));
Mockito.verify(terminologyCache).getValidation(cacheToken);
Mockito.verify(terminologyCache).cacheValidation(eq(cacheToken), same(createdValidationResult),eq(false));
}
@Test
public void testValidateCodingWithServer() throws IOException {
ValidationOptions validationOptions = new ValidationOptions(FhirPublication.R5).withGuessSystem().withVersionFlexible(false).withNoClient();
ValueSet valueSet = new ValueSet();
Coding coding = new Coding();
Mockito.doReturn(cacheToken).when(terminologyCache).generateValidationToken(validationOptions, coding, valueSet, expParameters);
Mockito.doReturn(pIn).when(context).constructParameters(validationOptions, coding);
TerminologyClientContext terminologyClientContext = context.getTxClientManager().getMaster();
Mockito.doReturn(createdValidationResult).when(context).validateOnServer(terminologyClientContext, valueSet, pIn, validationOptions);
ValidationContextCarrier ctxt = mock(ValidationContextCarrier.class);
ValidationResult actualValidationResult = context.validateCode(validationOptions, coding, valueSet, ctxt);
assertSame(createdValidationResult, actualValidationResult);
Mockito.verify(valueSetCheckerSimple, times(0)).validateCode("Coding", coding);
Mockito.verify(terminologyCache).getValidation(cacheToken);
Mockito.verify(terminologyCache).cacheValidation(eq(cacheToken), same(createdValidationResult),eq(true));
}
@Test
public void testValidateCodableConceptWithCache() throws IOException {
CodeableConcept codeableConcept = new CodeableConcept();
ValueSet valueSet = new ValueSet();
Mockito.doReturn(cacheToken).when(terminologyCache).generateValidationToken(CacheTestUtils.validationOptions, codeableConcept, valueSet, expParameters);
Mockito.doReturn(cachedValidationResult).when(terminologyCache).getValidation(cacheToken);
ValidationResult actualValidationResult = context.validateCode(CacheTestUtils.validationOptions, codeableConcept, valueSet);
assertNotSame(cachedValidationResult, actualValidationResult);
assertEquals(cachedValidationResult, actualValidationResult);
Mockito.verify(valueSetCheckerSimple, times(0)).validateCode("CodeableConcept", codeableConcept);
Mockito.verify(terminologyCache).getValidation(cacheToken);
Mockito.verify(terminologyCache, times(0)).cacheValidation(any(), any(), anyBoolean());
}
@Test
public void testValidateCodableConceptWithValueSetChecker() throws IOException {
Mockito.doReturn(valueSetCheckerSimple).when(context).constructValueSetCheckerSimple(any(), any());
Mockito.doReturn(createdValidationResult).when(valueSetCheckerSimple).validateCode(eq("CodeableConcept"),any(CodeableConcept.class));
CodeableConcept codeableConcept = new CodeableConcept();
ValueSet valueSet = new ValueSet();
Mockito.doReturn(cacheToken).when(terminologyCache).generateValidationToken(CacheTestUtils.validationOptions, codeableConcept, valueSet, expParameters);
ValidationResult validationResultB = context.validateCode(CacheTestUtils.validationOptions, codeableConcept, valueSet);
assertSame(createdValidationResult, validationResultB);
Mockito.verify(valueSetCheckerSimple).validateCode("CodeableConcept", codeableConcept);
Mockito.verify(terminologyCache).cacheValidation(eq(cacheToken), same(createdValidationResult), eq(false));
Mockito.verify(context, times(0)).validateOnServer(any(), any(), any(), any());
}
@Test
public void testValidateCodableConceptWithServer() throws IOException {
CodeableConcept codeableConcept = new CodeableConcept();
ValueSet valueSet = new ValueSet();
ValidationOptions validationOptions = CacheTestUtils.validationOptions.withNoClient();
Mockito.doReturn(pIn).when(context).constructParameters(validationOptions, codeableConcept);
TerminologyClientContext terminologyClientContext = context.getTxClientManager().getMaster();
Mockito.doReturn(createdValidationResult).when(context).validateOnServer(terminologyClientContext, valueSet, pIn, validationOptions);
Mockito.doReturn(cacheToken).when(terminologyCache).generateValidationToken(validationOptions, codeableConcept, valueSet, expParameters);
ValidationResult validationResultB = context.validateCode(validationOptions, codeableConcept, valueSet);
assertSame(createdValidationResult, validationResultB);
Mockito.verify(valueSetCheckerSimple, times(0)).validateCode("CodeableConcept", codeableConcept);
Mockito.verify(terminologyCache).cacheValidation(eq(cacheToken), same(createdValidationResult), eq(true));
Mockito.verify(context).validateOnServer(terminologyClientContext, valueSet, pIn, validationOptions);
}
@Test
public void testExpandValueSetWithCache() throws IOException {
ValueSet.ConceptSetComponent inc = new ValueSet.ConceptSetComponent();
ValueSet vs = new ValueSet();
vs.setStatus(Enumerations.PublicationStatus.ACTIVE);
vs.setCompose(new ValueSet.ValueSetComposeComponent());
vs.getCompose().setInactive(true);
vs.getCompose().getInclude().add(inc);
Mockito.doReturn(cacheToken).when(terminologyCache).generateExpandToken(argThat(new ValueSetMatcher(vs)),eq(true));
Mockito.doReturn(expectedExpansionResult).when(terminologyCache).getExpansion(cacheToken);
ValueSetExpansionOutcome actualExpansionResult = context.expandVS(inc, true, false);
assertSame(expectedExpansionResult, actualExpansionResult);
Mockito.verify(terminologyCache).getExpansion(cacheToken);
Mockito.verify(terminologyCache, times(0)).cacheExpansion(any(), any(), anyBoolean());
Mockito.verify(terminologyClient, times(0)).expandValueset(any(), any());
}
@Test
public void testExpandValueSetWithClient() throws IOException {
ValueSet.ConceptSetComponent inc = new ValueSet.ConceptSetComponent();
ValueSet vs = new ValueSet();
vs.setStatus(Enumerations.PublicationStatus.ACTIVE);
vs.setCompose(new ValueSet.ValueSetComposeComponent());
vs.getCompose().setInactive(true);
vs.getCompose().getInclude().add(inc);
Mockito.doReturn(cacheToken).when(terminologyCache).generateExpandToken(argThat(new ValueSetMatcher(vs)),eq(true));
TerminologyClientContext terminologyClientContext = context.getTxClientManager().getMaster();
Mockito.doReturn(expParameters).when(context).constructParameters(argThat(new TerminologyClientContextMatcher(terminologyClientContext)),argThat(new ValueSetMatcher(vs)), eq(true));
ValueSet expectedValueSet = new ValueSet();
Mockito.doReturn(expectedValueSet).when(terminologyClient).expandValueset(argThat(new ValueSetMatcher(vs)),
argThat(new ParametersMatcher(pInWithDependentResources)));
ValueSetExpansionOutcome actualExpansionResult = context.expandVS(inc, true, false);
assertEquals(expectedValueSet, actualExpansionResult.getValueset());
Mockito.verify(terminologyCache).getExpansion(cacheToken);
Mockito.verify(terminologyCache).cacheExpansion(cacheToken, actualExpansionResult,true);
}
@Test
public void testExpandValueSet4ArgsWithCache() throws IOException {
ValueSet vs = new ValueSet();
vs.setUrl(DUMMY_URL);
Mockito.doReturn(cacheToken).when(terminologyCache).generateExpandToken(vs,true);
Mockito.doReturn(expectedExpansionResult).when(terminologyCache).getExpansion(cacheToken);
Parameters pIn = new Parameters();
ValueSetExpansionOutcome actualExpansionResult = context.expandVS(vs, true, true, true, pIn);
assertEquals(expectedExpansionResult, actualExpansionResult);
Mockito.verify(terminologyCache).getExpansion(cacheToken);
Mockito.verify(terminologyCache, times(0)).cacheExpansion(any(), any(), anyBoolean());
Mockito.verify(terminologyClient, times(0)).expandValueset(any(), any());
}
private static class ValidationOptionsFhirPublicationMatcher implements ArgumentMatcher<ValidationOptions> {
final FhirPublication fhirPublication;
ValidationOptionsFhirPublicationMatcher(FhirPublication fhirPublication) {
this.fhirPublication = fhirPublication;
}
@Override
public boolean matches(ValidationOptions argument) {
return fhirPublication.toCode().equals(argument.getFhirVersion().toCode());
}
}
@Test
public void testExpandValueSet4ArgsWithValueSetExpanderSimple() throws IOException {
ValueSet vs = new ValueSet();
vs.setUrl(DUMMY_URL);
Mockito.doReturn(cacheToken).when(terminologyCache).generateExpandToken(vs,true);
Parameters pIn = new Parameters();
Mockito.doReturn(vs).when(expectedExpansionResult).getValueset();
Mockito.doReturn(expectedExpansionResult).when(valueSetExpanderSimple).expand(eq(vs),
argThat(new ParametersMatcher(pInWithDependentResources)));
Mockito.doReturn(valueSetExpanderSimple).when(context).constructValueSetExpanderSimple(argThat(new ValidationOptionsFhirPublicationMatcher(vs.getFHIRPublicationVersion())));
ValueSetExpansionOutcome actualExpansionResult = context.expandVS(vs, true, true, true, pIn);
assertEquals(expectedExpansionResult, actualExpansionResult);
Mockito.verify(terminologyCache).getExpansion(cacheToken);
Mockito.verify(terminologyCache).cacheExpansion(cacheToken, actualExpansionResult, false);
Mockito.verify(terminologyClient, times(0)).expandValueset(any(), any());
}
@Test
public void testExpandValueSet4ArgsWithClient() throws IOException {
ValueSet vs = new ValueSet();
vs.setUrl(DUMMY_URL);
Mockito.doReturn(cacheToken).when(terminologyCache).generateExpandToken(vs,true);
Parameters pIn = new Parameters();
ValueSet expectedValueSet = new ValueSet();
expectedValueSet.setUrl("dummyUrl2");
Mockito.doReturn(expectedExpansionResult).when(valueSetExpanderSimple).expand(eq(vs),
argThat(new ParametersMatcher(pInWithDependentResources)));
Mockito.doReturn(valueSetExpanderSimple).when(context).constructValueSetExpanderSimple(argThat(new ValidationOptionsFhirPublicationMatcher(vs.getFHIRPublicationVersion())));
Mockito.doReturn(expectedValueSet).when(terminologyClient).expandValueset(eq(vs), argThat(new ParametersMatcher(pInWithDependentResources)));
ValueSetExpansionOutcome actualExpansionResult = context.expandVS(vs, true, true, true, pIn, false);
assertEquals(expectedValueSet, actualExpansionResult.getValueset());
Mockito.verify(terminologyCache).getExpansion(cacheToken);
Mockito.verify(terminologyCache).cacheExpansion(cacheToken, actualExpansionResult, true);
}
} }

View File

@ -31,6 +31,7 @@ import org.hl7.fhir.r5.terminologies.validation.ValueSetValidator;
import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier; import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier;
import org.hl7.fhir.utilities.FhirPublication; import org.hl7.fhir.utilities.FhirPublication;
import org.hl7.fhir.utilities.ToolingClientLogger; import org.hl7.fhir.utilities.ToolingClientLogger;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationOptions; import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -61,27 +62,6 @@ public class SimpleWorkerContextTests {
@Mock @Mock
ITerminologyClient terminologyClient; ITerminologyClient terminologyClient;
@Mock
TerminologyCache.CacheToken cacheToken;
@Mock
ValidationResult expectedValidationResult;
@Mock
ValueSetExpansionOutcome expectedExpansionResult;
@Mock
ValueSetValidator valueSetCheckerSimple;
@Mock
ValueSetExpander valueSetExpanderSimple;
@Mock
Parameters pIn;
@Mock
Parameters expParameters;
public static final TerminologyCapabilities terminologyCapabilities = new TerminologyCapabilities(); public static final TerminologyCapabilities terminologyCapabilities = new TerminologyCapabilities();
static { terminologyCapabilities.getExpansion().setParameter(Arrays.asList());} static { terminologyCapabilities.getExpansion().setParameter(Arrays.asList());}
@ -93,342 +73,11 @@ public class SimpleWorkerContextTests {
@BeforeEach @BeforeEach
public void beforeEach() { public void beforeEach() {
Mockito.doReturn(DUMMY_URL).when(terminologyClient).getAddress(); Mockito.doReturn(DUMMY_URL).when(terminologyClient).getAddress();
context.initTxCache(terminologyCache); context.initTxCache(terminologyCache);
context.expParameters = expParameters;
context.terminologyClientManager.setMasterClient(terminologyClient, false); context.terminologyClientManager.setMasterClient(terminologyClient, false);
context.txLog = txLog;
} }
private final static Parameters pInWithDependentResources = new Parameters();
static {
pInWithDependentResources.addParameter("includeDefinition", false);
pInWithDependentResources.addParameter("excludeNester", false);
pInWithDependentResources.addParameter("incomplete-ok", true);
}
public class ValueSetMatcher implements ArgumentMatcher<ValueSet> {
private ValueSet left;
ValueSetMatcher(ValueSet left) {
this.left = left;
}
@Override
public boolean matches(ValueSet right) {
return left.getStatus().equals(right.getStatus())
&& left.getCompose().equalsDeep(right.getCompose());
}
}
public class CodingMatcher implements ArgumentMatcher<Coding> {
final private Coding left;
CodingMatcher(Coding left) { this.left = left; }
public boolean matches(Coding right) {
return left.equalsShallow(right);
}
}
public class ParametersMatcher implements ArgumentMatcher<Parameters> {
final private Parameters left;
ParametersMatcher(Parameters left) {
this.left = left;
}
@Override
public boolean matches(Parameters right) {
return left.equalsShallow(right);
}
}
public class TerminologyClientContextMatcher implements ArgumentMatcher<TerminologyClientContext> {
final private TerminologyClientContext left;
TerminologyClientContextMatcher(TerminologyClientContext left) {
this.left = left;
}
@Override
public boolean matches(TerminologyClientContext argument) {
return left.getAddress().equals(argument.getAddress());
}
}
@Test
public void testValidateCodingWithCache() throws IOException {
ValidationOptions validationOptions = new ValidationOptions(FhirPublication.R5).withGuessSystem().withVersionFlexible(false);
ValueSet valueSet = new ValueSet();
Coding coding = new Coding();
Mockito.doReturn(cacheToken).when(terminologyCache).generateValidationToken(validationOptions, coding, valueSet, expParameters);
Mockito.doReturn(expectedValidationResult).when(terminologyCache).getValidation(cacheToken);
ValidationContextCarrier ctxt = mock(ValidationContextCarrier.class);
ValidationResult actualValidationResult = context.validateCode(validationOptions, coding, valueSet, ctxt);
assertEquals(expectedValidationResult, actualValidationResult);
Mockito.verify(valueSetCheckerSimple, times(0)).validateCode("Coding", coding);
Mockito.verify(terminologyCache).getValidation(cacheToken);
Mockito.verify(terminologyCache, times(0)).cacheValidation(any(), any(), anyBoolean());
}
@Test
public void testValidateCodingWithValueSetChecker() throws IOException {
ValidationOptions validationOptions = new ValidationOptions(FhirPublication.R5).withGuessSystem().withVersionFlexible(false);
ValueSet valueSet = new ValueSet();
Coding coding = new Coding();
Mockito.doReturn(cacheToken).when(terminologyCache).generateValidationToken(validationOptions, coding, valueSet, expParameters);
Mockito.doReturn(valueSetCheckerSimple).when(context).constructValueSetCheckerSimple(any(), any(), any());
Mockito.doReturn(expectedValidationResult).when(valueSetCheckerSimple).validateCode(eq("Coding"), any(Coding.class));
ValidationContextCarrier ctxt = mock(ValidationContextCarrier.class);
ValidationResult actualValidationResult = context.validateCode(validationOptions, coding, valueSet, ctxt);
assertEquals(expectedValidationResult, actualValidationResult);
Mockito.verify(valueSetCheckerSimple).validateCode(eq("Coding"), argThat(new CodingMatcher(coding)));
Mockito.verify(terminologyCache).getValidation(cacheToken);
Mockito.verify(terminologyCache).cacheValidation(cacheToken, expectedValidationResult,false);
}
@Test
public void testValidateCodingWithServer() throws IOException {
ValidationOptions validationOptions = new ValidationOptions(FhirPublication.R5).withGuessSystem().withVersionFlexible(false).withNoClient();
ValueSet valueSet = new ValueSet();
Coding coding = new Coding();
Mockito.doReturn(cacheToken).when(terminologyCache).generateValidationToken(validationOptions, coding, valueSet, expParameters);
Mockito.doReturn(pIn).when(context).constructParameters(validationOptions, coding);
TerminologyClientContext terminologyClientContext = context.getTxClientManager().getMaster();
Mockito.doReturn(expectedValidationResult).when(context).validateOnServer(terminologyClientContext, valueSet, pIn, validationOptions);
ValidationContextCarrier ctxt = mock(ValidationContextCarrier.class);
ValidationResult actualValidationResult = context.validateCode(validationOptions, coding, valueSet, ctxt);
assertEquals(expectedValidationResult, actualValidationResult);
Mockito.verify(valueSetCheckerSimple, times(0)).validateCode("Coding", coding);
Mockito.verify(terminologyCache).getValidation(cacheToken);
Mockito.verify(terminologyCache).cacheValidation(cacheToken, expectedValidationResult,true);
}
@Test
public void testValidateCodableConceptWithCache() throws IOException {
CodeableConcept codeableConcept = new CodeableConcept();
ValueSet valueSet = new ValueSet();
Mockito.doReturn(cacheToken).when(terminologyCache).generateValidationToken(CacheTestUtils.validationOptions, codeableConcept, valueSet, expParameters);
Mockito.doReturn(expectedValidationResult).when(terminologyCache).getValidation(cacheToken);
ValidationResult actualValidationResult = context.validateCode(CacheTestUtils.validationOptions, codeableConcept, valueSet);
assertEquals(expectedValidationResult, actualValidationResult);
Mockito.verify(valueSetCheckerSimple, times(0)).validateCode("CodeableConcept", codeableConcept);
Mockito.verify(terminologyCache).getValidation(cacheToken);
Mockito.verify(terminologyCache, times(0)).cacheValidation(any(), any(), anyBoolean());
}
@Test
public void testValidateCodableConceptWithValueSetChecker() throws IOException {
Mockito.doReturn(valueSetCheckerSimple).when(context).constructValueSetCheckerSimple(any(), any());
Mockito.doReturn(expectedValidationResult).when(valueSetCheckerSimple).validateCode(eq("CodeableConcept"),any(CodeableConcept.class));
CodeableConcept codeableConcept = new CodeableConcept();
ValueSet valueSet = new ValueSet();
Mockito.doReturn(cacheToken).when(terminologyCache).generateValidationToken(CacheTestUtils.validationOptions, codeableConcept, valueSet, expParameters);
ValidationResult validationResultB = context.validateCode(CacheTestUtils.validationOptions, codeableConcept, valueSet);
assertEquals(expectedValidationResult, validationResultB);
Mockito.verify(valueSetCheckerSimple).validateCode("CodeableConcept", codeableConcept);
Mockito.verify(terminologyCache).cacheValidation(cacheToken, expectedValidationResult, false);
Mockito.verify(context, times(0)).validateOnServer(any(), any(), any(), any());
}
@Test
public void testValidateCodableConceptWithServer() throws IOException {
CodeableConcept codeableConcept = new CodeableConcept();
ValueSet valueSet = new ValueSet();
ValidationOptions validationOptions = CacheTestUtils.validationOptions.withNoClient();
Mockito.doReturn(pIn).when(context).constructParameters(validationOptions, codeableConcept);
TerminologyClientContext terminologyClientContext = context.getTxClientManager().getMaster();
Mockito.doReturn(expectedValidationResult).when(context).validateOnServer(terminologyClientContext, valueSet, pIn, validationOptions);
Mockito.doReturn(cacheToken).when(terminologyCache).generateValidationToken(validationOptions, codeableConcept, valueSet, expParameters);
ValidationResult validationResultB = context.validateCode(validationOptions, codeableConcept, valueSet);
assertEquals(expectedValidationResult, validationResultB);
Mockito.verify(valueSetCheckerSimple, times(0)).validateCode("CodeableConcept", codeableConcept);
Mockito.verify(terminologyCache).cacheValidation(cacheToken, expectedValidationResult, true);
Mockito.verify(context).validateOnServer(terminologyClientContext, valueSet, pIn, validationOptions);
}
@Test
public void testExpandValueSetWithCache() throws IOException {
ValueSet.ConceptSetComponent inc = new ValueSet.ConceptSetComponent();
ValueSet vs = new ValueSet();
vs.setStatus(Enumerations.PublicationStatus.ACTIVE);
vs.setCompose(new ValueSet.ValueSetComposeComponent());
vs.getCompose().setInactive(true);
vs.getCompose().getInclude().add(inc);
Mockito.doReturn(cacheToken).when(terminologyCache).generateExpandToken(argThat(new ValueSetMatcher(vs)),eq(true));
Mockito.doReturn(expectedExpansionResult).when(terminologyCache).getExpansion(cacheToken);
ValueSetExpansionOutcome actualExpansionResult = context.expandVS(inc, true, false);
assertEquals(expectedExpansionResult, actualExpansionResult);
Mockito.verify(terminologyCache).getExpansion(cacheToken);
Mockito.verify(terminologyCache, times(0)).cacheExpansion(any(), any(), anyBoolean());
Mockito.verify(terminologyClient, times(0)).expandValueset(any(), any());
}
@Test
public void testExpandValueSetWithClient() throws IOException {
ValueSet.ConceptSetComponent inc = new ValueSet.ConceptSetComponent();
ValueSet vs = new ValueSet();
vs.setStatus(Enumerations.PublicationStatus.ACTIVE);
vs.setCompose(new ValueSet.ValueSetComposeComponent());
vs.getCompose().setInactive(true);
vs.getCompose().getInclude().add(inc);
Mockito.doReturn(cacheToken).when(terminologyCache).generateExpandToken(argThat(new ValueSetMatcher(vs)),eq(true));
TerminologyClientContext terminologyClientContext = context.getTxClientManager().getMaster();
Mockito.doReturn(expParameters).when(context).constructParameters(argThat(new TerminologyClientContextMatcher(terminologyClientContext)),argThat(new ValueSetMatcher(vs)), eq(true));
ValueSet expectedValueSet = new ValueSet();
Mockito.doReturn(expectedValueSet).when(terminologyClient).expandValueset(argThat(new ValueSetMatcher(vs)),
argThat(new ParametersMatcher(pInWithDependentResources)));
ValueSetExpansionOutcome actualExpansionResult = context.expandVS(inc, true, false);
assertEquals(expectedValueSet, actualExpansionResult.getValueset());
Mockito.verify(terminologyCache).getExpansion(cacheToken);
Mockito.verify(terminologyCache).cacheExpansion(cacheToken, actualExpansionResult,true);
}
@Test
public void testExpandValueSet4ArgsWithCache() throws IOException {
ValueSet vs = new ValueSet();
vs.setUrl(DUMMY_URL);
Mockito.doReturn(cacheToken).when(terminologyCache).generateExpandToken(vs,true);
Mockito.doReturn(expectedExpansionResult).when(terminologyCache).getExpansion(cacheToken);
Parameters pIn = new Parameters();
ValueSetExpansionOutcome actualExpansionResult = context.expandVS(vs, true, true, true, pIn);
assertEquals(expectedExpansionResult, actualExpansionResult);
Mockito.verify(terminologyCache).getExpansion(cacheToken);
Mockito.verify(terminologyCache, times(0)).cacheExpansion(any(), any(), anyBoolean());
Mockito.verify(terminologyClient, times(0)).expandValueset(any(), any());
}
private class ValidationOptionsFhirPublicationMatcher implements ArgumentMatcher<ValidationOptions> {
final FhirPublication fhirPublication;
ValidationOptionsFhirPublicationMatcher(FhirPublication fhirPublication) {
this.fhirPublication = fhirPublication;
}
@Override
public boolean matches(ValidationOptions argument) {
return fhirPublication.toCode().equals(argument.getFhirVersion().toCode());
}
}
@Test
public void testExpandValueSet4ArgsWithValueSetExpanderSimple() throws IOException {
ValueSet vs = new ValueSet();
vs.setUrl(DUMMY_URL);
Mockito.doReturn(cacheToken).when(terminologyCache).generateExpandToken(vs,true);
Parameters pIn = new Parameters();
Mockito.doReturn(vs).when(expectedExpansionResult).getValueset();
Mockito.doReturn(expectedExpansionResult).when(valueSetExpanderSimple).expand(eq(vs),
argThat(new ParametersMatcher(pInWithDependentResources)));
Mockito.doReturn(valueSetExpanderSimple).when(context).constructValueSetExpanderSimple(argThat(new ValidationOptionsFhirPublicationMatcher(vs.getFHIRPublicationVersion())));
ValueSetExpansionOutcome actualExpansionResult = context.expandVS(vs, true, true, true, pIn);
assertEquals(expectedExpansionResult, actualExpansionResult);
Mockito.verify(terminologyCache).getExpansion(cacheToken);
Mockito.verify(terminologyCache).cacheExpansion(cacheToken, actualExpansionResult, false);
Mockito.verify(terminologyClient, times(0)).expandValueset(any(), any());
}
@Test
public void testExpandValueSet4ArgsWithClient() throws IOException {
ValueSet vs = new ValueSet();
vs.setUrl(DUMMY_URL);
Mockito.doReturn(cacheToken).when(terminologyCache).generateExpandToken(vs,true);
Parameters pIn = new Parameters();
ValueSet expectedValueSet = new ValueSet();
expectedValueSet.setUrl("dummyUrl2");
Mockito.doReturn(expectedExpansionResult).when(valueSetExpanderSimple).expand(eq(vs),
argThat(new ParametersMatcher(pInWithDependentResources)));
Mockito.doReturn(valueSetExpanderSimple).when(context).constructValueSetExpanderSimple(argThat(new ValidationOptionsFhirPublicationMatcher(vs.getFHIRPublicationVersion())));
Mockito.doReturn(expectedValueSet).when(terminologyClient).expandValueset(eq(vs), argThat(new ParametersMatcher(pInWithDependentResources)));
ValueSetExpansionOutcome actualExpansionResult = context.expandVS(vs, true, true, true, pIn, false);
assertEquals(expectedValueSet, actualExpansionResult.getValueset());
Mockito.verify(terminologyCache).getExpansion(cacheToken);
Mockito.verify(terminologyCache).cacheExpansion(cacheToken, actualExpansionResult, true);
}
@Test @Test
public void testInitializationWithCache() { public void testInitializationWithCache() {
String address = "dummyUrl"; String address = "dummyUrl";

View File

@ -757,7 +757,9 @@ public class ValidationMessage implements Comparator<ValidationMessage>, Compara
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
return (this.getMessage() != null && this.getMessage().equals(((ValidationMessage)o).getMessage())) && (this.getLocation() != null && this.getLocation().equals(((ValidationMessage)o).getLocation())); return (
this.getMessage() != null && this.getMessage().equals(((ValidationMessage)o).getMessage()))
&& (this.getLocation() != null && this.getLocation().equals(((ValidationMessage)o).getLocation()));
} }
@Override @Override

View File

@ -5612,6 +5612,31 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
} }
private static void addMessagesReplaceExistingIfMoreSevere(List<ValidationMessage> errors, List<ValidationMessage> newErrors) {
for (ValidationMessage newError : newErrors) {
int index = indexOfMatchingMessageAndLocation(errors, newError);
if (index == -1) {
errors.add(newError);
} else {
ValidationMessage existingError = errors.get(index);
if (newError.getLevel().ordinal() < existingError.getLevel().ordinal()) {
errors.set(index, newError);
}
}
}
}
private static int indexOfMatchingMessageAndLocation(List<ValidationMessage> messages, ValidationMessage message) {
for (int i = 0; i < messages.size(); i++) {
ValidationMessage iMessage = messages.get(i);
if (message.getMessage() != null && message.getMessage().equals(iMessage.getMessage())
&& message.getLocation() != null && message.getLocation().equals(iMessage.getLocation())) {
return i;
}
}
return -1;
}
public boolean startInner(ValidationContext valContext, List<ValidationMessage> errors, Element resource, Element element, StructureDefinition defn, NodeStack stack, boolean checkSpecials, PercentageTracker pct, ValidationMode mode, boolean fromContained) { public boolean startInner(ValidationContext valContext, List<ValidationMessage> errors, Element resource, Element element, StructureDefinition defn, NodeStack stack, boolean checkSpecials, PercentageTracker pct, ValidationMode mode, boolean fromContained) {
// the first piece of business is to see if we've validated this resource against this profile before. // the first piece of business is to see if we've validated this resource against this profile before.
// if we have (*or if we still are*), then we'll just return our existing errors // if we have (*or if we still are*), then we'll just return our existing errors
@ -5633,11 +5658,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
trackUsage(defn, valContext, element); trackUsage(defn, valContext, element);
ok = validateElement(valContext, localErrors, defn, defn.getSnapshot().getElement().get(0), null, null, resource, element, element.getName(), stack, false, true, null, pct, mode) && ok; ok = validateElement(valContext, localErrors, defn, defn.getSnapshot().getElement().get(0), null, null, resource, element, element.getName(), stack, false, true, null, pct, mode) && ok;
resTracker.storeOutcomes(defn, localErrors); resTracker.storeOutcomes(defn, localErrors);
for (ValidationMessage vm : localErrors) { addMessagesReplaceExistingIfMoreSevere(errors, localErrors);
if (!errors.contains(vm)) {
errors.add(vm);
}
}
} else { } else {
ok = false; ok = false;
} }

View File

@ -283,11 +283,12 @@ public class ValidationEngineTests {
OperationOutcome op = ve.validate(FhirFormat.JSON, TestingUtilities.loadTestResourceStream("validator", "observation401_ucum.json"), profiles); OperationOutcome op = ve.validate(FhirFormat.JSON, TestingUtilities.loadTestResourceStream("validator", "observation401_ucum.json"), profiles);
Assertions.assertTrue(checkOutcomes("test401USCore", op, Assertions.assertTrue(checkOutcomes("test401USCore", op,
"Observation null information/informational: Validate Observation against the Body weight profile (http://hl7.org/fhir/StructureDefinition/bodyweight) which is required by the FHIR specification because the LOINC code 29463-7 was found\n"+ "Observation null information/informational: Validate Observation against the Body weight profile (http://hl7.org/fhir/StructureDefinition/bodyweight) which is required by the FHIR specification because the LOINC code 29463-7 was found\n"+
"Observation.code.coding[0].system null information/not-found: A definition for CodeSystem 'http://loinc.org' could not be found, so the code cannot be validated\n"+
"Observation.value.ofType(Quantity) null warning/business-rule: Unable to validate code 'kg' in system 'http://unitsofmeasure.org' because the validator is running without terminology services\n"+ "Observation.value.ofType(Quantity) null warning/business-rule: Unable to validate code 'kg' in system 'http://unitsofmeasure.org' because the validator is running without terminology services\n"+
"Observation.value.ofType(Quantity).code null warning/informational: Unable to validate code without using server because: Resolved system http://unitsofmeasure.org (v3.0.1), but the definition doesn't include any codes, so the code has not been validated\n"+ "Observation.value.ofType(Quantity).code null warning/informational: Unable to validate code without using server because: Resolved system http://unitsofmeasure.org (v3.0.1), but the definition doesn't include any codes, so the code has not been validated\n"+
// "Observation.code null warning/code-invalid: None of the codings provided are in the value set 'Vital Signs' (http://hl7.org/fhir/ValueSet/observation-vitalsignresult|4.0.1), and a coding should come from this value set unless it has no suitable code (note that the validator cannot judge what is suitable) (codes = http://loinc.org#29463-7)\n"+ // "Observation.code null warning/code-invalid: None of the codings provided are in the value set 'Vital Signs' (http://hl7.org/fhir/ValueSet/observation-vitalsignresult|4.0.1), and a coding should come from this value set unless it has no suitable code (note that the validator cannot judge what is suitable) (codes = http://loinc.org#29463-7)\n"+
"Observation null warning/invalid: Best Practice Recommendation: In general, all observations should have a performer\n"+ "Observation null warning/invalid: Best Practice Recommendation: In general, all observations should have a performer\n"+
"Observation.code.coding[0].system null warning/not-found: A definition for CodeSystem 'http://loinc.org' could not be found, so the code cannot be validated\n"+
"Observation.code null warning/not-found: Unable to check whether the code is in the value set 'http://hl7.org/fhir/ValueSet/observation-vitalsignresult|4.0.1' because the code system http://loinc.org was not found\n"+ "Observation.code null warning/not-found: Unable to check whether the code is in the value set 'http://hl7.org/fhir/ValueSet/observation-vitalsignresult|4.0.1' because the code system http://loinc.org was not found\n"+
"Observation null warning/invariant: Constraint failed: dom-6: 'A resource should have narrative for robust management' (defined in http://hl7.org/fhir/StructureDefinition/DomainResource) (Best Practice Recommendation)")); "Observation null warning/invariant: Constraint failed: dom-6: 'A resource should have narrative for robust management' (defined in http://hl7.org/fhir/StructureDefinition/DomainResource) (Best Practice Recommendation)"));
assertTrue(logger.verifyHasNoRequests(), "Unexpected request to TX server"); assertTrue(logger.verifyHasNoRequests(), "Unexpected request to TX server");