Patient validate operation with remote terminology service enabled returns 400 bad request (#6124)

* Patient $validate operation with Remote Terminology Service enabled returns 400 Bad Request - failing test

* Patient $validate operation with Remote Terminology Service enabled returns 400 Bad Request - implementation
This commit is contained in:
volodymyr-korzh 2024-07-31 11:38:48 -06:00 committed by GitHub
parent 7e75ad27bf
commit c32784ed51
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 567 additions and 119 deletions

View File

@ -7,6 +7,7 @@ org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService.
org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService.codeNotFoundInValueSet=Code "{0}" is not in valueset: {1}
org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.unknownCodeInSystem=Unknown code "{0}#{1}". The Remote Terminology server {2} returned {3}
org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.unknownCodeInValueSet=Unknown code "{0}#{1}" for ValueSet with URL "{2}". The Remote Terminology server {3} returned {4}
ca.uhn.fhir.jpa.term.TermReadSvcImpl.expansionRefersToUnknownCs=Unknown CodeSystem URI "{0}" referenced from ValueSet
ca.uhn.fhir.jpa.term.TermReadSvcImpl.valueSetNotYetExpanded=ValueSet "{0}" has not yet been pre-expanded. Performing in-memory expansion without parameters. Current status: {1} | {2}

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 6122
title: "Previously, executing the '$validate' operation on a resource instance could result in an HTTP 400 Bad Request
instead of an HTTP 200 OK response with a list of validation issues. This has been fixed."

View File

@ -595,11 +595,11 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
fail(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome()));
}
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(8, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
assertEquals(10, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size());
assertEquals(0, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size());
assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
assertEquals(6, myCaptureQueriesListener.getCommitCount());
assertEquals(8, myCaptureQueriesListener.getCommitCount());
// Validate again (should rely only on caches)
myCaptureQueriesListener.clear();

View File

@ -33,7 +33,6 @@ import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent;
import org.hl7.fhir.r4.model.Property;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.Type;
import org.hl7.fhir.r4.model.ValueSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -83,6 +82,7 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
String theCode,
String theDisplay,
String theValueSetUrl) {
return invokeRemoteValidateCode(theCodeSystem, theCode, theDisplay, theValueSetUrl, null);
}
@ -101,12 +101,7 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
// so let's try to get it from the VS if is not present
String codeSystem = theCodeSystem;
if (isNotBlank(theCode) && isBlank(codeSystem)) {
codeSystem = extractCodeSystemForCode((ValueSet) theValueSet, theCode);
}
// Remote terminology services shouldn't be used to validate codes with an implied system
if (isBlank(codeSystem)) {
return null;
codeSystem = ValidationSupportUtils.extractCodeSystemForCode(theValueSet, theCode);
}
String valueSetUrl = DefaultProfileValidationSupport.getConformanceResourceUrl(myCtx, valueSet);
@ -118,48 +113,6 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
return invokeRemoteValidateCode(codeSystem, theCode, theDisplay, valueSetUrl, valueSet);
}
/**
* Try to obtain the codeSystem of the received code from the received ValueSet
*/
private String extractCodeSystemForCode(ValueSet theValueSet, String theCode) {
if (theValueSet.getCompose() == null
|| theValueSet.getCompose().getInclude() == null
|| theValueSet.getCompose().getInclude().isEmpty()) {
return null;
}
if (theValueSet.getCompose().getInclude().size() == 1) {
ValueSet.ConceptSetComponent include =
theValueSet.getCompose().getInclude().iterator().next();
return getVersionedCodeSystem(include);
}
// when component has more than one include, their codeSystem(s) could be different, so we need to make sure
// that we are picking up the system for the include filter to which the code corresponds
for (ValueSet.ConceptSetComponent include : theValueSet.getCompose().getInclude()) {
if (include.hasSystem()) {
for (ValueSet.ConceptReferenceComponent concept : include.getConcept()) {
if (concept.hasCodeElement() && concept.getCode().equals(theCode)) {
return getVersionedCodeSystem(include);
}
}
}
}
// at this point codeSystem couldn't be extracted for a multi-include ValueSet. Just on case it was
// because the format was not well handled, let's allow to watch the VS by an easy logging change
ourLog.trace("CodeSystem couldn't be extracted for code: {} for ValueSet: {}", theCode, theValueSet.getId());
return null;
}
private String getVersionedCodeSystem(ValueSet.ConceptSetComponent theComponent) {
String codeSystem = theComponent.getSystem();
if (!codeSystem.contains("|") && theComponent.hasVersion()) {
codeSystem += "|" + theComponent.getVersion();
}
return codeSystem;
}
@Override
public IBaseResource fetchCodeSystem(String theSystem) {
// callers of this want the whole resource.
@ -630,11 +583,22 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
resourceType = "CodeSystem";
}
IBaseParameters output = client.operation()
.onType(resourceType)
.named("validate-code")
.withParameters(input)
.execute();
IBaseParameters output;
try {
output = client.operation()
.onType(resourceType)
.named("validate-code")
.withParameters(input)
.execute();
} catch (ResourceNotFoundException | InvalidRequestException ex) {
ourLog.error(ex.getMessage(), ex);
CodeValidationResult result = new CodeValidationResult();
result.setSeverity(IssueSeverity.ERROR);
String errorMessage = buildErrorMessage(
theCodeSystem, theCode, theValueSetUrl, theValueSet, client.getServerBase(), ex.getMessage());
result.setMessage(errorMessage);
return result;
}
List<String> resultValues = ParametersUtil.getNamedParameterValuesAsString(getFhirContext(), output, "result");
if (resultValues.isEmpty() || isBlank(resultValues.get(0))) {
@ -666,6 +630,21 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
return retVal;
}
private String buildErrorMessage(
String theCodeSystem,
String theCode,
String theValueSetUrl,
IBaseResource theValueSet,
String theServerUrl,
String theServerMessage) {
if (theValueSetUrl == null && theValueSet == null) {
return getErrorMessage("unknownCodeInSystem", theCodeSystem, theCode, theServerUrl, theServerMessage);
} else {
return getErrorMessage(
"unknownCodeInValueSet", theCodeSystem, theCode, theValueSetUrl, theServerUrl, theServerMessage);
}
}
protected IBaseParameters buildValidateCodeInputParameters(
String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl, IBaseResource theValueSet) {
IBaseParameters params = ParametersUtil.newInstance(getFhirContext());

View File

@ -0,0 +1,133 @@
package org.hl7.fhir.common.hapi.validation.support;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.ValueSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class ValidationSupportUtils {
private static final Logger ourLog = LoggerFactory.getLogger(ValidationSupportUtils.class);
private ValidationSupportUtils() {}
public static String extractCodeSystemForCode(IBaseResource theValueSet, String theCode) {
if (theValueSet instanceof org.hl7.fhir.dstu3.model.ValueSet) {
return extractCodeSystemForCodeDSTU3((org.hl7.fhir.dstu3.model.ValueSet) theValueSet, theCode);
} else if (theValueSet instanceof ValueSet) {
return extractCodeSystemForCodeR4((ValueSet) theValueSet, theCode);
} else if (theValueSet instanceof org.hl7.fhir.r5.model.ValueSet) {
return extractCodeSystemForCodeR5((org.hl7.fhir.r5.model.ValueSet) theValueSet, theCode);
}
return null;
}
/**
* Try to obtain the codeSystem of the received code from the input DSTU3 ValueSet
*/
private static String extractCodeSystemForCodeDSTU3(org.hl7.fhir.dstu3.model.ValueSet theValueSet, String theCode) {
if (theValueSet.getCompose().getInclude().isEmpty()) {
return null;
}
if (theValueSet.getCompose().getInclude().size() == 1) {
org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent include =
theValueSet.getCompose().getInclude().iterator().next();
return include.hasSystem() ? getVersionedCodeSystem(include.getSystem(), include.getVersion()) : null;
}
// when component has more than one include, their codeSystem(s) could be different, so we need to make sure
// that we are picking up the system for the include filter to which the code corresponds
for (org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent include :
theValueSet.getCompose().getInclude()) {
if (include.hasSystem()) {
for (org.hl7.fhir.dstu3.model.ValueSet.ConceptReferenceComponent concept : include.getConcept()) {
if (concept.hasCodeElement() && concept.getCode().equals(theCode)) {
return getVersionedCodeSystem(include.getSystem(), include.getVersion());
}
}
}
}
// at this point codeSystem couldn't be extracted for a multi-include ValueSet. Just on case it was
// because the format was not well handled, let's allow to watch the VS by an easy logging change
logCodeAndValueSet(theCode, theValueSet.getId());
return null;
}
/**
* Try to obtain the codeSystem of the received code from the input R4 ValueSet
*/
private static String extractCodeSystemForCodeR4(ValueSet theValueSet, String theCode) {
if (theValueSet.getCompose().getInclude().isEmpty()) {
return null;
}
if (theValueSet.getCompose().getInclude().size() == 1) {
ValueSet.ConceptSetComponent include =
theValueSet.getCompose().getInclude().iterator().next();
return include.hasSystem() ? getVersionedCodeSystem(include.getSystem(), include.getVersion()) : null;
}
// when component has more than one include, their codeSystem(s) could be different, so we need to make sure
// that we are picking up the system for the include filter to which the code corresponds
for (ValueSet.ConceptSetComponent include : theValueSet.getCompose().getInclude()) {
if (include.hasSystem()) {
for (ValueSet.ConceptReferenceComponent concept : include.getConcept()) {
if (concept.hasCodeElement() && concept.getCode().equals(theCode)) {
return getVersionedCodeSystem(include.getSystem(), include.getVersion());
}
}
}
}
// at this point codeSystem couldn't be extracted for a multi-include ValueSet. Just on case it was
// because the format was not well handled, let's allow to watch the VS by an easy logging change
logCodeAndValueSet(theCode, theValueSet.getId());
return null;
}
private static String getVersionedCodeSystem(String theCodeSystem, String theVersion) {
if (!theCodeSystem.contains("|") && theVersion != null) {
return theCodeSystem + "|" + theVersion;
}
return theCodeSystem;
}
/**
* Try to obtain the codeSystem of the received code from the input R5 ValueSet
*/
private static String extractCodeSystemForCodeR5(org.hl7.fhir.r5.model.ValueSet theValueSet, String theCode) {
if (theValueSet.getCompose().getInclude().isEmpty()) {
return null;
}
if (theValueSet.getCompose().getInclude().size() == 1) {
org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent include =
theValueSet.getCompose().getInclude().iterator().next();
return include.hasSystem() ? getVersionedCodeSystem(include.getSystem(), include.getVersion()) : null;
}
// when component has more than one include, their codeSystem(s) could be different, so we need to make sure
// that we are picking up the system for the include filter to which the code corresponds
for (org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent include :
theValueSet.getCompose().getInclude()) {
if (include.hasSystem()) {
for (org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent concept : include.getConcept()) {
if (concept.hasCodeElement() && concept.getCode().equals(theCode)) {
return getVersionedCodeSystem(include.getSystem(), include.getVersion());
}
}
}
}
// at this point codeSystem couldn't be extracted for a multi-include ValueSet. Just on case it was
// because the format was not well handled, let's allow to watch the VS by an easy logging change
logCodeAndValueSet(theCode, theValueSet.getId());
return null;
}
private static void logCodeAndValueSet(String theCode, String theValueSet) {
ourLog.trace("CodeSystem couldn't be extracted for code: {} for ValueSet: {}", theCode, theValueSet);
}
}

View File

@ -16,6 +16,7 @@ import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.fhir.ucum.UcumService;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportUtils;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -25,11 +26,13 @@ import org.hl7.fhir.r5.context.IWorkerContextManager;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.CodeableConcept;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.NamingSystem;
import org.hl7.fhir.r5.model.OperationOutcome;
import org.hl7.fhir.r5.model.PackageInformation;
import org.hl7.fhir.r5.model.Parameters;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.profilemodel.PEBuilder;
@ -66,7 +69,7 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
private final VersionCanonicalizer myVersionCanonicalizer;
private final LoadingCache<ResourceKey, IBaseResource> myFetchResourceCache;
private volatile List<StructureDefinition> myAllStructures;
private org.hl7.fhir.r5.model.Parameters myExpansionProfile;
private Parameters myExpansionProfile;
public VersionSpecificWorkerContextWrapper(
ValidationSupportContext theValidationSupportContext, VersionCanonicalizer theVersionCanonicalizer) {
@ -215,7 +218,7 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
}
@Override
public org.hl7.fhir.r5.model.Parameters getExpansionParameters() {
public Parameters getExpansionParameters() {
return myExpansionProfile;
}
@ -224,7 +227,7 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
setExpansionProfile(expParameters);
}
public void setExpansionProfile(org.hl7.fhir.r5.model.Parameters expParameters) {
public void setExpansionProfile(Parameters expParameters) {
myExpansionProfile = expParameters;
}
@ -318,7 +321,7 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
issue.getDetails().setText(codeValidationIssue.getMessage());
issue.addExtension()
.setUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-message-id")
.setValue(new org.hl7.fhir.r5.model.StringType("Terminology_PassThrough_TX_Message"));
.setValue(new StringType("Terminology_PassThrough_TX_Message"));
issues.add(issue);
}
return issues;
@ -369,8 +372,7 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
}
@Override
public ValueSetExpansionOutcome expandVS(
org.hl7.fhir.r5.model.ValueSet source, boolean cacheOk, boolean Hierarchical) {
public ValueSetExpansionOutcome expandVS(ValueSet source, boolean cacheOk, boolean Hierarchical) {
IBaseResource convertedSource;
try {
convertedSource = myVersionCanonicalizer.valueSetFromValidatorCanonical(source);
@ -381,7 +383,7 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
.getRootValidationSupport()
.expandValueSet(myValidationSupportContext, null, convertedSource);
org.hl7.fhir.r5.model.ValueSet convertedResult = null;
ValueSet convertedResult = null;
if (expanded.getValueSet() != null) {
try {
convertedResult = myVersionCanonicalizer.valueSetToValidatorCanonical(expanded.getValueSet());
@ -399,7 +401,7 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
@Override
public ValueSetExpansionOutcome expandVS(
Resource src,
org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent binding,
ElementDefinition.ElementDefinitionBindingComponent binding,
boolean cacheOk,
boolean Hierarchical) {
ValueSet valueSet = fetchResource(ValueSet.class, binding.getValueSet(), src);
@ -427,14 +429,14 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
}
@Override
public org.hl7.fhir.r5.model.CodeSystem fetchCodeSystem(String system) {
public CodeSystem fetchCodeSystem(String system) {
IBaseResource fetched =
myValidationSupportContext.getRootValidationSupport().fetchCodeSystem(system);
if (fetched == null) {
return null;
}
try {
return (org.hl7.fhir.r5.model.CodeSystem) myVersionCanonicalizer.codeSystemToValidatorCanonical(fetched);
return myVersionCanonicalizer.codeSystemToValidatorCanonical(fetched);
} catch (FHIRException e) {
throw new InternalErrorException(Msg.code(665) + e);
}
@ -448,7 +450,7 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
return null;
}
try {
return (org.hl7.fhir.r5.model.CodeSystem) myVersionCanonicalizer.codeSystemToValidatorCanonical(fetched);
return myVersionCanonicalizer.codeSystemToValidatorCanonical(fetched);
} catch (FHIRException e) {
throw new InternalErrorException(Msg.code(1992) + e);
}
@ -746,8 +748,7 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
}
@Override
public ValidationResult validateCode(
ValidationOptions theOptions, String code, org.hl7.fhir.r5.model.ValueSet theValueSet) {
public ValidationResult validateCode(ValidationOptions theOptions, String code, ValueSet theValueSet) {
IBaseResource convertedVs = null;
try {
if (theValueSet != null) {
@ -757,17 +758,16 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
throw new InternalErrorException(Msg.code(690) + e);
}
String system = ValidationSupportUtils.extractCodeSystemForCode(theValueSet, code);
ConceptValidationOptions validationOptions =
convertConceptValidationOptions(theOptions).setInferSystem(true);
return doValidation(convertedVs, validationOptions, null, code, null);
return doValidation(convertedVs, validationOptions, system, code, null);
}
@Override
public ValidationResult validateCode(
ValidationOptions theOptions,
org.hl7.fhir.r5.model.Coding theCoding,
org.hl7.fhir.r5.model.ValueSet theValueSet) {
public ValidationResult validateCode(ValidationOptions theOptions, Coding theCoding, ValueSet theValueSet) {
IBaseResource convertedVs = null;
try {
@ -868,10 +868,7 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
}
@Override
public ValidationResult validateCode(
ValidationOptions theOptions,
org.hl7.fhir.r5.model.CodeableConcept code,
org.hl7.fhir.r5.model.ValueSet theVs) {
public ValidationResult validateCode(ValidationOptions theOptions, CodeableConcept code, ValueSet theVs) {
List<ValidationResult> validationResultsOk = new ArrayList<>();
List<OperationOutcome.OperationOutcomeIssueComponent> issues = new ArrayList<>();

View File

@ -0,0 +1,185 @@
package org.hl7.fhir.common.hapi.validation.support;
import com.google.common.collect.Lists;
import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
public class ValidationSupportUtilsTest {
public static final String SYSTEM_URL = "http://hl7.org/fhir/ValueSet/administrative-gender";
public static final String SYSTEM_VERSION = "3.0.2";
public static final String SYSTEM_URL_2 = "http://hl7.org/fhir/ValueSet/other-valueset";
public static final String SYSTEM_VERSION_2 = "4.0.1";
public static final String VALUE_SET_URL = "http://value.set/url";
public static final String CODE = "CODE";
public static final String NOT_THE_CODE = "not-the-code";
@Test
public void extractCodeSystemForCode_nullValueSet_returnsNull() {
String result = ValidationSupportUtils.extractCodeSystemForCode(null, CODE);
assertNull(result);
}
private static Stream<Arguments> extractCodeSystemForCodeDSTU3TestCases() {
List<org.hl7.fhir.dstu3.model.ValueSet.ConceptReferenceComponent> conceptWithCode = Lists.newArrayList(
new org.hl7.fhir.dstu3.model.ValueSet.ConceptReferenceComponent().setCode(NOT_THE_CODE),
new org.hl7.fhir.dstu3.model.ValueSet.ConceptReferenceComponent().setCode(CODE));
List<org.hl7.fhir.dstu3.model.ValueSet.ConceptReferenceComponent> conceptNoCode = Lists.newArrayList(
new org.hl7.fhir.dstu3.model.ValueSet.ConceptReferenceComponent().setCode(NOT_THE_CODE));
return Stream.of(
Arguments.of(Collections.emptyList(), null, "Empty ValueSet includes"),
Arguments.of(Lists.newArrayList(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent(),
new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent()),
null, "ValueSet includes without system"),
Arguments.of(Lists.newArrayList(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent()),
null, "ValueSet include without system"),
Arguments.of(Lists.newArrayList(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL)),
SYSTEM_URL, "ValueSet include with one system and no code"),
Arguments.of(Lists.newArrayList(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL).setConcept(conceptWithCode)),
SYSTEM_URL, "ValueSet include with one system and code"),
Arguments.of(Lists.newArrayList(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL).setVersion(SYSTEM_VERSION)),
SYSTEM_URL + "|" + SYSTEM_VERSION, "ValueSet include with one versioned system and no code"),
Arguments.of(Lists.newArrayList(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL),
new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL_2)),
null, "ValueSet includes with two systems and no code"),
Arguments.of(Lists.newArrayList(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL).setConcept(conceptWithCode),
new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL_2)),
SYSTEM_URL, "ValueSet includes with two systems and correct code"),
Arguments.of(Lists.newArrayList(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL).setVersion(SYSTEM_VERSION).setConcept(conceptWithCode),
new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL_2).setVersion(SYSTEM_VERSION_2)),
SYSTEM_URL + "|" + SYSTEM_VERSION, "ValueSet includes with two systems with versions and correct code"),
Arguments.of(Lists.newArrayList(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL).setConcept(conceptNoCode),
new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL_2)),
null, "ValueSet includes with two systems and different code"));
}
@ParameterizedTest
@MethodSource("extractCodeSystemForCodeDSTU3TestCases")
public void extractCodeSystemForCodeDSTU3_withDifferentValueSetIncludes_returnsCorrectResult(List<org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent> theValueSetComponents,
String theExpectedCodeSystem, String theMessage) {
// setup
org.hl7.fhir.dstu3.model.ValueSet valueSet = new org.hl7.fhir.dstu3.model.ValueSet();
valueSet.setUrl(VALUE_SET_URL);
valueSet.setCompose(new org.hl7.fhir.dstu3.model.ValueSet.ValueSetComposeComponent().setInclude(theValueSetComponents));
// execute
String result = ValidationSupportUtils.extractCodeSystemForCode(valueSet, CODE);
// validate
assertEquals(theExpectedCodeSystem, result, theMessage);
}
private static Stream<Arguments> extractCodeSystemForCodeR4TestCases() {
List<ValueSet.ConceptReferenceComponent> conceptWithCode = Lists.newArrayList(
new ValueSet.ConceptReferenceComponent().setCode(NOT_THE_CODE),
new ValueSet.ConceptReferenceComponent().setCode(CODE));
List<ValueSet.ConceptReferenceComponent> conceptNoCode = Lists.newArrayList(
new ValueSet.ConceptReferenceComponent().setCode(NOT_THE_CODE));
return Stream.of(
Arguments.of(Collections.emptyList(), null, "Empty ValueSet includes"),
Arguments.of(Lists.newArrayList(new ValueSet.ConceptSetComponent(), new ValueSet.ConceptSetComponent()),
null, "ValueSet includes without system"),
Arguments.of(Lists.newArrayList(new ValueSet.ConceptSetComponent()),
null, "ValueSet include without system"),
Arguments.of(Lists.newArrayList(new ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL)),
SYSTEM_URL, "ValueSet include with one system and no code"),
Arguments.of(Lists.newArrayList(new ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL).setConcept(conceptWithCode)),
SYSTEM_URL, "ValueSet include with one system and code"),
Arguments.of(Lists.newArrayList(new ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL).setVersion(SYSTEM_VERSION)),
SYSTEM_URL + "|" + SYSTEM_VERSION, "ValueSet include with one versioned system and no code"),
Arguments.of(Lists.newArrayList(new ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL),
new ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL_2)),
null, "ValueSet includes with two systems and no code"),
Arguments.of(Lists.newArrayList(new ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL).setConcept(conceptWithCode),
new ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL_2)),
SYSTEM_URL, "ValueSet includes with two systems and correct code"),
Arguments.of(Lists.newArrayList(new ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL).setVersion(SYSTEM_VERSION).setConcept(conceptWithCode),
new ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL_2).setVersion(SYSTEM_VERSION_2)),
SYSTEM_URL + "|" + SYSTEM_VERSION, "ValueSet includes with two systems with versions and correct code"),
Arguments.of(Lists.newArrayList(new ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL).setConcept(conceptNoCode),
new ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL_2)),
null, "ValueSet includes with two systems and different code"));
}
@ParameterizedTest
@MethodSource("extractCodeSystemForCodeR4TestCases")
public void extractCodeSystemForCodeR4_withDifferentValueSetIncludes_returnsCorrectResult(List<ValueSet.ConceptSetComponent> theValueSetComponents,
String theExpectedCodeSystem, String theMessage) {
// setup
ValueSet valueSet = new ValueSet();
valueSet.setUrl(VALUE_SET_URL);
valueSet.setCompose(new ValueSet.ValueSetComposeComponent().setInclude(theValueSetComponents));
// execute
String result = ValidationSupportUtils.extractCodeSystemForCode(valueSet, CODE);
// validate
assertEquals(theExpectedCodeSystem, result, theMessage);
}
private static Stream<Arguments> extractCodeSystemForCodeR5TestCases() {
List<org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent> conceptWithCode = Lists.newArrayList(
new org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent().setCode(NOT_THE_CODE),
new org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent().setCode(CODE));
List<org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent> conceptNoCode = Lists.newArrayList(
new org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent().setCode(NOT_THE_CODE));
return Stream.of(
Arguments.of(Collections.emptyList(), null, "Empty ValueSet includes"),
Arguments.of(Lists.newArrayList(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent(),
new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent()),
null, "ValueSet includes without system"),
Arguments.of(Lists.newArrayList(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent()),
null, "ValueSet include without system"),
Arguments.of(Lists.newArrayList(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL)),
SYSTEM_URL, "ValueSet include with one system and no code"),
Arguments.of(Lists.newArrayList(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL).setConcept(conceptWithCode)),
SYSTEM_URL, "ValueSet include with one system and code"),
Arguments.of(Lists.newArrayList(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL).setVersion(SYSTEM_VERSION)),
SYSTEM_URL + "|" + SYSTEM_VERSION, "ValueSet include with one versioned system and no code"),
Arguments.of(Lists.newArrayList(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL),
new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL_2)),
null, "ValueSet includes with two systems and no code"),
Arguments.of(Lists.newArrayList(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL).setConcept(conceptWithCode),
new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL_2)),
SYSTEM_URL, "ValueSet includes with two systems and correct code"),
Arguments.of(Lists.newArrayList(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL).setVersion(SYSTEM_VERSION).setConcept(conceptWithCode),
new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL_2).setVersion(SYSTEM_VERSION_2)),
SYSTEM_URL + "|" + SYSTEM_VERSION, "ValueSet includes with two systems with versions and correct code"),
Arguments.of(Lists.newArrayList(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL).setConcept(conceptNoCode),
new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent().setSystem(SYSTEM_URL_2)),
null, "ValueSet includes with two systems and different code"));
}
@ParameterizedTest
@MethodSource("extractCodeSystemForCodeR5TestCases")
public void extractCodeSystemForCodeR5_withDifferentValueSetIncludes_returnsCorrectResult(List<org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent> theValueSetComponents,
String theExpectedCodeSystem, String theMessage) {
// setup
org.hl7.fhir.r5.model.ValueSet valueSet = new org.hl7.fhir.r5.model.ValueSet();
valueSet.setUrl(VALUE_SET_URL);
valueSet.setCompose(new org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent().setInclude(theValueSetComponents));
// execute
String result = ValidationSupportUtils.extractCodeSystemForCode(valueSet, CODE);
// validate
assertEquals(theExpectedCodeSystem, result, theMessage);
}
}

View File

@ -8,11 +8,17 @@ import ca.uhn.fhir.fhirpath.BaseValidationTestWithInlineMocks;
import ca.uhn.fhir.i18n.HapiLocalizer;
import ca.uhn.hapi.converters.canonical.VersionCanonicalizer;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.junit.jupiter.api.Test;
import org.mockito.quality.Strictness;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;
@ -68,6 +74,28 @@ public class VersionSpecificWorkerContextWrapperTest extends BaseValidationTestW
wrapper.cacheResource(mock(Resource.class));
}
@Test
public void validateCode_normally_resolvesCodeSystemFromValueSet() {
// setup
IValidationSupport validationSupport = mockValidationSupport();
ValidationSupportContext mockContext = mockValidationSupportContext(validationSupport);
VersionCanonicalizer versionCanonicalizer = new VersionCanonicalizer(FhirContext.forR5Cached());
VersionSpecificWorkerContextWrapper wrapper = new VersionSpecificWorkerContextWrapper(mockContext, versionCanonicalizer);
ValueSet valueSet = new ValueSet();
valueSet.getCompose().addInclude().setSystem("http://codesystems.com/system").addConcept().setCode("code0");
valueSet.getCompose().addInclude().setSystem("http://codesystems.com/system2").addConcept().setCode("code2");
when(validationSupport.fetchResource(eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(valueSet);
when(validationSupport.validateCodeInValueSet(any(), any(), any(), any(), any(), any())).thenReturn(new IValidationSupport.CodeValidationResult());
// execute
wrapper.validateCode(new ValidationOptions(), "code0", valueSet);
// verify
verify(validationSupport, times(1)).validateCodeInValueSet(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), any());
verify(validationSupport, times(1)).validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), any());
}
private IValidationSupport mockValidationSupportWithTwoBinaries() {
IValidationSupport validationSupport;
validationSupport = mockValidationSupport();

View File

@ -1266,7 +1266,8 @@ public class FhirInstanceValidatorDstu3Test extends BaseValidationTestWithInline
ValidationResult output = myVal.validateWithResult(input);
logResultsAndReturnAll(output);
assertThat(output.getMessages().get(0).getMessage()).contains("The value provided ('notvalidcode') was not found in the value set 'ObservationStatus'");
assertThat(output.getMessages().get(0).getMessage()).contains("Unknown code 'http://hl7.org/fhir/observation-status#notvalidcode'");
assertThat(output.getMessages().get(1).getMessage()).contains("The value provided ('notvalidcode') was not found in the value set 'ObservationStatus'");
}
@Test

View File

@ -45,6 +45,7 @@ import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.Consent;
import org.hl7.fhir.r4.model.ContactPoint;
import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.Media;
@ -107,6 +108,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@ -126,6 +128,7 @@ public class FhirInstanceValidatorR4Test extends BaseValidationTestWithInlineMoc
private FhirValidator myFhirValidator;
private ArrayList<String> myValidConcepts;
private Set<String> myValidSystems = new HashSet<>();
private Set<String> myValidValueSets = new HashSet<>();
private Map<String, StructureDefinition> myStructureDefinitionMap = new HashMap<>();
private CachingValidationSupport myValidationSupport;
private IValidationSupport myMockSupport;
@ -135,6 +138,10 @@ public class FhirInstanceValidatorR4Test extends BaseValidationTestWithInlineMoc
myValidConcepts.add(theSystem + "___" + theCode);
}
private void addValidValueSet(String theValueSetUrl) {
myValidValueSets.add(theValueSetUrl);
}
/**
* An invalid local reference should not cause a ServiceException.
*/
@ -266,6 +273,16 @@ public class FhirInstanceValidatorR4Test extends BaseValidationTestWithInlineMoc
}
});
when(myMockSupport.isValueSetSupported(any(), nullable(String.class))).thenAnswer(new Answer<Boolean>() {
@Override
public Boolean answer(InvocationOnMock theInvocation) {
String argument = theInvocation.getArgument(1, String.class);
boolean retVal = myValidValueSets.contains(argument);
ourLog.debug("isValueSetSupported({}) : {}", argument, retVal);
return retVal;
}
});
when(myMockSupport.validateCode(any(), any(), nullable(String.class), nullable(String.class), nullable(String.class), nullable(String.class))).thenAnswer(new Answer<IValidationSupport.CodeValidationResult>() {
@Override
public IValidationSupport.CodeValidationResult answer(InvocationOnMock theInvocation) {
@ -855,6 +872,25 @@ public class FhirInstanceValidatorR4Test extends BaseValidationTestWithInlineMoc
assertTrue(output.isSuccessful());
}
@Test
public void testValidate_patientWithGenderCode_resolvesCodeSystemFromValueSet() {
// setup
Patient patient = new Patient();
patient.setGender(Enumerations.AdministrativeGender.MALE);
addValidConcept("http://hl7.org/fhir/administrative-gender", "male");
addValidValueSet("http://hl7.org/fhir/ValueSet/administrative-gender");
// execute
ValidationResult output = myFhirValidator.validateWithResult(patient);
logResultsAndReturnNonInformationalOnes(output);
// verify
assertTrue(output.isSuccessful());
verify(myMockSupport, times(1)).validateCodeInValueSet(any(), any(), eq("http://hl7.org/fhir/administrative-gender"), eq("male"), any(), any());
verify(myMockSupport, times(1)).validateCode(any(), any(), eq("http://hl7.org/fhir/administrative-gender"), eq("male"), any(), any());
}
@Test
public void testValidateProfileWithExtension() throws IOException, FHIRException {
PrePopulatedValidationSupport valSupport = new PrePopulatedValidationSupport(ourCtx);
@ -1298,7 +1334,8 @@ public class FhirInstanceValidatorR4Test extends BaseValidationTestWithInlineMoc
"</Observation>";
ValidationResult output = myFhirValidator.validateWithResult(input);
logResultsAndReturnAll(output);
assertEquals("The value provided ('notvalidcode') was not found in the value set 'ObservationStatus' (http://hl7.org/fhir/ValueSet/observation-status|4.0.1), and a code is required from this value set (error message = Unknown code 'notvalidcode' for in-memory expansion of ValueSet 'http://hl7.org/fhir/ValueSet/observation-status')", output.getMessages().get(0).getMessage());
assertThat(output.getMessages().get(0).getMessage()).contains("Unknown code 'http://hl7.org/fhir/observation-status#notvalidcode'");
assertThat(output.getMessages().get(1).getMessage()).contains("The value provided ('notvalidcode') was not found in the value set 'ObservationStatus' (http://hl7.org/fhir/ValueSet/observation-status|4.0.1), and a code is required from this value set (error message = Unknown code 'http://hl7.org/fhir/observation-status#notvalidcode' for in-memory expansion of ValueSet 'http://hl7.org/fhir/ValueSet/observation-status')");
}
@Test
@ -1488,10 +1525,18 @@ public class FhirInstanceValidatorR4Test extends BaseValidationTestWithInlineMoc
input.getValueQuantity().setCode("Heck");
output = myFhirValidator.validateWithResult(input);
all = logResultsAndReturnNonInformationalOnes(output);
assertThat(all).hasSize(2);
assertThat(all).hasSize(3);
// validate first error, it has similar message as second error, but location is different
// as in R4 (as opposed to R4B/R5) Observation.value.ofType(Quantity) has no ValueSet binding,
// there is no `Unknown code for ValueSet` error message
assertThat(all.get(0).getMessage()).contains("Error processing unit 'Heck': The unit 'Heck' is unknown' at position 0 (for 'http://unitsofmeasure.org#Heck')");
assertThat(all.get(1).getMessage()).contains("The value provided ('Heck') was not found in the value set 'Body Temperature Units'");
assertThat(all.get(0).getLocationString()).contains("Observation.value.ofType(Quantity)");
// validate second error, it has similar message as first error, but location is different
assertThat(all.get(1).getMessage()).contains("Error processing unit 'Heck': The unit 'Heck' is unknown' at position 0 (for 'http://unitsofmeasure.org#Heck')");
assertThat(all.get(1).getLocationString()).contains("Observation.value.ofType(Quantity).code");
// validate third error
assertThat(all.get(2).getMessage()).contains("The value provided ('Heck') was not found in the value set 'Body Temperature Units'");
assertThat(all.get(2).getLocationString()).contains("Observation.value.ofType(Quantity).code");
}
@Test
@ -1600,9 +1645,10 @@ public class FhirInstanceValidatorR4Test extends BaseValidationTestWithInlineMoc
"}";
ValidationResult output = myFhirValidator.validateWithResult(input);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
assertThat(errors.size()).as(errors.toString()).isEqualTo(2);
assertThat(errors.get(1).getMessage()).contains("The value provided ('BLAH') was not found in the value set 'CurrencyCode' (http://hl7.org/fhir/ValueSet/currencies|4.0.1)");
assertThat(errors.get(1).getMessage()).contains("error message = Unknown code \"urn:iso:std:iso:4217#BLAH\"");
assertThat(errors.size()).as(errors.toString()).isEqualTo(3);
assertThat(errors.get(1).getMessage()).contains("Unknown code 'urn:iso:std:iso:4217#BLAH'");
assertThat(errors.get(2).getMessage()).contains("The value provided ('BLAH') was not found in the value set 'CurrencyCode' (http://hl7.org/fhir/ValueSet/currencies|4.0.1)");
assertThat(errors.get(2).getMessage()).contains("error message = Unknown code \"urn:iso:std:iso:4217#BLAH\"");
}

View File

@ -21,6 +21,8 @@ import ca.uhn.fhir.rest.client.api.IHttpResponse;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import ca.uhn.fhir.util.ParametersUtil;
import com.google.common.collect.Lists;
@ -45,11 +47,15 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -119,6 +125,52 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat
assertNull(outcome);
}
public static Stream<Arguments> getRemoteTerminologyServerResponses() {
return Stream.of(
Arguments.of(new ResourceNotFoundException("System Not Present"), "404 Not Found: System Not Present",
"Unknown code \"null#CODE\". The Remote Terminology server", null, null),
Arguments.of(new InvalidRequestException("Invalid Request"), "400 Bad Request: Invalid Request",
"Unknown code \"null#CODE\". The Remote Terminology server", null, null),
Arguments.of(new ResourceNotFoundException("System Not Present"), "404 Not Found: System Not Present",
"Unknown code \"NotFoundSystem#CODE\". The Remote Terminology server", "NotFoundSystem", null),
Arguments.of(new InvalidRequestException("Invalid Request"), "400 Bad Request: Invalid Request",
"Unknown code \"InvalidSystem#CODE\". The Remote Terminology server", "InvalidSystem", null),
Arguments.of(new ResourceNotFoundException("System Not Present"), "404 Not Found: System Not Present",
"Unknown code \"null#CODE\" for ValueSet with URL \"NotFoundValueSetUrl\". The Remote Terminology server",
null, "NotFoundValueSetUrl"),
Arguments.of(new InvalidRequestException("Invalid Request"), "400 Bad Request: Invalid Request",
"Unknown code \"null#CODE\" for ValueSet with URL \"InvalidValueSetUrl\". The Remote Terminology server", null, "InvalidValueSetUrl"),
Arguments.of(new ResourceNotFoundException("System Not Present"), "404 Not Found: System Not Present",
"Unknown code \"NotFoundSystem#CODE\" for ValueSet with URL \"NotFoundValueSetUrl\". The Remote Terminology server",
"NotFoundSystem", "NotFoundValueSetUrl"),
Arguments.of(new InvalidRequestException("Invalid Request"), "400 Bad Request: Invalid Request",
"Unknown code \"InvalidSystem#CODE\" for ValueSet with URL \"InvalidValueSetUrl\". The Remote Terminology server", "InvalidSystem", "InvalidValueSetUrl")
);
}
@ParameterizedTest
@MethodSource(value = "getRemoteTerminologyServerResponses")
public void testValidateCode_codeSystemAndValueSetUrlAreIncorrect_returnsValidationResultWithError(Exception theException,
String theServerMessage,
String theValidationMessage,
String theCodeSystem,
String theValueSetUrl) {
myCodeSystemProvider.myNextValidateCodeException = theException;
myValueSetProvider.myNextValidateCodeException = theException;
IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(null, null, theCodeSystem, CODE, DISPLAY, theValueSetUrl);
validateValidationErrorResult(outcome, theValidationMessage, theServerMessage);
}
private static void validateValidationErrorResult(IValidationSupport.CodeValidationResult outcome, String... theMessages) {
assertNotNull(outcome);
assertEquals(IValidationSupport.IssueSeverity.ERROR, outcome.getSeverity());
assertNotNull(outcome.getMessage());
for (String message : theMessages) {
assertTrue(outcome.getMessage().contains(message));
}
}
@Test
public void testValidateCode_forValueSet_returnsCorrectly() {
createNextValueSetReturnParameters(true, DISPLAY, null);
@ -209,20 +261,6 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat
assertNull(myValueSetProvider.myLastValueSet);
}
/**
* Remote terminology services shouldn't be used to validate codes with an implied system
*/
@Test
public void testValidateCodeInValueSet_InferSystem() {
createNextValueSetReturnParameters(true, DISPLAY, null);
ValueSet valueSet = new ValueSet();
valueSet.setUrl(VALUE_SET_URL);
IValidationSupport.CodeValidationResult outcome = mySvc.validateCodeInValueSet(null, new ConceptValidationOptions().setInferSystem(true), null, CODE, DISPLAY, valueSet);
assertNull(outcome);
}
@Test
public void testTranslateCode_AllInParams_AllOutParams() {
myConceptMapProvider.myNextReturnParams = new Parameters();
@ -342,32 +380,46 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat
IValidationSupport.CodeValidationResult outcome = mySvc.validateCodeInValueSet(null,
new ConceptValidationOptions().setInferSystem(true), null, CODE, DISPLAY, valueSet);
// validate service doesn't do early return (as when no code system is present)
// validate service doesn't return error message (as when no code system is present)
assertNotNull(outcome);
assertNull(outcome.getMessage());
assertTrue(outcome.isOk());
}
@Nested
public class MultiComposeIncludeValueSet {
@Test
public void SystemNotPresentReturnsNull() {
public static Stream<Arguments> getRemoteTerminologyServerExceptions() {
return Stream.of(
Arguments.of(new ResourceNotFoundException("System Not Present"), "404 Not Found: System Not Present"),
Arguments.of(new InvalidRequestException("Invalid Request"), "400 Bad Request: Invalid Request")
);
}
@ParameterizedTest
@MethodSource(value = "getRemoteTerminologyServerExceptions")
public void systemNotPresent_returnsValidationResultWithError(Exception theException, String theServerMessage) {
myValueSetProvider.myNextValidateCodeException = theException;
createNextValueSetReturnParameters(true, DISPLAY, null);
ValueSet valueSet = new ValueSet();
valueSet.setUrl(VALUE_SET_URL);
valueSet.setCompose(new ValueSet.ValueSetComposeComponent().setInclude(
Lists.newArrayList(new ValueSet.ConceptSetComponent(), new ValueSet.ConceptSetComponent()) ));
Lists.newArrayList(new ValueSet.ConceptSetComponent(), new ValueSet.ConceptSetComponent())));
IValidationSupport.CodeValidationResult outcome = mySvc.validateCodeInValueSet(null,
new ConceptValidationOptions().setInferSystem(true), null, CODE, DISPLAY, valueSet);
assertNull(outcome);
String unknownCodeForValueSetError = "Unknown code \"null#CODE\" for ValueSet with URL \"http://value.set/url\". The Remote Terminology server http://";
validateValidationErrorResult(outcome, unknownCodeForValueSetError, theServerMessage);
}
@Test
public void SystemPresentCodeNotPresentReturnsNull() {
@ParameterizedTest
@MethodSource(value = "getRemoteTerminologyServerExceptions")
public void systemPresentCodeNotPresent_returnsValidationResultWithError(Exception theException, String theServerMessage) {
myValueSetProvider.myNextValidateCodeException = theException;
createNextValueSetReturnParameters(true, DISPLAY, null);
ValueSet valueSet = new ValueSet();
@ -377,12 +429,13 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat
valueSet.setCompose(new ValueSet.ValueSetComposeComponent().setInclude(
Lists.newArrayList(
new ValueSet.ConceptSetComponent().setSystem(systemUrl),
new ValueSet.ConceptSetComponent().setSystem(systemUrl2)) ));
new ValueSet.ConceptSetComponent().setSystem(systemUrl2))));
IValidationSupport.CodeValidationResult outcome = mySvc.validateCodeInValueSet(null,
new ConceptValidationOptions().setInferSystem(true), null, CODE, DISPLAY, valueSet);
assertNull(outcome);
String unknownCodeForValueSetError = "Unknown code \"null#CODE\" for ValueSet with URL \"http://value.set/url\". The Remote Terminology server http://";
validateValidationErrorResult(outcome, unknownCodeForValueSetError, theServerMessage);
}
@ -526,6 +579,7 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat
static private class MyCodeSystemProvider implements IResourceProvider {
private SummaryEnum myLastSummaryParam;
private Exception myNextValidateCodeException;
private UriParam myLastUrlParam;
private List<CodeSystem> myNextReturnCodeSystems;
private UriType mySystemUrl;
@ -548,9 +602,12 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat
@OperationParam(name = "url", min = 0, max = 1) UriType theSystem,
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay
) {
) throws Exception {
myCode = theCode;
mySystemUrl = theSystem;
if (myNextValidateCodeException != null) {
throw myNextValidateCodeException;
}
return myNextValidationResult.toParameters(ourCtx);
}
@ -566,6 +623,7 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat
private static class MyValueSetProvider implements IResourceProvider {
private Parameters myNextReturnParams;
private Exception myNextValidateCodeException;
private List<ValueSet> myNextReturnValueSets;
private UriType myLastUrl;
private CodeType myLastCode;
@ -589,13 +647,16 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat
@OperationParam(name = "system", min = 0, max = 1) UriType theSystem,
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay,
@OperationParam(name = "valueSet") ValueSet theValueSet
) {
) throws Exception {
myInvocationCount++;
myLastUrl = theValueSetUrl;
myLastCode = theCode;
myLastSystem = theSystem;
myLastDisplay = theDisplay;
myLastValueSet = theValueSet;
if (myNextValidateCodeException != null) {
throw myNextValidateCodeException;
}
return myNextReturnParams;
}

View File

@ -1216,7 +1216,8 @@ public class FhirInstanceValidatorR4BTest extends BaseValidationTestWithInlineMo
"</Observation>";
ValidationResult output = myFhirValidator.validateWithResult(input);
logResultsAndReturnAll(output);
assertEquals("The value provided ('notvalidcode') was not found in the value set 'ObservationStatus' (http://hl7.org/fhir/ValueSet/observation-status|4.3.0), and a code is required from this value set (error message = Unknown code 'notvalidcode' for in-memory expansion of ValueSet 'http://hl7.org/fhir/ValueSet/observation-status')", output.getMessages().get(0).getMessage());
assertThat(output.getMessages().get(0).getMessage()).contains("Unknown code 'http://hl7.org/fhir/observation-status#notvalidcode'");
assertThat(output.getMessages().get(1).getMessage()).contains("The value provided ('notvalidcode') was not found in the value set 'ObservationStatus' (http://hl7.org/fhir/ValueSet/observation-status|4.3.0), and a code is required from this value set (error message = Unknown code 'http://hl7.org/fhir/observation-status#notvalidcode' for in-memory expansion of ValueSet 'http://hl7.org/fhir/ValueSet/observation-status')");
}
@Test
@ -1366,10 +1367,19 @@ public class FhirInstanceValidatorR4BTest extends BaseValidationTestWithInlineMo
input.getValueQuantity().setCode("Heck");
output = myFhirValidator.validateWithResult(input);
all = logResultsAndReturnNonInformationalOnes(output);
assertThat(all).hasSize(2);
assertThat(all.get(0).getMessage()).contains("The Coding provided (http://unitsofmeasure.org#Heck) was not found in the value set 'Vital Signs Units' (http://hl7.org/fhir/ValueSet/ucum-vitals-common|4.3.0)");
assertThat(all.get(1).getMessage()).contains("The value provided ('Heck') was not found in the value set 'Body Temperature Units'");
assertThat(all).hasSize(3);
// validate first error, in R4B (as opposed to R4) Observation.value.ofType(Quantity) has ValueSet binding,
// so first error has `Unknown code for ValueSet` error message
assertThat(all.get(0).getMessage()).contains("The Coding provided (http://unitsofmeasure.org#Heck) was not found in the value set 'Vital Signs Units' " +
"(http://hl7.org/fhir/ValueSet/ucum-vitals-common|4.3.0), and a code should come from this value set unless it has no suitable code (note that the validator cannot judge what is suitable). " +
" (error message = Unknown code 'http://unitsofmeasure.org#Heck' for in-memory expansion of ValueSet 'http://hl7.org/fhir/ValueSet/ucum-vitals-common')");
assertThat(all.get(0).getLocationString()).contains("Observation.value.ofType(Quantity)");
// validate second error
assertThat(all.get(1).getMessage()).contains("Error processing unit 'Heck': The unit 'Heck' is unknown' at position 0 (for 'http://unitsofmeasure.org#Heck')");
assertThat(all.get(1).getLocationString()).contains("Observation.value.ofType(Quantity).code");
// validate third error
assertThat(all.get(2).getMessage()).contains("The value provided ('Heck') was not found in the value set 'Body Temperature Units'");
assertThat(all.get(2).getLocationString()).contains("Observation.value.ofType(Quantity).code");
}
@Test
@ -1480,8 +1490,9 @@ public class FhirInstanceValidatorR4BTest extends BaseValidationTestWithInlineMo
}""";
ValidationResult output = myFhirValidator.validateWithResult(input);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
assertThat(errors.size()).as(errors.toString()).isEqualTo(2);
assertThat(errors.get(1).getMessage()).contains("The value provided ('BLAH') was not found in the value set 'CurrencyCode' (http://hl7.org/fhir/ValueSet/currencies|4.3.0), and a code is required from this value set");
assertThat(errors.size()).as(errors.toString()).isEqualTo(3);
assertThat(errors.get(1).getMessage()).contains("Unknown code 'urn:iso:std:iso:4217#BLAH'");
assertThat(errors.get(2).getMessage()).contains("The value provided ('BLAH') was not found in the value set 'CurrencyCode' (http://hl7.org/fhir/ValueSet/currencies|4.3.0), and a code is required from this value set");
}

View File

@ -894,7 +894,8 @@ public class FhirInstanceValidatorR5Test extends BaseValidationTestWithInlineMoc
"</Observation>";
ValidationResult output = myVal.validateWithResult(input);
logResultsAndReturnAll(output);
assertThat(output.getMessages().get(0).getMessage()).contains("The value provided ('notvalidcode') was not found in the value set 'Observation Status' (http://hl7.org/fhir/ValueSet/observation-status|5.0.0), and a code is required from this value set (error message = Unknown code 'notvalidcode' for in-memory expansion of ValueSet 'http://hl7.org/fhir/ValueSet/observation-status')");
assertThat(output.getMessages().get(0).getMessage()).contains("Unknown code 'http://hl7.org/fhir/observation-status#notvalidcode'");
assertThat(output.getMessages().get(1).getMessage()).contains("The value provided ('notvalidcode') was not found in the value set 'Observation Status' (http://hl7.org/fhir/ValueSet/observation-status|5.0.0), and a code is required from this value set (error message = Unknown code 'http://hl7.org/fhir/observation-status#notvalidcode' for in-memory expansion of ValueSet 'http://hl7.org/fhir/ValueSet/observation-status')");
}