Add support for response parameter issues for `validate-code` operation (#6360)

* Add support for response parameter issues for validate-code operation. Implement workaround for missing error for invalid codes by adding an issue when it is absent.

* address minor code review comments

* revert functionality of adding original code from remote validate-code call to the CodeValidationResult

* revert change to FhirInstanceValidatorR4Test

* Revert some of the code review addressal after responding.

* Remove populating message in success scenarios.

* Fix compilation failure in test after reverting method rename.

* Revert change to populate the code from the response again as it implies more refactoring.

---------

Co-authored-by: nathaniel.doef <nathaniel.doef@smilecdr.com>
This commit is contained in:
Martha Mitran 2024-10-16 07:16:41 -07:00 committed by Tadgh
parent b911ec77e1
commit 8fc5274894
29 changed files with 1817 additions and 1217 deletions

View File

@ -727,7 +727,7 @@ public interface IValidationSupport {
return this; return this;
} }
String getCodeSystemName() { public String getCodeSystemName() {
return myCodeSystemName; return myCodeSystemName;
} }

View File

@ -43,6 +43,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -58,20 +59,20 @@ public class ParametersUtil {
public static Optional<String> getNamedParameterValueAsString( public static Optional<String> getNamedParameterValueAsString(
FhirContext theCtx, IBaseParameters theParameters, String theParameterName) { FhirContext theCtx, IBaseParameters theParameters, String theParameterName) {
Function<IPrimitiveType<?>, String> mapper = t -> defaultIfBlank(t.getValueAsString(), null); Function<IPrimitiveType<?>, String> mapper = t -> defaultIfBlank(t.getValueAsString(), null);
return extractNamedParameters(theCtx, theParameters, theParameterName, mapper).stream() return extractNamedParameterValues(theCtx, theParameters, theParameterName, mapper).stream()
.findFirst(); .findFirst();
} }
public static List<String> getNamedParameterValuesAsString( public static List<String> getNamedParameterValuesAsString(
FhirContext theCtx, IBaseParameters theParameters, String theParameterName) { FhirContext theCtx, IBaseParameters theParameters, String theParameterName) {
Function<IPrimitiveType<?>, String> mapper = t -> defaultIfBlank(t.getValueAsString(), null); Function<IPrimitiveType<?>, String> mapper = t -> defaultIfBlank(t.getValueAsString(), null);
return extractNamedParameters(theCtx, theParameters, theParameterName, mapper); return extractNamedParameterValues(theCtx, theParameters, theParameterName, mapper);
} }
public static List<Integer> getNamedParameterValuesAsInteger( public static List<Integer> getNamedParameterValuesAsInteger(
FhirContext theCtx, IBaseParameters theParameters, String theParameterName) { FhirContext theCtx, IBaseParameters theParameters, String theParameterName) {
Function<IPrimitiveType<?>, Integer> mapper = t -> (Integer) t.getValue(); Function<IPrimitiveType<?>, Integer> mapper = t -> (Integer) t.getValue();
return extractNamedParameters(theCtx, theParameters, theParameterName, mapper); return extractNamedParameterValues(theCtx, theParameters, theParameterName, mapper);
} }
public static Optional<Integer> getNamedParameterValueAsInteger( public static Optional<Integer> getNamedParameterValueAsInteger(
@ -80,6 +81,19 @@ public class ParametersUtil {
.findFirst(); .findFirst();
} }
/**
* Returns the resource within a parameter.
* @param theCtx thr FHIR context
* @param theParameters the parameters instance where to look for the resource
* @param theParameterName the parameter name
* @return the resource
*/
public static Optional<IBaseResource> getNamedParameterResource(
FhirContext theCtx, IBaseParameters theParameters, String theParameterName) {
return extractNamedParameterResources(theCtx, theParameters, theParameterName).stream()
.findFirst();
}
public static Optional<IBase> getNamedParameter( public static Optional<IBase> getNamedParameter(
FhirContext theCtx, IBaseResource theParameters, String theParameterName) { FhirContext theCtx, IBaseResource theParameters, String theParameterName) {
return getNamedParameters(theCtx, theParameters, theParameterName).stream() return getNamedParameters(theCtx, theParameters, theParameterName).stream()
@ -153,7 +167,7 @@ public class ParametersUtil {
.map(t -> (Integer) t); .map(t -> (Integer) t);
} }
private static <T> List<T> extractNamedParameters( private static <T> List<T> extractNamedParameterValues(
FhirContext theCtx, FhirContext theCtx,
IBaseParameters theParameters, IBaseParameters theParameters,
String theParameterName, String theParameterName,
@ -170,7 +184,25 @@ public class ParametersUtil {
.filter(t -> t instanceof IPrimitiveType<?>) .filter(t -> t instanceof IPrimitiveType<?>)
.map(t -> ((IPrimitiveType<?>) t)) .map(t -> ((IPrimitiveType<?>) t))
.map(theMapper) .map(theMapper)
.filter(t -> t != null) .filter(Objects::nonNull)
.forEach(retVal::add);
}
return retVal;
}
private static List<IBaseResource> extractNamedParameterResources(
FhirContext theCtx, IBaseParameters theParameters, String theParameterName) {
List<IBaseResource> retVal = new ArrayList<>();
List<IBase> namedParameters = getNamedParameters(theCtx, theParameters, theParameterName);
for (IBase nextParameter : namedParameters) {
BaseRuntimeElementCompositeDefinition<?> nextParameterDef =
(BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(nextParameter.getClass());
BaseRuntimeChildDefinition resourceChild = nextParameterDef.getChildByName("resource");
List<IBase> resourceValues = resourceChild.getAccessor().getValues(nextParameter);
resourceValues.stream()
.filter(IBaseResource.class::isInstance)
.map(t -> ((IBaseResource) t))
.forEach(retVal::add); .forEach(retVal::add);
} }
return retVal; return retVal;

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 6359
title: "After upgrading org.hl7.fhir.core from 6.1.2.2 to 6.3.11, the $validate-code operation stopped returning an
error for invalid codes using remote terminology. This has been fixed."

View File

@ -0,0 +1,5 @@
---
type: add
issue: 6359
title: "Remote Terminology validation has been enhanced to support output parameter `issues` for the $validate-code
operation."

View File

@ -1,7 +1,5 @@
package ca.uhn.fhir.jpa.provider.r4; package ca.uhn.fhir.jpa.provider.r4;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.config.JpaConfig; import ca.uhn.fhir.jpa.config.JpaConfig;
import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.model.util.JpaConstants;
@ -15,6 +13,7 @@ import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import jakarta.servlet.http.HttpServletRequest;
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
@ -34,74 +33,81 @@ import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import jakarta.servlet.http.HttpServletRequest;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.fail;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
/* /*
* This set of Unit Tests instantiates and injects an instance of * This set of integration tests that instantiates and injects an instance of
* {@link org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport} * {@link org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport}
* into the ValidationSupportChain, which tests the logic of dynamically selecting the correct Remote Terminology * into the ValidationSupportChain, which tests the logic of dynamically selecting the correct Remote Terminology
* implementation. It also exercises the code found in * implementation. It also exercises the code found in
* {@link org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport#invokeRemoteValidateCode} * {@link org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport#invokeRemoteValidateCode}
*/ */
public class ResourceProviderR4RemoteTerminologyTest extends BaseResourceProviderR4Test { public class ValidateCodeOperationWithRemoteTerminologyR4Test extends BaseResourceProviderR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderR4RemoteTerminologyTest.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidateCodeOperationWithRemoteTerminologyR4Test.class);
private static final String DISPLAY = "DISPLAY"; private static final String DISPLAY = "DISPLAY";
private static final String DISPLAY_BODY_MASS_INDEX = "Body mass index (BMI) [Ratio]"; private static final String DISPLAY_BODY_MASS_INDEX = "Body mass index (BMI) [Ratio]";
private static final String CODE_BODY_MASS_INDEX = "39156-5"; private static final String CODE_BODY_MASS_INDEX = "39156-5";
private static final String CODE_SYSTEM_V2_0247_URI = "http://terminology.hl7.org/CodeSystem/v2-0247"; private static final String CODE_SYSTEM_V2_0247_URI = "http://terminology.hl7.org/CodeSystem/v2-0247";
private static final String INVALID_CODE_SYSTEM_URI = "http://terminology.hl7.org/CodeSystem/INVALID-CODESYSTEM"; private static final String INVALID_CODE_SYSTEM_URI = "http://terminology.hl7.org/CodeSystem/INVALID-CODESYSTEM";
private static final String UNKNOWN_VALUE_SYSTEM_URI = "http://hl7.org/fhir/ValueSet/unknown-value-set"; private static final String UNKNOWN_VALUE_SYSTEM_URI = "http://hl7.org/fhir/ValueSet/unknown-value-set";
private static FhirContext ourCtx = FhirContext.forR4(); private static final FhirContext ourCtx = FhirContext.forR4();
private MyCodeSystemProvider myCodeSystemProvider = new MyCodeSystemProvider();
private MyValueSetProvider myValueSetProvider = new MyValueSetProvider();
@RegisterExtension @RegisterExtension
public RestfulServerExtension myRestfulServerExtension = new RestfulServerExtension(ourCtx, myCodeSystemProvider, protected static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx);
myValueSetProvider);
private RemoteTerminologyServiceValidationSupport mySvc; private RemoteTerminologyServiceValidationSupport mySvc;
private MyCodeSystemProvider myCodeSystemProvider;
private MyValueSetProvider myValueSetProvider;
@Autowired @Autowired
@Qualifier(JpaConfig.JPA_VALIDATION_SUPPORT_CHAIN) @Qualifier(JpaConfig.JPA_VALIDATION_SUPPORT_CHAIN)
private ValidationSupportChain myValidationSupportChain; private ValidationSupportChain myValidationSupportChain;
@BeforeEach @BeforeEach
public void before_addRemoteTerminologySupport() throws Exception { public void before() throws Exception {
String baseUrl = "http://localhost:" + myRestfulServerExtension.getPort(); String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort();
mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl); mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl);
myValidationSupportChain.addValidationSupport(0, mySvc); myValidationSupportChain.addValidationSupport(0, mySvc);
myCodeSystemProvider = new MyCodeSystemProvider();
myValueSetProvider = new MyValueSetProvider();
ourRestfulServerExtension.registerProvider(myCodeSystemProvider);
ourRestfulServerExtension.registerProvider(myValueSetProvider);
} }
@AfterEach @AfterEach
public void after_removeRemoteTerminologySupport() { public void after() {
myValidationSupportChain.removeValidationSupport(mySvc); myValidationSupportChain.removeValidationSupport(mySvc);
myRestfulServerExtension.getRestfulServer().getInterceptorService().unregisterAllInterceptors(); ourRestfulServerExtension.getRestfulServer().getInterceptorService().unregisterAllInterceptors();
ourRestfulServerExtension.unregisterProvider(myCodeSystemProvider);
ourRestfulServerExtension.unregisterProvider(myValueSetProvider);
} }
@Test @Test
public void testValidateCodeOperationOnCodeSystem_byCodingAndUrlWhereSystemIsDifferent_throwsException() { public void validateCodeOperationOnCodeSystem_byCodingAndUrlWhereSystemIsDifferent_throwsException() {
assertThatExceptionOfType(InvalidRequestException.class).isThrownBy(() -> { assertThatExceptionOfType(InvalidRequestException.class).isThrownBy(() -> myClient
Parameters respParam = myClient
.operation() .operation()
.onType(CodeSystem.class) .onType(CodeSystem.class)
.named(JpaConstants.OPERATION_VALIDATE_CODE) .named(JpaConstants.OPERATION_VALIDATE_CODE)
.withParameter(Parameters.class, "coding", new Coding().setSystem(CODE_SYSTEM_V2_0247_URI).setCode("P")) .withParameter(Parameters.class, "coding", new Coding().setSystem(CODE_SYSTEM_V2_0247_URI).setCode("P"))
.andParameter("url", new UriType(INVALID_CODE_SYSTEM_URI)) .andParameter("url", new UriType(INVALID_CODE_SYSTEM_URI))
.execute(); .execute());
});
} }
@Test @Test
public void testValidateCodeOperationOnCodeSystem_byCodingAndUrl_usingBuiltInCodeSystems() { public void validateCodeOperationOnCodeSystem_byCodingAndUrl_usingBuiltInCodeSystems() {
myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>(); myCodeSystemProvider.myReturnCodeSystems = new ArrayList<>();
myCodeSystemProvider.myNextReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/v2-0247")); myCodeSystemProvider.myReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/v2-0247"));
createNextCodeSystemReturnParameters(true, DISPLAY, null); myCodeSystemProvider.myReturnParams = new Parameters();
myCodeSystemProvider.myReturnParams.addParameter("result", true);
myCodeSystemProvider.myReturnParams.addParameter("display", DISPLAY);
logAllConcepts(); logAllConcepts();
@ -116,13 +122,13 @@ public class ResourceProviderR4RemoteTerminologyTest extends BaseResourceProvide
String resp = myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); String resp = myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp); ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameterValue("result")).booleanValue()); assertTrue(((BooleanType) respParam.getParameterValue("result")).booleanValue());
assertEquals(DISPLAY, respParam.getParameterValue("display").toString()); assertEquals(DISPLAY, respParam.getParameterValue("display").toString());
} }
@Test @Test
public void testValidateCodeOperationOnCodeSystem_byCodingAndUrlWhereCodeSystemIsUnknown_returnsFalse() { public void validateCodeOperationOnCodeSystem_byCodingAndUrlWhereCodeSystemIsUnknown_returnsFalse() {
myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>(); myCodeSystemProvider.myReturnCodeSystems = new ArrayList<>();
Parameters respParam = myClient Parameters respParam = myClient
.operation() .operation()
@ -142,7 +148,7 @@ public class ResourceProviderR4RemoteTerminologyTest extends BaseResourceProvide
} }
@Test @Test
public void testValidateCodeOperationOnValueSet_byCodingAndUrlWhereSystemIsDifferent_throwsException() { public void validateCodeOperationOnValueSet_byCodingAndUrlWhereSystemIsDifferent_throwsException() {
try { try {
myClient.operation() myClient.operation()
.onType(ValueSet.class) .onType(ValueSet.class)
@ -159,12 +165,14 @@ public class ResourceProviderR4RemoteTerminologyTest extends BaseResourceProvide
} }
@Test @Test
public void testValidateCodeOperationOnValueSet_byUrlAndSystem_usingBuiltInCodeSystems() { public void validateCodeOperationOnValueSet_byUrlAndSystem_usingBuiltInCodeSystems() {
myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>(); myCodeSystemProvider.myReturnCodeSystems = new ArrayList<>();
myCodeSystemProvider.myNextReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/list-example-use-codes")); myCodeSystemProvider.myReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/list-example-use-codes"));
myValueSetProvider.myNextReturnValueSets = new ArrayList<>(); myValueSetProvider.myReturnValueSets = new ArrayList<>();
myValueSetProvider.myNextReturnValueSets.add((ValueSet) new ValueSet().setId("ValueSet/list-example-codes")); myValueSetProvider.myReturnValueSets.add((ValueSet) new ValueSet().setId("ValueSet/list-example-codes"));
createNextValueSetReturnParameters(true, DISPLAY, null); myValueSetProvider.myReturnParams = new Parameters();
myValueSetProvider.myReturnParams.addParameter("result", true);
myValueSetProvider.myReturnParams.addParameter("display", DISPLAY);
Parameters respParam = myClient Parameters respParam = myClient
.operation() .operation()
@ -179,17 +187,19 @@ public class ResourceProviderR4RemoteTerminologyTest extends BaseResourceProvide
String resp = myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); String resp = myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp); ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameterValue("result")).booleanValue()); assertTrue(((BooleanType) respParam.getParameterValue("result")).booleanValue());
assertEquals(DISPLAY, respParam.getParameterValue("display").toString()); assertEquals(DISPLAY, respParam.getParameterValue("display").toString());
} }
@Test @Test
public void testValidateCodeOperationOnValueSet_byUrlSystemAndCode() { public void validateCodeOperationOnValueSet_byUrlSystemAndCode() {
myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>(); myCodeSystemProvider.myReturnCodeSystems = new ArrayList<>();
myCodeSystemProvider.myNextReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/list-example-use-codes")); myCodeSystemProvider.myReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/list-example-use-codes"));
myValueSetProvider.myNextReturnValueSets = new ArrayList<>(); myValueSetProvider.myReturnValueSets = new ArrayList<>();
myValueSetProvider.myNextReturnValueSets.add((ValueSet) new ValueSet().setId("ValueSet/list-example-codes")); myValueSetProvider.myReturnValueSets.add((ValueSet) new ValueSet().setId("ValueSet/list-example-codes"));
createNextValueSetReturnParameters(true, DISPLAY_BODY_MASS_INDEX, null); myValueSetProvider.myReturnParams = new Parameters();
myValueSetProvider.myReturnParams.addParameter("result", true);
myValueSetProvider.myReturnParams.addParameter("display", DISPLAY_BODY_MASS_INDEX);
Parameters respParam = myClient Parameters respParam = myClient
.operation() .operation()
@ -203,13 +213,13 @@ public class ResourceProviderR4RemoteTerminologyTest extends BaseResourceProvide
String resp = myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); String resp = myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp); ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameterValue("result")).booleanValue()); assertTrue(((BooleanType) respParam.getParameterValue("result")).booleanValue());
assertEquals(DISPLAY_BODY_MASS_INDEX, respParam.getParameterValue("display").toString()); assertEquals(DISPLAY_BODY_MASS_INDEX, respParam.getParameterValue("display").toString());
} }
@Test @Test
public void testValidateCodeOperationOnValueSet_byCodingAndUrlWhereValueSetIsUnknown_returnsFalse() { public void validateCodeOperationOnValueSet_byCodingAndUrlWhereValueSetIsUnknown_returnsFalse() {
myValueSetProvider.myNextReturnValueSets = new ArrayList<>(); myValueSetProvider.myReturnValueSets = new ArrayList<>();
Parameters respParam = myClient Parameters respParam = myClient
.operation() .operation()
@ -228,33 +238,10 @@ public class ResourceProviderR4RemoteTerminologyTest extends BaseResourceProvide
" - Unknown or unusable ValueSet[" + UNKNOWN_VALUE_SYSTEM_URI + "]"); " - Unknown or unusable ValueSet[" + UNKNOWN_VALUE_SYSTEM_URI + "]");
} }
private void createNextCodeSystemReturnParameters(boolean theResult, String theDisplay, String theMessage) { @SuppressWarnings("unused")
myCodeSystemProvider.myNextReturnParams = new Parameters();
myCodeSystemProvider.myNextReturnParams.addParameter("result", theResult);
myCodeSystemProvider.myNextReturnParams.addParameter("display", theDisplay);
if (theMessage != null) {
myCodeSystemProvider.myNextReturnParams.addParameter("message", theMessage);
}
}
private void createNextValueSetReturnParameters(boolean theResult, String theDisplay, String theMessage) {
myValueSetProvider.myNextReturnParams = new Parameters();
myValueSetProvider.myNextReturnParams.addParameter("result", theResult);
myValueSetProvider.myNextReturnParams.addParameter("display", theDisplay);
if (theMessage != null) {
myValueSetProvider.myNextReturnParams.addParameter("message", theMessage);
}
}
private static class MyCodeSystemProvider implements IResourceProvider { private static class MyCodeSystemProvider implements IResourceProvider {
private List<CodeSystem> myReturnCodeSystems;
private UriParam myLastUrlParam; private Parameters myReturnParams;
private List<CodeSystem> myNextReturnCodeSystems;
private int myInvocationCount;
private UriType myLastUrl;
private CodeType myLastCode;
private StringType myLastDisplay;
private Parameters myNextReturnParams;
@Operation(name = "validate-code", idempotent = true, returnParameters = { @Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1), @OperationParam(name = "result", type = BooleanType.class, min = 1),
@ -268,18 +255,13 @@ public class ResourceProviderR4RemoteTerminologyTest extends BaseResourceProvide
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode, @OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay @OperationParam(name = "display", min = 0, max = 1) StringType theDisplay
) { ) {
myInvocationCount++; return myReturnParams;
myLastUrl = theCodeSystemUrl;
myLastCode = theCode;
myLastDisplay = theDisplay;
return myNextReturnParams;
} }
@Search @Search
public List<CodeSystem> find(@RequiredParam(name = "url") UriParam theUrlParam) { public List<CodeSystem> find(@RequiredParam(name = "url") UriParam theUrlParam) {
myLastUrlParam = theUrlParam; assert myReturnCodeSystems != null;
assert myNextReturnCodeSystems != null; return myReturnCodeSystems;
return myNextReturnCodeSystems;
} }
@Override @Override
@ -288,16 +270,10 @@ public class ResourceProviderR4RemoteTerminologyTest extends BaseResourceProvide
} }
} }
@SuppressWarnings("unused")
private static class MyValueSetProvider implements IResourceProvider { private static class MyValueSetProvider implements IResourceProvider {
private Parameters myNextReturnParams; private Parameters myReturnParams;
private List<ValueSet> myNextReturnValueSets; private List<ValueSet> myReturnValueSets;
private UriType myLastUrl;
private CodeType myLastCode;
private int myInvocationCount;
private UriType myLastSystem;
private StringType myLastDisplay;
private ValueSet myLastValueSet;
private UriParam myLastUrlParam;
@Operation(name = "validate-code", idempotent = true, returnParameters = { @Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1), @OperationParam(name = "result", type = BooleanType.class, min = 1),
@ -313,20 +289,13 @@ public class ResourceProviderR4RemoteTerminologyTest extends BaseResourceProvide
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay, @OperationParam(name = "display", min = 0, max = 1) StringType theDisplay,
@OperationParam(name = "valueSet") ValueSet theValueSet @OperationParam(name = "valueSet") ValueSet theValueSet
) { ) {
myInvocationCount++; return myReturnParams;
myLastUrl = theValueSetUrl;
myLastCode = theCode;
myLastSystem = theSystem;
myLastDisplay = theDisplay;
myLastValueSet = theValueSet;
return myNextReturnParams;
} }
@Search @Search
public List<ValueSet> find(@RequiredParam(name = "url") UriParam theUrlParam) { public List<ValueSet> find(@RequiredParam(name = "url") UriParam theUrlParam) {
myLastUrlParam = theUrlParam; assert myReturnValueSets != null;
assert myNextReturnValueSets != null; return myReturnValueSets;
return myNextReturnValueSets;
} }
@Override @Override

View File

@ -3,7 +3,9 @@ package ca.uhn.fhir.util;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.IntegerType; import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.StringType;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -17,7 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
public class ParametersUtilR4Test { public class ParametersUtilR4Test {
private static final String TEST_PERSON_ID = "Person/32768"; private static final String TEST_PERSON_ID = "Person/32768";
private static FhirContext ourFhirContext = FhirContext.forR4(); private static final FhirContext ourFhirContext = FhirContext.forR4Cached();
@Test @Test
public void testCreateParameters() { public void testCreateParameters() {
@ -47,7 +49,7 @@ public class ParametersUtilR4Test {
p.addParameter() p.addParameter()
.setValue(new StringType("VALUE4")); .setValue(new StringType("VALUE4"));
List<String> values = ParametersUtil.getNamedParameterValuesAsString(FhirContext.forR4(), p, "foo"); List<String> values = ParametersUtil.getNamedParameterValuesAsString(ourFhirContext, p, "foo");
assertThat(values).containsExactly("VALUE1", "VALUE2"); assertThat(values).containsExactly("VALUE1", "VALUE2");
} }
@ -58,7 +60,7 @@ public class ParametersUtilR4Test {
.setName("foo") .setName("foo")
.setValue(new IntegerType(123)); .setValue(new IntegerType(123));
Optional<Integer> value = ParametersUtil.getNamedParameterValueAsInteger(FhirContext.forR4(), p, "foo"); Optional<Integer> value = ParametersUtil.getNamedParameterValueAsInteger(ourFhirContext, p, "foo");
assertThat(value).isPresent(); assertThat(value).isPresent();
assertEquals(123, value.get().intValue()); assertEquals(123, value.get().intValue());
} }
@ -80,7 +82,7 @@ public class ParametersUtilR4Test {
@Test @Test
public void testAddPartDecimalNoScientificNotation() { public void testAddPartDecimalNoScientificNotation() {
// setup // setup
Double decimalValue = Double.valueOf("10000000"); double decimalValue = 10000000;
IBaseParameters parameters = ParametersUtil.newInstance(ourFhirContext); IBaseParameters parameters = ParametersUtil.newInstance(ourFhirContext);
IBase resultPart = ParametersUtil.addParameterToParameters(ourFhirContext, parameters, "link"); IBase resultPart = ParametersUtil.addParameterToParameters(ourFhirContext, parameters, "link");
@ -92,4 +94,20 @@ public class ParametersUtilR4Test {
List<String> results = ParametersUtil.getNamedParameterPartAsString(ourFhirContext, parameters, "link", "linkCreated"); List<String> results = ParametersUtil.getNamedParameterPartAsString(ourFhirContext, parameters, "link", "linkCreated");
assertEquals(expected, results.get(0)); assertEquals(expected, results.get(0));
} }
@Test
public void testGetNamedParameterResource() {
OperationOutcome outcome = new OperationOutcome();
outcome.addIssue().setSeverity(OperationOutcome.IssueSeverity.ERROR).setDiagnostics("An error was found.");
Parameters p = new Parameters();
p.addParameter().setName("foo").setResource(outcome);
p.addParameter().setName("bar").setValue(new StringType("value1"));
Optional<IBaseResource> fooResource = ParametersUtil.getNamedParameterResource(ourFhirContext,p, "foo");
assertThat(fooResource).isPresent();
assertThat(fooResource.get()).isEqualTo(outcome);
Optional<IBaseResource> barResource = ParametersUtil.getNamedParameterResource(ourFhirContext,p, "bar");
assertThat(barResource).isEmpty();
}
} }

View File

@ -22,12 +22,14 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Base; import org.hl7.fhir.r4.model.Base;
import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeType; import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent; import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent;
import org.hl7.fhir.r4.model.Property; import org.hl7.fhir.r4.model.Property;
@ -37,9 +39,14 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import static ca.uhn.fhir.util.ParametersUtil.getNamedParameterResource;
import static ca.uhn.fhir.util.ParametersUtil.getNamedParameterValueAsString;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -52,6 +59,9 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class RemoteTerminologyServiceValidationSupport extends BaseValidationSupport implements IValidationSupport { public class RemoteTerminologyServiceValidationSupport extends BaseValidationSupport implements IValidationSupport {
private static final Logger ourLog = LoggerFactory.getLogger(RemoteTerminologyServiceValidationSupport.class); private static final Logger ourLog = LoggerFactory.getLogger(RemoteTerminologyServiceValidationSupport.class);
public static final String ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM = "unknownCodeInSystem";
public static final String ERROR_CODE_UNKNOWN_CODE_IN_VALUE_SET = "unknownCodeInValueSet";
private String myBaseUrl; private String myBaseUrl;
private final List<Object> myClientInterceptors = new ArrayList<>(); private final List<Object> myClientInterceptors = new ArrayList<>();
@ -192,8 +202,8 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
// e.g. ClientResponseInterceptorModificationTemplate // e.g. ClientResponseInterceptorModificationTemplate
ourLog.error(e.getMessage(), e); ourLog.error(e.getMessage(), e);
LookupCodeResult result = LookupCodeResult.notFound(system, code); LookupCodeResult result = LookupCodeResult.notFound(system, code);
result.setErrorMessage( result.setErrorMessage(getErrorMessage(
getErrorMessage("unknownCodeInSystem", system, code, client.getServerBase(), e.getMessage())); ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM, system, code, getBaseUrl(), e.getMessage()));
return result; return result;
} }
if (outcome != null && !outcome.isEmpty()) { if (outcome != null && !outcome.isEmpty()) {
@ -575,6 +585,21 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
IGenericClient client = provideClient(); IGenericClient client = provideClient();
// this message builder can be removed once we introduce a parameter object like CodeValidationRequest
ValidationErrorMessageBuilder errorMessageBuilder = theServerMessage -> {
if (theValueSetUrl == null && theValueSet == null) {
return getErrorMessage(
ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM, theCodeSystem, theCode, getBaseUrl(), theServerMessage);
}
return getErrorMessage(
ERROR_CODE_UNKNOWN_CODE_IN_VALUE_SET,
theCodeSystem,
theCode,
theValueSetUrl,
getBaseUrl(),
theServerMessage);
};
IBaseParameters input = IBaseParameters input =
buildValidateCodeInputParameters(theCodeSystem, theCode, theDisplay, theValueSetUrl, theValueSet); buildValidateCodeInputParameters(theCodeSystem, theCode, theDisplay, theValueSetUrl, theValueSet);
@ -583,93 +608,167 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
resourceType = "CodeSystem"; resourceType = "CodeSystem";
} }
IBaseParameters output;
try { try {
output = client.operation() IBaseParameters output = client.operation()
.onType(resourceType) .onType(resourceType)
.named("validate-code") .named("validate-code")
.withParameters(input) .withParameters(input)
.execute(); .execute();
return createCodeValidationResult(output, errorMessageBuilder, theCode);
} catch (ResourceNotFoundException | InvalidRequestException ex) { } catch (ResourceNotFoundException | InvalidRequestException ex) {
ourLog.error(ex.getMessage(), ex); ourLog.error(ex.getMessage(), ex);
CodeValidationResult result = new CodeValidationResult(); String errorMessage = errorMessageBuilder.buildErrorMessage(ex.getMessage());
result.setSeverity(IssueSeverity.ERROR); CodeValidationIssueCode issueCode = ex instanceof ResourceNotFoundException
String errorMessage = buildErrorMessage( ? CodeValidationIssueCode.NOT_FOUND
theCodeSystem, theCode, theValueSetUrl, theValueSet, client.getServerBase(), ex.getMessage()); : CodeValidationIssueCode.CODE_INVALID;
result.setMessage(errorMessage); return createErrorCodeValidationResult(issueCode, errorMessage);
}
}
private CodeValidationResult createErrorCodeValidationResult(
CodeValidationIssueCode theIssueCode, String theMessage) {
IssueSeverity severity = IssueSeverity.ERROR;
return new CodeValidationResult()
.setSeverity(severity)
.setMessage(theMessage)
.addCodeValidationIssue(new CodeValidationIssue(
theMessage, severity, theIssueCode, CodeValidationIssueCoding.INVALID_CODE));
}
private CodeValidationResult createCodeValidationResult(
IBaseParameters theOutput, ValidationErrorMessageBuilder theMessageBuilder, String theCode) {
final FhirContext fhirContext = getFhirContext();
Optional<String> resultValue = getNamedParameterValueAsString(fhirContext, theOutput, "result");
if (!resultValue.isPresent()) {
throw new IllegalArgumentException(
Msg.code(2560) + "Parameter `result` is missing from the $validate-code response.");
}
boolean success = resultValue.get().equalsIgnoreCase("true");
CodeValidationResult result = new CodeValidationResult();
// TODO MM: avoid passing the code and only retrieve it from the response
// that implies larger changes, like adding the result boolean to CodeValidationResult
// since CodeValidationResult#isOk() relies on code being populated to determine the result/success
if (success) {
result.setCode(theCode);
}
Optional<String> systemValue = getNamedParameterValueAsString(fhirContext, theOutput, "system");
systemValue.ifPresent(result::setCodeSystemName);
Optional<String> versionValue = getNamedParameterValueAsString(fhirContext, theOutput, "version");
versionValue.ifPresent(result::setCodeSystemVersion);
Optional<String> displayValue = getNamedParameterValueAsString(fhirContext, theOutput, "display");
displayValue.ifPresent(result::setDisplay);
// in theory the message and the issues should not be populated when result=false
if (success) {
return result; return result;
} }
List<String> resultValues = ParametersUtil.getNamedParameterValuesAsString(getFhirContext(), output, "result"); // for now assume severity ERROR, we may need to process the following for success cases as well
if (resultValues.isEmpty() || isBlank(resultValues.get(0))) { result.setSeverity(IssueSeverity.ERROR);
return null;
}
Validate.isTrue(resultValues.size() == 1, "Response contained %d 'result' values", resultValues.size());
boolean success = "true".equalsIgnoreCase(resultValues.get(0)); Optional<String> messageValue = getNamedParameterValueAsString(fhirContext, theOutput, "message");
messageValue.ifPresent(value -> result.setMessage(theMessageBuilder.buildErrorMessage(value)));
CodeValidationResult retVal = new CodeValidationResult();
if (success) {
retVal.setCode(theCode);
List<String> displayValues =
ParametersUtil.getNamedParameterValuesAsString(getFhirContext(), output, "display");
if (!displayValues.isEmpty()) {
retVal.setDisplay(displayValues.get(0));
}
Optional<IBaseResource> issuesValue = getNamedParameterResource(fhirContext, theOutput, "issues");
if (issuesValue.isPresent()) {
// it seems to be safe to cast to IBaseOperationOutcome as any other type would not reach this point
createCodeValidationIssues(
(IBaseOperationOutcome) issuesValue.get(),
fhirContext.getVersion().getVersion())
.ifPresent(i -> i.forEach(result::addCodeValidationIssue));
} else { } else {
// create a validation issue out of the message
retVal.setSeverity(IssueSeverity.ERROR); // this is a workaround to overcome an issue in the FHIR Validator library
List<String> messageValues = // where ValueSet bindings are only reading issues but not messages
ParametersUtil.getNamedParameterValuesAsString(getFhirContext(), output, "message"); // @see https://github.com/hapifhir/org.hl7.fhir.core/issues/1766
if (!messageValues.isEmpty()) { result.addCodeValidationIssue(createCodeValidationIssue(result.getMessage()));
retVal.setMessage(messageValues.get(0));
}
} }
return retVal; return result;
} }
private String buildErrorMessage( /**
String theCodeSystem, * Creates a list of {@link ca.uhn.fhir.context.support.IValidationSupport.CodeValidationIssue} from the issues
String theCode, * returned by the $validate-code operation.
String theValueSetUrl, * Please note that this method should only be used for Remote Terminology for now as it only translates
IBaseResource theValueSet, * issues text/message and assumes all other fields.
String theServerUrl, * When issues will be supported across all validators in hapi-fhir, a proper generic conversion method should
String theServerMessage) { * be available and this method will be deleted.
if (theValueSetUrl == null && theValueSet == null) { *
return getErrorMessage("unknownCodeInSystem", theCodeSystem, theCode, theServerUrl, theServerMessage); * @param theOperationOutcome the outcome of the $validate-code operation
} else { * @param theFhirVersion the FHIR version
return getErrorMessage( * @return the list of {@link ca.uhn.fhir.context.support.IValidationSupport.CodeValidationIssue}
"unknownCodeInValueSet", theCodeSystem, theCode, theValueSetUrl, theServerUrl, theServerMessage); */
public static Optional<Collection<CodeValidationIssue>> createCodeValidationIssues(
IBaseOperationOutcome theOperationOutcome, FhirVersionEnum theFhirVersion) {
if (theFhirVersion == FhirVersionEnum.R4) {
return Optional.of(createCodeValidationIssuesR4((OperationOutcome) theOperationOutcome));
} }
if (theFhirVersion == FhirVersionEnum.DSTU3) {
return Optional.of(
createCodeValidationIssuesDstu3((org.hl7.fhir.dstu3.model.OperationOutcome) theOperationOutcome));
}
return Optional.empty();
}
private static Collection<CodeValidationIssue> createCodeValidationIssuesR4(OperationOutcome theOperationOutcome) {
return theOperationOutcome.getIssue().stream()
.map(issueComponent ->
createCodeValidationIssue(issueComponent.getDetails().getText()))
.collect(Collectors.toList());
}
private static Collection<CodeValidationIssue> createCodeValidationIssuesDstu3(
org.hl7.fhir.dstu3.model.OperationOutcome theOperationOutcome) {
return theOperationOutcome.getIssue().stream()
.map(issueComponent ->
createCodeValidationIssue(issueComponent.getDetails().getText()))
.collect(Collectors.toList());
}
private static CodeValidationIssue createCodeValidationIssue(String theMessage) {
return new CodeValidationIssue(
theMessage,
// assume issue type is OperationOutcome.IssueType#CODEINVALID as it is the only match
IssueSeverity.ERROR,
CodeValidationIssueCode.INVALID,
CodeValidationIssueCoding.INVALID_CODE);
}
public interface ValidationErrorMessageBuilder {
String buildErrorMessage(String theServerMessage);
} }
protected IBaseParameters buildValidateCodeInputParameters( protected IBaseParameters buildValidateCodeInputParameters(
String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl, IBaseResource theValueSet) { String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl, IBaseResource theValueSet) {
IBaseParameters params = ParametersUtil.newInstance(getFhirContext()); final FhirContext fhirContext = getFhirContext();
IBaseParameters params = ParametersUtil.newInstance(fhirContext);
if (theValueSet == null && theValueSetUrl == null) { if (theValueSet == null && theValueSetUrl == null) {
ParametersUtil.addParameterToParametersUri(getFhirContext(), params, "url", theCodeSystem); ParametersUtil.addParameterToParametersUri(fhirContext, params, "url", theCodeSystem);
ParametersUtil.addParameterToParametersString(getFhirContext(), params, "code", theCode); ParametersUtil.addParameterToParametersString(fhirContext, params, "code", theCode);
if (isNotBlank(theDisplay)) { if (isNotBlank(theDisplay)) {
ParametersUtil.addParameterToParametersString(getFhirContext(), params, "display", theDisplay); ParametersUtil.addParameterToParametersString(fhirContext, params, "display", theDisplay);
} }
return params; return params;
} }
if (isNotBlank(theValueSetUrl)) { if (isNotBlank(theValueSetUrl)) {
ParametersUtil.addParameterToParametersUri(getFhirContext(), params, "url", theValueSetUrl); ParametersUtil.addParameterToParametersUri(fhirContext, params, "url", theValueSetUrl);
} }
ParametersUtil.addParameterToParametersString(getFhirContext(), params, "code", theCode); ParametersUtil.addParameterToParametersString(fhirContext, params, "code", theCode);
if (isNotBlank(theCodeSystem)) { if (isNotBlank(theCodeSystem)) {
ParametersUtil.addParameterToParametersUri(getFhirContext(), params, "system", theCodeSystem); ParametersUtil.addParameterToParametersUri(fhirContext, params, "system", theCodeSystem);
} }
if (isNotBlank(theDisplay)) { if (isNotBlank(theDisplay)) {
ParametersUtil.addParameterToParametersString(getFhirContext(), params, "display", theDisplay); ParametersUtil.addParameterToParametersString(fhirContext, params, "display", theDisplay);
} }
if (theValueSet != null) { if (theValueSet != null) {
ParametersUtil.addParameterToParameters(getFhirContext(), params, "valueSet", theValueSet); ParametersUtil.addParameterToParameters(fhirContext, params, "valueSet", theValueSet);
} }
return params; return params;
} }

View File

@ -8,7 +8,6 @@ import ca.uhn.fhir.context.support.IValidationSupport.GroupConceptProperty;
import ca.uhn.fhir.context.support.IValidationSupport.LookupCodeResult; import ca.uhn.fhir.context.support.IValidationSupport.LookupCodeResult;
import ca.uhn.fhir.context.support.IValidationSupport.StringConceptProperty; import ca.uhn.fhir.context.support.IValidationSupport.StringConceptProperty;
import ca.uhn.fhir.context.support.LookupCodeRequest; import ca.uhn.fhir.context.support.LookupCodeRequest;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -22,6 +21,12 @@ import static ca.uhn.fhir.context.support.IValidationSupport.TYPE_GROUP;
import static ca.uhn.fhir.context.support.IValidationSupport.TYPE_STRING; import static ca.uhn.fhir.context.support.IValidationSupport.TYPE_STRING;
import static java.util.stream.IntStream.range; import static java.util.stream.IntStream.range;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE;
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE_SYSTEM;
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE_SYSTEM_NAME;
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE_SYSTEM_VERSION;
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.DISPLAY;
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.LANGUAGE;
import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.createConceptProperty; import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.createConceptProperty;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
@ -40,20 +45,15 @@ import static org.junit.jupiter.api.Assertions.fail;
* e.g. assertEqualConceptProperty * e.g. assertEqualConceptProperty
*/ */
public interface ILookupCodeTest { public interface ILookupCodeTest {
String DISPLAY = "DISPLAY";
String LANGUAGE = "en";
String CODE_SYSTEM = "CODE_SYS";
String CODE_SYSTEM_VERSION = "CODE_SYS_VERSION";
String CODE_SYSTEM_NAME = "Code System";
String CODE = "CODE";
IValidationSupport getService(); IValidationSupport getService();
IMyCodeSystemProvider getCodeSystemProvider(); IValidationProviders.IMyLookupCodeProvider getLookupCodeProvider();
@Test @Test
default void lookupCode_forCodeSystemWithBlankCode_throwsException() { default void lookupCode_forCodeSystemWithBlankCode_throwsException() {
IValidationSupport service = getService();
LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, "");
try { try {
getService().lookupCode(null, new LookupCodeRequest(CODE_SYSTEM, "")); service.lookupCode(null, request);
fail(); fail();
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
assertEquals("theCode must be provided", e.getMessage()); assertEquals("theCode must be provided", e.getMessage());
@ -70,11 +70,14 @@ public interface ILookupCodeTest {
return "someUnsupportedType"; return "someUnsupportedType";
} }
}); });
getCodeSystemProvider().setLookupCodeResult(result); getLookupCodeProvider().setLookupCodeResult(result);
IValidationSupport service = getService();
LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, null);
// test and verify // test and verify
try { try {
getService().lookupCode(null, new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, null)); service.lookupCode(null, request);
fail(); fail();
} catch (InternalErrorException e) { } catch (InternalErrorException e) {
assertThat(e.getMessage()).contains("HAPI-1739: Don't know how to handle "); assertThat(e.getMessage()).contains("HAPI-1739: Don't know how to handle ");
@ -88,7 +91,7 @@ public interface ILookupCodeTest {
result.setCodeSystemVersion(CODE_SYSTEM_VERSION); result.setCodeSystemVersion(CODE_SYSTEM_VERSION);
result.setCodeSystemDisplayName(CODE_SYSTEM_NAME); result.setCodeSystemDisplayName(CODE_SYSTEM_NAME);
result.setCodeDisplay(DISPLAY); result.setCodeDisplay(DISPLAY);
getCodeSystemProvider().setLookupCodeResult(result); getLookupCodeProvider().setLookupCodeResult(result);
// test and verify // test and verify
LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, null); LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, null);
@ -107,7 +110,7 @@ public interface ILookupCodeTest {
result.setFound(true); result.setFound(true);
result.getDesignations().add(designation1); result.getDesignations().add(designation1);
result.getDesignations().add(designation2); result.getDesignations().add(designation2);
getCodeSystemProvider().setLookupCodeResult(result); getLookupCodeProvider().setLookupCodeResult(result);
// test and verify // test and verify
LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, null); LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, null);
@ -120,7 +123,7 @@ public interface ILookupCodeTest {
BaseConceptProperty property = createConceptProperty(propertyName, thePropertyValue); BaseConceptProperty property = createConceptProperty(propertyName, thePropertyValue);
LookupCodeResult result = new LookupCodeResult(); LookupCodeResult result = new LookupCodeResult();
result.getProperties().add(property); result.getProperties().add(property);
getCodeSystemProvider().setLookupCodeResult(result); getLookupCodeProvider().setLookupCodeResult(result);
// test // test
LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, List.of(propertyName)); LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, List.of(propertyName));
@ -148,7 +151,7 @@ public interface ILookupCodeTest {
propertyNamesToFilter.add(currentPropertyName); propertyNamesToFilter.add(currentPropertyName);
} }
} }
getCodeSystemProvider().setLookupCodeResult(result); getLookupCodeProvider().setLookupCodeResult(result);
// test // test
LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, propertyNamesToFilter); LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, propertyNamesToFilter);
@ -172,7 +175,7 @@ public interface ILookupCodeTest {
group.addSubProperty(createConceptProperty(subPropertyName + i, thePropertyValues.get(i))); group.addSubProperty(createConceptProperty(subPropertyName + i, thePropertyValues.get(i)));
} }
result.getProperties().add(group); result.getProperties().add(group);
getCodeSystemProvider().setLookupCodeResult(result); getLookupCodeProvider().setLookupCodeResult(result);
// test and verify // test and verify
LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, List.of(groupName)); LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, List.of(groupName));
@ -186,8 +189,8 @@ public interface ILookupCodeTest {
// verify // verify
assertNotNull(outcome); assertNotNull(outcome);
assertEquals(theRequest.getCode(), getCodeSystemProvider().getCode()); assertEquals(theRequest.getCode(), getLookupCodeProvider().getCode());
assertEquals(theRequest.getSystem(), getCodeSystemProvider().getSystem()); assertEquals(theRequest.getSystem(), getLookupCodeProvider().getSystem());
assertEquals(theExpectedResult.isFound(), outcome.isFound()); assertEquals(theExpectedResult.isFound(), outcome.isFound());
assertEquals(theExpectedResult.getErrorMessage(), outcome.getErrorMessage()); assertEquals(theExpectedResult.getErrorMessage(), outcome.getErrorMessage());
assertEquals(theExpectedResult.getCodeSystemDisplayName(), outcome.getCodeSystemDisplayName()); assertEquals(theExpectedResult.getCodeSystemDisplayName(), outcome.getCodeSystemDisplayName());
@ -207,7 +210,7 @@ public interface ILookupCodeTest {
LookupCodeResult result = new LookupCodeResult(); LookupCodeResult result = new LookupCodeResult();
result.setFound(true); result.setFound(true);
result.getDesignations().add(theConceptDesignation); result.getDesignations().add(theConceptDesignation);
getCodeSystemProvider().setLookupCodeResult(result); getLookupCodeProvider().setLookupCodeResult(result);
// test and verify // test and verify
LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, null); LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, null);
@ -247,11 +250,4 @@ public interface ILookupCodeTest {
assertEquals(theActualDesignation.getUseSystem(), theExpectedDesignation.getUseSystem()); assertEquals(theActualDesignation.getUseSystem(), theExpectedDesignation.getUseSystem());
assertEquals(theActualDesignation.getUseDisplay(), theExpectedDesignation.getUseDisplay()); assertEquals(theActualDesignation.getUseDisplay(), theExpectedDesignation.getUseDisplay());
} }
interface IMyCodeSystemProvider extends IResourceProvider {
String getCode();
String getSystem();
void setLookupCodeResult(LookupCodeResult theLookupCodeResult);
}
} }

View File

@ -23,7 +23,7 @@ public interface IRemoteTerminologyLookupCodeTest extends ILookupCodeTest {
default void lookupCode_forCodeSystemWithCodeNotFound_returnsNotFound() { default void lookupCode_forCodeSystemWithCodeNotFound_returnsNotFound() {
String baseUrl = getService().getBaseUrl(); String baseUrl = getService().getBaseUrl();
final String codeNotFound = "a"; final String codeNotFound = "a";
final String system = CODE_SYSTEM; final String system = IValidationProviders.CODE_SYSTEM;
final String codeAndSystem = system + "#" + codeNotFound; final String codeAndSystem = system + "#" + codeNotFound;
final String exceptionMessage = MessageFormat.format(MESSAGE_RESPONSE_NOT_FOUND, codeNotFound); final String exceptionMessage = MessageFormat.format(MESSAGE_RESPONSE_NOT_FOUND, codeNotFound);
LookupCodeResult result = new LookupCodeResult() LookupCodeResult result = new LookupCodeResult()
@ -31,7 +31,7 @@ public interface IRemoteTerminologyLookupCodeTest extends ILookupCodeTest {
.setSearchedForCode(codeNotFound) .setSearchedForCode(codeNotFound)
.setSearchedForSystem(system) .setSearchedForSystem(system)
.setErrorMessage("Unknown code \"" + codeAndSystem + "\". The Remote Terminology server " + baseUrl + " returned HTTP 404 Not Found: " + exceptionMessage); .setErrorMessage("Unknown code \"" + codeAndSystem + "\". The Remote Terminology server " + baseUrl + " returned HTTP 404 Not Found: " + exceptionMessage);
getCodeSystemProvider().setLookupCodeResult(result); getLookupCodeProvider().setLookupCodeResult(result);
LookupCodeRequest request = new LookupCodeRequest(system, codeNotFound, null, null); LookupCodeRequest request = new LookupCodeRequest(system, codeNotFound, null, null);
verifyLookupCodeResult(request, result); verifyLookupCodeResult(request, result);
@ -49,9 +49,11 @@ public interface IRemoteTerminologyLookupCodeTest extends ILookupCodeTest {
.setSearchedForCode(codeNotFound) .setSearchedForCode(codeNotFound)
.setSearchedForSystem(system) .setSearchedForSystem(system)
.setErrorMessage("Unknown code \"" + codeAndSystem + "\". The Remote Terminology server " + baseUrl + " returned HTTP 400 Bad Request: " + exceptionMessage); .setErrorMessage("Unknown code \"" + codeAndSystem + "\". The Remote Terminology server " + baseUrl + " returned HTTP 400 Bad Request: " + exceptionMessage);
getCodeSystemProvider().setLookupCodeResult(result); getLookupCodeProvider().setLookupCodeResult(result);
LookupCodeRequest request = new LookupCodeRequest(system, codeNotFound, null, null); LookupCodeRequest request = new LookupCodeRequest(system, codeNotFound, null, null);
verifyLookupCodeResult(request, result); verifyLookupCodeResult(request, result);
} }
} }

View File

@ -0,0 +1,17 @@
package org.hl7.fhir.common.hapi.validation;
import ca.uhn.fhir.context.support.IValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
public interface IRemoteTerminologyValidateCodeTest extends IValidateCodeTest {
default List<IValidationSupport.CodeValidationIssue> getCodeValidationIssues(IBaseOperationOutcome theOperationOutcome) {
// this method should be removed once support for issues is fully implemented across all validator types
Optional<Collection<IValidationSupport.CodeValidationIssue>> issues = RemoteTerminologyServiceValidationSupport.createCodeValidationIssues(theOperationOutcome, getService().getFhirContext().getVersion().getVersion());
return issues.map(theCodeValidationIssues -> theCodeValidationIssues.stream().toList()).orElseGet(List::of);
}
}

View File

@ -0,0 +1,331 @@
package org.hl7.fhir.common.hapi.validation;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport.CodeValidationResult;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
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.List;
import java.util.stream.Stream;
import static ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity.ERROR;
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE;
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE_SYSTEM;
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE_SYSTEM_VERSION;
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.DISPLAY;
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.ERROR_MESSAGE;
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.VALUE_SET_URL;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public interface IValidateCodeTest {
IValidationProviders.IMyCodeSystemProvider getCodeSystemProvider();
IValidationProviders.IMyValueSetProvider getValueSetProvider();
IValidationSupport getService();
IBaseParameters createParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource);
String getCodeSystemError();
String getValueSetError();
IBaseOperationOutcome getCodeSystemInvalidCodeOutcome();
IBaseOperationOutcome getValueSetInvalidCodeOutcome();
default void createCodeSystemReturnParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) {
getCodeSystemProvider().setReturnParams(createParameters(theResult, theDisplay, theMessage, theIssuesResource));
}
default void createValueSetReturnParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) {
getValueSetProvider().setReturnParams(createParameters(theResult, theDisplay, theMessage, theIssuesResource));
}
@Test
default void validateCode_withCodeSystemBlankCode_ReturnsNull() {
CodeValidationResult outcome = getService()
.validateCode(null, null, CODE_SYSTEM, null, DISPLAY, null);
assertNull(outcome);
}
@Test
default void validateCode_withValueSetBlankCode_returnsNull() {
CodeValidationResult outcome = getService().validateCode(null, null, CODE_SYSTEM, "", DISPLAY, VALUE_SET_URL);
assertNull(outcome);
}
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")
default void validateCode_codeSystemAndValueSetUrlAreIncorrect_returnsValidationResultWithError(Exception theException,
String theServerMessage,
String theValidationMessage,
String theCodeSystem,
String theValueSetUrl) {
getCodeSystemProvider().setException(theException);
getValueSetProvider().setException(theException);
CodeValidationResult outcome = getService().validateCode(null, null, theCodeSystem, CODE, DISPLAY, theValueSetUrl);
verifyErrorResultFromException(outcome, theValidationMessage, theServerMessage);
}
default void verifyErrorResultFromException(CodeValidationResult outcome, String... theMessages) {
assertNotNull(outcome);
assertEquals(ERROR, outcome.getSeverity());
assertNotNull(outcome.getMessage());
for (String message : theMessages) {
assertTrue(outcome.getMessage().contains(message));
}
assertFalse(outcome.getCodeValidationIssues().isEmpty());
}
@Test
default void validateCode_withMissingResult_returnsCorrectly() {
createCodeSystemReturnParameters(null, null, null, null);
IValidationSupport service = getService();
try {
service.validateCode(null, null, CODE_SYSTEM, CODE, null, null);
fail();
} catch (IllegalArgumentException e) {
assertEquals("HAPI-2560: Parameter `result` is missing from the $validate-code response.", e.getMessage());
}
}
@Test
default void validateCode_withValueSetSuccess_returnsCorrectly() {
createValueSetReturnParameters(true, DISPLAY, null, null);
CodeValidationResult outcome = getService().validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, VALUE_SET_URL);
assertNotNull(outcome);
assertEquals(CODE, outcome.getCode());
assertEquals(DISPLAY, outcome.getDisplay());
assertNull(outcome.getSeverity());
assertNull(outcome.getMessage());
assertTrue(outcome.getCodeValidationIssues().isEmpty());
assertEquals(CODE, getValueSetProvider().getCode());
assertEquals(DISPLAY, getValueSetProvider().getDisplay());
assertEquals(VALUE_SET_URL, getValueSetProvider().getValueSet());
}
@Test
default void validateCode_withCodeSystemSuccess_returnsCorrectly() {
createCodeSystemReturnParameters(true, DISPLAY, null, null);
CodeValidationResult outcome = getService().validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, null);
assertNotNull(outcome);
assertEquals(CODE, outcome.getCode());
assertEquals(DISPLAY, outcome.getDisplay());
assertNull(outcome.getSeverity());
assertNull(outcome.getMessage());
assertTrue(outcome.getCodeValidationIssues().isEmpty());
assertEquals(CODE, getCodeSystemProvider().getCode());
}
@Test
default void validateCode_withCodeSystemProvidingMinimalInputs_ReturnsSuccess() {
createCodeSystemReturnParameters(true, null, null, null);
CodeValidationResult outcome = getService()
.validateCode(null, null, CODE_SYSTEM, CODE, null, null);
assertNotNull(outcome);
assertEquals(CODE_SYSTEM, outcome.getCodeSystemName());
assertEquals(CODE_SYSTEM_VERSION, outcome.getCodeSystemVersion());
assertEquals(CODE, outcome.getCode());
assertNull(outcome.getDisplay());
assertNull(outcome.getSeverity());
assertNull(outcome.getMessage());
assertTrue(outcome.getCodeValidationIssues().isEmpty());
assertEquals(CODE, getCodeSystemProvider().getCode());
assertEquals(CODE_SYSTEM, getCodeSystemProvider().getSystem());
}
@Test
default void validateCode_withCodeSystemSuccessWithMessageValue_returnsCorrectly() {
createCodeSystemReturnParameters(true, DISPLAY, null, null);
CodeValidationResult outcome = getService()
.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, null);
assertNotNull(outcome);
assertEquals(CODE_SYSTEM, outcome.getCodeSystemName());
assertEquals(CODE_SYSTEM_VERSION, outcome.getCodeSystemVersion());
assertEquals(CODE, outcome.getCode());
assertEquals(DISPLAY, outcome.getDisplay());
assertNull(outcome.getSeverity());
assertNull(outcome.getMessage());
assertTrue(outcome.getCodeValidationIssues().isEmpty());
assertEquals(CODE, getCodeSystemProvider().getCode());
assertEquals(DISPLAY, getCodeSystemProvider().getDisplay());
assertEquals(CODE_SYSTEM, getCodeSystemProvider().getSystem());
}
@Test
default void validateCode_withCodeSystemError_returnsCorrectly() {
IBaseOperationOutcome invalidCodeOutcome = getCodeSystemInvalidCodeOutcome();
createCodeSystemReturnParameters(false, null, ERROR_MESSAGE, invalidCodeOutcome);
CodeValidationResult outcome = getService()
.validateCode(null, null, CODE_SYSTEM, CODE, null, null);
assertNotNull(outcome);
assertEquals(CODE_SYSTEM, outcome.getCodeSystemName());
assertEquals(CODE_SYSTEM_VERSION, outcome.getCodeSystemVersion());
// assertEquals(CODE, outcome.getCode());
assertEquals(ERROR, outcome.getSeverity());
assertEquals(getCodeSystemError(), outcome.getMessage());
assertFalse(outcome.getCodeValidationIssues().isEmpty());
verifyIssues(invalidCodeOutcome, outcome);
}
@Test
default void validateCode_withCodeSystemErrorAndIssues_returnsCorrectly() {
createCodeSystemReturnParameters(false, null, ERROR_MESSAGE, null);
CodeValidationResult outcome = getService()
.validateCode(null, null, CODE_SYSTEM, CODE, null, null);
String expectedError = getCodeSystemError();
assertNotNull(outcome);
assertEquals(CODE_SYSTEM, outcome.getCodeSystemName());
assertEquals(CODE_SYSTEM_VERSION, outcome.getCodeSystemVersion());
// assertEquals(CODE, outcome.getCode());
assertNull(outcome.getDisplay());
assertEquals(ERROR, outcome.getSeverity());
assertEquals(expectedError, outcome.getMessage());
assertFalse(outcome.getCodeValidationIssues().isEmpty());
assertEquals(1, outcome.getCodeValidationIssues().size());
assertEquals(expectedError, outcome.getCodeValidationIssues().get(0).getMessage());
assertEquals(ERROR, outcome.getCodeValidationIssues().get(0).getSeverity());
}
@Test
default void validateCode_withValueSetProvidingMinimalInputsSuccess_returnsCorrectly() {
createValueSetReturnParameters(true, null, null, null);
CodeValidationResult outcome = getService()
.validateCode(null, null, CODE_SYSTEM, CODE, null, VALUE_SET_URL);
assertNotNull(outcome);
assertEquals(CODE_SYSTEM, outcome.getCodeSystemName());
assertEquals(CODE_SYSTEM_VERSION, outcome.getCodeSystemVersion());
assertEquals(CODE, outcome.getCode());
assertNull(outcome.getDisplay());
assertNull(outcome.getSeverity());
assertNull(outcome.getMessage());
assertTrue(outcome.getCodeValidationIssues().isEmpty());
assertEquals(CODE, getValueSetProvider().getCode());
assertEquals(VALUE_SET_URL, getValueSetProvider().getValueSet());
}
@Test
default void validateCode_withValueSetSuccessWithMessage_returnsCorrectly() {
createValueSetReturnParameters(true, DISPLAY, null, null);
CodeValidationResult outcome = getService()
.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, VALUE_SET_URL);
assertNotNull(outcome);
assertEquals(CODE_SYSTEM, outcome.getCodeSystemName());
assertEquals(CODE_SYSTEM_VERSION, outcome.getCodeSystemVersion());
assertEquals(CODE, outcome.getCode());
assertEquals(DISPLAY, outcome.getDisplay());
assertNull(outcome.getSeverity());
assertNull(outcome.getMessage());
assertTrue(outcome.getCodeValidationIssues().isEmpty());
assertEquals(CODE, getValueSetProvider().getCode());
assertEquals(DISPLAY, getValueSetProvider().getDisplay());
assertEquals(VALUE_SET_URL, getValueSetProvider().getValueSet());
}
@Test
default void validateCode_withValueSetError_returnsCorrectly() {
createValueSetReturnParameters(false, DISPLAY, ERROR_MESSAGE, null);
CodeValidationResult outcome = getService()
.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, VALUE_SET_URL);
String expectedError = getValueSetError();
assertNotNull(outcome);
assertEquals(CODE_SYSTEM, outcome.getCodeSystemName());
assertEquals(CODE_SYSTEM_VERSION, outcome.getCodeSystemVersion());
// assertEquals(CODE, outcome.getCode());
assertEquals(DISPLAY, outcome.getDisplay());
assertEquals(ERROR, outcome.getSeverity());
assertEquals(expectedError, outcome.getMessage());
assertEquals(1, outcome.getCodeValidationIssues().size());
assertEquals(expectedError, outcome.getCodeValidationIssues().get(0).getMessage());
assertEquals(ERROR, outcome.getCodeValidationIssues().get(0).getSeverity());
assertEquals(CODE, getValueSetProvider().getCode());
assertEquals(DISPLAY, getValueSetProvider().getDisplay());
assertEquals(VALUE_SET_URL, getValueSetProvider().getValueSet());
}
@Test
default void validateCode_withValueSetErrorWithIssues_returnsCorrectly() {
IBaseOperationOutcome invalidCodeOutcome = getValueSetInvalidCodeOutcome();
createValueSetReturnParameters(false, DISPLAY, ERROR_MESSAGE, invalidCodeOutcome);
CodeValidationResult outcome = getService()
.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, VALUE_SET_URL);
assertNotNull(outcome);
assertEquals(CODE_SYSTEM, outcome.getCodeSystemName());
assertEquals(CODE_SYSTEM_VERSION, outcome.getCodeSystemVersion());
// assertEquals(CODE, outcome.getCode());
assertEquals(DISPLAY, outcome.getDisplay());
assertEquals(ERROR, outcome.getSeverity());
assertEquals(getValueSetError(), outcome.getMessage());
assertFalse(outcome.getCodeValidationIssues().isEmpty());
verifyIssues(invalidCodeOutcome, outcome);
assertEquals(CODE, getValueSetProvider().getCode());
assertEquals(DISPLAY, getValueSetProvider().getDisplay());
assertEquals(VALUE_SET_URL, getValueSetProvider().getValueSet());
}
default void verifyIssues(IBaseOperationOutcome theOperationOutcome, CodeValidationResult theResult) {
List<IValidationSupport.CodeValidationIssue> issues = getCodeValidationIssues(theOperationOutcome);
assertEquals(issues.size(), theResult.getCodeValidationIssues().size());
for (int i = 0; i < issues.size(); i++) {
IValidationSupport.CodeValidationIssue expectedIssue = issues.get(i);
IValidationSupport.CodeValidationIssue actualIssue = theResult.getCodeValidationIssues().get(i);
assertEquals(expectedIssue.getCode(), actualIssue.getCode());
assertEquals(expectedIssue.getSeverity(), actualIssue.getSeverity());
assertEquals(expectedIssue.getCoding(), actualIssue.getCoding());
assertEquals(expectedIssue.getMessage(), actualIssue.getMessage());
}
}
List<IValidationSupport.CodeValidationIssue> getCodeValidationIssues(IBaseOperationOutcome theOperationOutcome);
}

View File

@ -0,0 +1,39 @@
package org.hl7.fhir.common.hapi.validation;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.rest.server.IResourceProvider;
import org.hl7.fhir.instance.model.api.IBaseParameters;
public interface IValidationProviders {
String CODE_SYSTEM = "http://code.system/url";
String CODE_SYSTEM_VERSION = "1.0.0";
String CODE_SYSTEM_NAME = "Test Code System";
String CODE = "CODE";
String VALUE_SET_URL = "http://value.set/url";
String DISPLAY = "Explanation for code TestCode.";
String LANGUAGE = "en";
String ERROR_MESSAGE = "This is an error message";
interface IMyCodeSystemProvider extends IResourceProvider {
String getCode();
String getSystem();
String getDisplay();
void setException(Exception theException);
void setReturnParams(IBaseParameters theParameters);
}
interface IMyLookupCodeProvider extends IResourceProvider {
String getCode();
String getSystem();
void setLookupCodeResult(IValidationSupport.LookupCodeResult theLookupCodeResult);
}
interface IMyValueSetProvider extends IResourceProvider {
String getCode();
String getSystem();
String getDisplay();
String getValueSet();
void setException(Exception theException);
void setReturnParams(IBaseParameters theParameters);
}
}

View File

@ -21,8 +21,8 @@ import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerVali
import org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationSupport; import org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.dstu3.fhirpath.FHIRPathEngine; import org.hl7.fhir.dstu3.fhirpath.FHIRPathEngine;
import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.dstu3.model.Base; import org.hl7.fhir.dstu3.model.Base;
import org.hl7.fhir.dstu3.model.BooleanType; import org.hl7.fhir.dstu3.model.BooleanType;
import org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.dstu3.model.Bundle;
@ -55,7 +55,6 @@ import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor;
import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher; import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher;
import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy;
@ -65,6 +64,8 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock;
import org.mockito.quality.Strictness; import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
@ -110,6 +111,7 @@ public class FhirInstanceValidatorDstu3Test extends BaseValidationTestWithInline
private FhirValidator myVal; private FhirValidator myVal;
private ArrayList<String> myValidConcepts; private ArrayList<String> myValidConcepts;
private Set<String> myValidSystems = new HashSet<>(); private Set<String> myValidSystems = new HashSet<>();
private Set<String> myValidSystemsNotReturningIssues = new HashSet<>();
private HashMap<String, StructureDefinition> myStructureDefinitions; private HashMap<String, StructureDefinition> myStructureDefinitions;
private HashMap<String, CodeSystem> myCodeSystems; private HashMap<String, CodeSystem> myCodeSystems;
private HashMap<String, ValueSet> myValueSets; private HashMap<String, ValueSet> myValueSets;
@ -117,7 +119,15 @@ public class FhirInstanceValidatorDstu3Test extends BaseValidationTestWithInline
private CachingValidationSupport myValidationSupport; private CachingValidationSupport myValidationSupport;
private void addValidConcept(String theSystem, String theCode) { private void addValidConcept(String theSystem, String theCode) {
myValidSystems.add(theSystem); addValidConcept(theSystem, theCode, true);
}
private void addValidConcept(String theSystem, String theCode, boolean theShouldSystemReturnIssuesForInvalidCode) {
if (theShouldSystemReturnIssuesForInvalidCode) {
myValidSystems.add(theSystem);
} else {
myValidSystemsNotReturningIssues.add(theSystem);
}
myValidConcepts.add(theSystem + "___" + theCode); myValidConcepts.add(theSystem + "___" + theCode);
} }
@ -219,9 +229,10 @@ public class FhirInstanceValidatorDstu3Test extends BaseValidationTestWithInline
retVal = new IValidationSupport.CodeValidationResult().setCode(code); retVal = new IValidationSupport.CodeValidationResult().setCode(code);
} else if (myValidSystems.contains(system)) { } else if (myValidSystems.contains(system)) {
final String message = "Unknown code (for '" + system + "#" + code + "')"; final String message = "Unknown code (for '" + system + "#" + code + "')";
retVal = new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message).setCodeValidationIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(message, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE)));
return new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message).setCodeValidationIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(message, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE))); } else if (myValidSystemsNotReturningIssues.contains(system)) {
final String message = "Unknown code (for '" + system + "#" + code + "')";
retVal = new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message);
} else if (myCodeSystems.containsKey(system)) { } else if (myCodeSystems.containsKey(system)) {
CodeSystem cs = myCodeSystems.get(system); CodeSystem cs = myCodeSystems.get(system);
Optional<ConceptDefinitionComponent> found = cs.getConcept().stream().filter(t -> t.getCode().equals(code)).findFirst(); Optional<ConceptDefinitionComponent> found = cs.getConcept().stream().filter(t -> t.getCode().equals(code)).findFirst();
@ -1161,9 +1172,11 @@ public class FhirInstanceValidatorDstu3Test extends BaseValidationTestWithInline
} }
@Test // TODO: uncomment value false when https://github.com/hapifhir/org.hl7.fhir.core/issues/1766 is fixed
public void testValidateResourceContainingLoincCode() { @ParameterizedTest
addValidConcept("http://loinc.org", "1234567"); @ValueSource(booleans = {true, /*false*/})
public void testValidateResourceContainingLoincCode(boolean theShouldSystemReturnIssuesForInvalidCode) {
addValidConcept("http://loinc.org", "1234567", theShouldSystemReturnIssuesForInvalidCode);
Observation input = new Observation(); Observation input = new Observation();
// input.getMeta().addProfile("http://hl7.org/fhir/StructureDefinition/devicemetricobservation"); // input.getMeta().addProfile("http://hl7.org/fhir/StructureDefinition/devicemetricobservation");

View File

@ -0,0 +1,159 @@
package org.hl7.fhir.dstu3.hapi.validation;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import jakarta.servlet.http.HttpServletRequest;
import org.hl7.fhir.common.hapi.validation.IValidationProviders;
import org.hl7.fhir.dstu3.model.BooleanType;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.CodeType;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import java.util.List;
public interface IValidateCodeProvidersDstu3 {
@SuppressWarnings("unused")
class MyCodeSystemProviderDstu3 implements IValidationProviders.IMyCodeSystemProvider {
private UriType mySystemUrl;
private CodeType myCode;
private StringType myDisplay;
private Exception myException;
private Parameters myReturnParams;
@Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = org.hl7.fhir.dstu3.model.BooleanType.class, min = 1),
@OperationParam(name = "message", type = org.hl7.fhir.dstu3.model.StringType.class),
@OperationParam(name = "display", type = org.hl7.fhir.dstu3.model.StringType.class)
})
public org.hl7.fhir.dstu3.model.Parameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) org.hl7.fhir.dstu3.model.IdType theId,
@OperationParam(name = "url", min = 0, max = 1) org.hl7.fhir.dstu3.model.UriType theCodeSystemUrl,
@OperationParam(name = "code", min = 0, max = 1) org.hl7.fhir.dstu3.model.CodeType theCode,
@OperationParam(name = "display", min = 0, max = 1) org.hl7.fhir.dstu3.model.StringType theDisplay
) throws Exception {
mySystemUrl = theCodeSystemUrl;
myCode = theCode;
myDisplay = theDisplay;
if (myException != null) {
throw myException;
}
return myReturnParams;
}
@Operation(name = JpaConstants.OPERATION_LOOKUP, idempotent = true, returnParameters= {
@OperationParam(name = "name", type = org.hl7.fhir.dstu3.model.StringType.class, min = 1),
@OperationParam(name = "version", type = org.hl7.fhir.dstu3.model.StringType.class),
@OperationParam(name = "display", type = org.hl7.fhir.dstu3.model.StringType.class, min = 1),
@OperationParam(name = "abstract", type = org.hl7.fhir.dstu3.model.BooleanType.class, min = 1),
@OperationParam(name = "property", type = org.hl7.fhir.dstu3.model.StringType.class, min = 0, max = OperationParam.MAX_UNLIMITED)
})
public IBaseParameters lookup(
HttpServletRequest theServletRequest,
@OperationParam(name = "code", max = 1) org.hl7.fhir.dstu3.model.CodeType theCode,
@OperationParam(name = "system",max = 1) org.hl7.fhir.dstu3.model.UriType theSystem,
@OperationParam(name = "coding", max = 1) Coding theCoding,
@OperationParam(name = "version", max = 1) org.hl7.fhir.dstu3.model.StringType theVersion,
@OperationParam(name = "displayLanguage", max = 1) org.hl7.fhir.dstu3.model.CodeType theDisplayLanguage,
@OperationParam(name = "property", max = OperationParam.MAX_UNLIMITED) List<org.hl7.fhir.dstu3.model.CodeType> thePropertyNames,
RequestDetails theRequestDetails
) {
myCode = theCode;
return myReturnParams;
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return CodeSystem.class;
}
public void setException(Exception theException) {
myException = theException;
}
@Override
public void setReturnParams(IBaseParameters theParameters) {
myReturnParams = (Parameters) theParameters;
}
@Override
public String getCode() {
return myCode != null ? myCode.getValueAsString() : null;
}
@Override
public String getSystem() {
return mySystemUrl != null ? mySystemUrl.getValueAsString() : null;
}
public String getDisplay() {
return myDisplay != null ? myDisplay.getValue() : null;
}
}
@SuppressWarnings("unused")
class MyValueSetProviderDstu3 implements IValidationProviders.IMyValueSetProvider {
private Exception myException;
private Parameters myReturnParams;
private UriType mySystemUrl;
private UriType myValueSetUrl;
private CodeType myCode;
private StringType myDisplay;
@Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = org.hl7.fhir.dstu3.model.StringType.class),
@OperationParam(name = "display", type = org.hl7.fhir.dstu3.model.StringType.class)
})
public Parameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "url", min = 0, max = 1) org.hl7.fhir.dstu3.model.UriType theValueSetUrl,
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@OperationParam(name = "system", min = 0, max = 1) UriType theSystem,
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay,
@OperationParam(name = "valueSet") org.hl7.fhir.dstu3.model.ValueSet theValueSet
) throws Exception {
mySystemUrl = theSystem;
myValueSetUrl = theValueSetUrl;
myCode = theCode;
myDisplay = theDisplay;
if (myException != null) {
throw myException;
}
return myReturnParams;
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return ValueSet.class;
}
public void setException(Exception theException) {
myException = theException;
}
@Override
public void setReturnParams(IBaseParameters theParameters) {
myReturnParams = (Parameters) theParameters;
}
@Override
public String getCode() {
return myCode != null ? myCode.getValueAsString() : null;
}
@Override
public String getSystem() {
return mySystemUrl != null ? mySystemUrl.getValueAsString() : null;
}
@Override
public String getValueSet() {
return myValueSetUrl != null ? myValueSetUrl.getValueAsString() : null;
}
public String getDisplay() {
return myDisplay != null ? myDisplay.getValue() : null;
}
}
}

View File

@ -14,6 +14,7 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.hl7.fhir.common.hapi.validation.IRemoteTerminologyLookupCodeTest; import org.hl7.fhir.common.hapi.validation.IRemoteTerminologyLookupCodeTest;
import org.hl7.fhir.common.hapi.validation.IValidationProviders;
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
import org.hl7.fhir.dstu3.model.BooleanType; import org.hl7.fhir.dstu3.model.BooleanType;
import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.CodeSystem;
@ -29,6 +30,7 @@ import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.DecimalType; import org.hl7.fhir.r4.model.DecimalType;
import org.hl7.fhir.r4.model.InstantType; import org.hl7.fhir.r4.model.InstantType;
import org.hl7.fhir.r4.model.IntegerType; import org.hl7.fhir.r4.model.IntegerType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
@ -49,20 +51,25 @@ public class RemoteTerminologyLookupCodeDstu3Test implements IRemoteTerminologyL
@RegisterExtension @RegisterExtension
public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx); public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx);
private final RemoteTerminologyServiceValidationSupport mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx); private final RemoteTerminologyServiceValidationSupport mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx);
private IValidationProviders.IMyLookupCodeProvider myLookupCodeProvider;
@BeforeEach @BeforeEach
public void before() { public void before() {
String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort(); String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort();
mySvc.setBaseUrl(baseUrl); mySvc.setBaseUrl(baseUrl);
mySvc.addClientInterceptor(new LoggingInterceptor(true)); mySvc.addClientInterceptor(new LoggingInterceptor(true));
ourRestfulServerExtension.getRestfulServer().registerProvider(myCodeSystemProvider); myLookupCodeProvider = new MyLookupCodeProviderDstu3();
ourRestfulServerExtension.getRestfulServer().registerProvider(myLookupCodeProvider);
} }
private final MyCodeSystemProviderDstu3 myCodeSystemProvider = new MyCodeSystemProviderDstu3(); @AfterEach
public void after() {
ourRestfulServerExtension.getRestfulServer().unregisterProvider(myLookupCodeProvider);
}
@Override @Override
public IMyCodeSystemProvider getCodeSystemProvider() { public IValidationProviders.IMyLookupCodeProvider getLookupCodeProvider() {
return myCodeSystemProvider; return myLookupCodeProvider;
} }
@Override @Override
@ -83,7 +90,7 @@ public class RemoteTerminologyLookupCodeDstu3Test implements IRemoteTerminologyL
@ParameterizedTest @ParameterizedTest
@MethodSource(value = "getEmptyPropertyValues") @MethodSource(value = "getEmptyPropertyValues")
public void lookupCode_forCodeSystemWithPropertyEmptyValue_returnsCorrectParameters(IBaseDatatype thePropertyValue) { void lookupCode_forCodeSystemWithPropertyEmptyValue_returnsCorrectParameters(IBaseDatatype thePropertyValue) {
verifyLookupWithEmptyPropertyValue(thePropertyValue); verifyLookupWithEmptyPropertyValue(thePropertyValue);
} }
@ -122,20 +129,20 @@ public class RemoteTerminologyLookupCodeDstu3Test implements IRemoteTerminologyL
@ParameterizedTest @ParameterizedTest
@MethodSource(value = "getPropertyValueArguments") @MethodSource(value = "getPropertyValueArguments")
public void lookupCode_forCodeSystemWithProperty_returnsCorrectProperty(IBaseDatatype thePropertyValue) { void lookupCode_forCodeSystemWithProperty_returnsCorrectProperty(IBaseDatatype thePropertyValue) {
verifyLookupWithProperty(List.of(thePropertyValue), List.of()); verifyLookupWithProperty(List.of(thePropertyValue), List.of());
} }
@ParameterizedTest @ParameterizedTest
@MethodSource(value = "getPropertyValueListArguments") @MethodSource(value = "getPropertyValueListArguments")
public void lookupCode_forCodeSystemWithPropertyFilter_returnsCorrectProperty(List<IBaseDatatype> thePropertyValues) { void lookupCode_forCodeSystemWithPropertyFilter_returnsCorrectProperty(List<IBaseDatatype> thePropertyValues) {
verifyLookupWithProperty(thePropertyValues, List.of()); verifyLookupWithProperty(thePropertyValues, List.of());
verifyLookupWithProperty(thePropertyValues, List.of(thePropertyValues.size() - 1)); verifyLookupWithProperty(thePropertyValues, List.of(thePropertyValues.size() - 1));
} }
@ParameterizedTest @ParameterizedTest
@MethodSource(value = "getPropertyValueListArguments") @MethodSource(value = "getPropertyValueListArguments")
public void lookupCode_forCodeSystemWithPropertyGroup_returnsCorrectProperty(List<IBaseDatatype> thePropertyValues) { void lookupCode_forCodeSystemWithPropertyGroup_returnsCorrectProperty(List<IBaseDatatype> thePropertyValues) {
verifyLookupWithSubProperties(thePropertyValues); verifyLookupWithSubProperties(thePropertyValues);
} }
@ -155,7 +162,8 @@ public class RemoteTerminologyLookupCodeDstu3Test implements IRemoteTerminologyL
verifyLookupWithConceptDesignation(theConceptDesignation); verifyLookupWithConceptDesignation(theConceptDesignation);
} }
static class MyCodeSystemProviderDstu3 implements IMyCodeSystemProvider { @SuppressWarnings("unused")
static class MyLookupCodeProviderDstu3 implements IValidationProviders.IMyLookupCodeProvider {
private UriType mySystemUrl; private UriType mySystemUrl;
private CodeType myCode; private CodeType myCode;
private LookupCodeResult myLookupCodeResult; private LookupCodeResult myLookupCodeResult;

View File

@ -0,0 +1,68 @@
package org.hl7.fhir.dstu3.hapi.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.LookupCodeRequest;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import ca.uhn.fhir.util.ClasspathUtil;
import org.hl7.fhir.common.hapi.validation.IValidationProviders;
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class RemoteTerminologyLookupCodeWithResponseFileDstu3Test {
private static final FhirContext ourCtx = FhirContext.forDstu3Cached();
private IValidateCodeProvidersDstu3.MyCodeSystemProviderDstu3 myCodeSystemProvider;
@RegisterExtension
public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx);
private RemoteTerminologyServiceValidationSupport mySvc;
@BeforeEach
public void before() {
String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort();
mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl);
mySvc.addClientInterceptor(new LoggingInterceptor(false).setLogRequestSummary(true).setLogResponseSummary(true));
myCodeSystemProvider = new IValidateCodeProvidersDstu3.MyCodeSystemProviderDstu3();
ourRestfulServerExtension.getRestfulServer().registerProviders(myCodeSystemProvider);
}
@AfterEach
public void after() {
ourRestfulServerExtension.getRestfulServer().unregisterProvider(List.of(myCodeSystemProvider));
}
@Test
void lookupCode_withParametersOutput_convertsCorrectly() {
String paramsAsString = ClasspathUtil.loadResource("/terminology/CodeSystem-lookup-output-with-subproperties.json");
IBaseResource baseResource = ourCtx.newJsonParser().parseResource(paramsAsString);
assertTrue(baseResource instanceof Parameters);
Parameters resultParameters = (Parameters) baseResource;
myCodeSystemProvider.setReturnParams(resultParameters);
LookupCodeRequest request = new LookupCodeRequest(IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, null, List.of("interfaces"));
// test
IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(null, request);
assertNotNull(outcome);
IBaseParameters theActualParameters = outcome.toParameters(ourCtx, request.getPropertyNames().stream().map(StringType::new).toList());
String actual = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(theActualParameters);
String expected = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resultParameters);
assertEquals(expected, actual);
}
}

View File

@ -1,294 +0,0 @@
package org.hl7.fhir.dstu3.hapi.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.LookupCodeRequest;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import ca.uhn.fhir.util.ClasspathUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
import org.hl7.fhir.dstu3.model.BooleanType;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.CodeType;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.Type;
import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import java.util.List;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Version specific tests for validation using RemoteTerminologyValidationSupport.
* The tests in this class simulate the call to a remote server and therefore, only tests the code in
* the RemoteTerminologyServiceValidationSupport itself. The remote client call is simulated using the test providers.
* @see RemoteTerminologyServiceValidationSupport
*
* Other operations are tested separately.
* @see RemoteTerminologyLookupCodeDstu3Test
*/
public class RemoteTerminologyServiceResourceProviderDstu3Test {
private static final String DISPLAY = "DISPLAY";
private static final String CODE_SYSTEM = "CODE_SYS";
private static final String CODE = "CODE";
private static final String VALUE_SET_URL = "http://value.set/url";
private static final String SAMPLE_MESSAGE = "This is a sample message";
private static final FhirContext ourCtx = FhirContext.forDstu3Cached();
private static final MyCodeSystemProvider ourCodeSystemProvider = new MyCodeSystemProvider();
private static final MyValueSetProvider ourValueSetProvider = new MyValueSetProvider();
@RegisterExtension
public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx, ourCodeSystemProvider,
ourValueSetProvider);
private RemoteTerminologyServiceValidationSupport mySvc;
@BeforeEach
public void before_ConfigureService() {
String myBaseUrl = "http://localhost:" + ourRestfulServerExtension.getPort();
mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, myBaseUrl);
mySvc.addClientInterceptor(new LoggingInterceptor(false).setLogRequestSummary(true).setLogResponseSummary(true));
}
@AfterEach
public void after_UnregisterProviders() {
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.ONCE);
ourRestfulServerExtension.getRestfulServer().getInterceptorService().unregisterAllInterceptors();
}
@Test
public void testValidateCodeInCodeSystem_BlankCode_ReturnsNull() {
IValidationSupport.CodeValidationResult outcome = mySvc
.validateCode(null, null, CODE_SYSTEM, null, DISPLAY, null);
assertNull(outcome);
}
@Test
public void testValidateCodeInCodeSystem_ProvidingMinimalInputs_ReturnsSuccess() {
createNextCodeSystemReturnParameters(true, null, null);
IValidationSupport.CodeValidationResult outcome = mySvc
.validateCode(null, null, CODE_SYSTEM, CODE, null, null);
assertNotNull(outcome);
assertEquals(CODE, outcome.getCode());
assertNull(outcome.getSeverity());
assertNull(outcome.getMessage());
assertEquals(CODE, ourCodeSystemProvider.myLastCode.getValue());
assertEquals(CODE_SYSTEM, ourCodeSystemProvider.myLastUrl.getValueAsString());
}
@Test
public void testValidateCodeInCodeSystem_WithMessageValue_ReturnsMessage() {
createNextCodeSystemReturnParameters(true, DISPLAY, SAMPLE_MESSAGE);
IValidationSupport.CodeValidationResult outcome = mySvc
.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, null);
assertNotNull(outcome);
assertEquals(CODE, outcome.getCode());
assertEquals(DISPLAY, outcome.getDisplay());
assertNull(outcome.getSeverity());
assertNull(outcome.getMessage());
assertEquals(CODE, ourCodeSystemProvider.myLastCode.getValue());
assertEquals(DISPLAY, ourCodeSystemProvider.myLastDisplay.getValue());
assertEquals(CODE_SYSTEM, ourCodeSystemProvider.myLastUrl.getValueAsString());
assertEquals(SAMPLE_MESSAGE, getParameterValue(ourCodeSystemProvider.myNextReturnParams, "message").toString());
}
@Test
public void testValidateCodeInCodeSystem_AssumeFailure_ReturnsFailureCodeAndFailureMessage() {
createNextCodeSystemReturnParameters(false, null, SAMPLE_MESSAGE);
IValidationSupport.CodeValidationResult outcome = mySvc
.validateCode(null, null, CODE_SYSTEM, CODE, null, null);
assertNotNull(outcome);
assertEquals(IValidationSupport.IssueSeverity.ERROR, outcome.getSeverity());
assertEquals(SAMPLE_MESSAGE, outcome.getMessage());
assertFalse(((BooleanType) getParameterValue(ourCodeSystemProvider.myNextReturnParams, "result")).booleanValue());
}
@Test
public void testValidateCodeInValueSet_ProvidingMinimalInputs_ReturnsSuccess() {
ourValueSetProvider.myNextReturnParams = new Parameters();
ourValueSetProvider.myNextReturnParams.addParameter().setName("result").setValue(new BooleanType(true));
IValidationSupport.CodeValidationResult outcome = mySvc
.validateCode(null, null, CODE_SYSTEM, CODE, null, VALUE_SET_URL);
assertNotNull(outcome);
assertEquals(CODE, outcome.getCode());
assertNull(outcome.getSeverity());
assertNull(outcome.getMessage());
assertEquals(CODE, ourValueSetProvider.myLastCode.getValue());
assertEquals(VALUE_SET_URL, ourValueSetProvider.myLastUrl.getValueAsString());
}
@Test
public void testValidateCodeInValueSet_WithMessageValue_ReturnsMessage() {
ourValueSetProvider.myNextReturnParams = new Parameters();
ourValueSetProvider.myNextReturnParams.addParameter().setName("result").setValue(new BooleanType(true));
ourValueSetProvider.myNextReturnParams.addParameter().setName("display").setValue(new StringType(DISPLAY));
ourValueSetProvider.myNextReturnParams.addParameter().setName("message").setValue(new StringType(SAMPLE_MESSAGE));
IValidationSupport.CodeValidationResult outcome = mySvc
.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, VALUE_SET_URL);
assertNotNull(outcome);
assertEquals(CODE, outcome.getCode());
assertEquals(DISPLAY, outcome.getDisplay());
assertNull(outcome.getSeverity());
assertNull(outcome.getMessage());
assertEquals(CODE, ourValueSetProvider.myLastCode.getValue());
assertEquals(DISPLAY, ourValueSetProvider.myLastDisplay.getValue());
assertEquals(VALUE_SET_URL, ourValueSetProvider.myLastUrl.getValueAsString());
assertEquals(SAMPLE_MESSAGE, getParameterValue(ourValueSetProvider.myNextReturnParams, "message").toString());
}
@Test
public void lookupCode_withParametersOutput_convertsCorrectly() {
String paramsAsString = ClasspathUtil.loadResource("/r4/CodeSystem-lookup-output-with-subproperties.json");
IBaseResource baseResource = ourCtx.newJsonParser().parseResource(paramsAsString);
assertTrue(baseResource instanceof Parameters);
Parameters resultParameters = (Parameters) baseResource;
ourCodeSystemProvider.myNextReturnParams = resultParameters;
LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, null, List.of("interfaces"));
// test
IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(null, request);
assertNotNull(outcome);
IBaseParameters theActualParameters = outcome.toParameters(ourCtx, request.getPropertyNames().stream().map(StringType::new).toList());
String actual = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(theActualParameters);
String expected = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resultParameters);
assertEquals(expected, actual);
}
private void createNextCodeSystemReturnParameters(boolean theResult, String theDisplay, String theMessage) {
ourCodeSystemProvider.myNextReturnParams = new Parameters();
ourCodeSystemProvider.myNextReturnParams.addParameter().setName("result").setValue(new BooleanType(theResult));
ourCodeSystemProvider.myNextReturnParams.addParameter().setName("display").setValue(new StringType(theDisplay));
if (theMessage != null) {
ourCodeSystemProvider.myNextReturnParams.addParameter().setName("message").setValue(new StringType(theMessage));
}
}
private Type getParameterValue(Parameters theParameters, String theParameterName) {
Optional<Parameters.ParametersParameterComponent> paramOpt = theParameters.getParameter()
.stream().filter(param -> param.getName().equals(theParameterName)).findFirst();
assertTrue(paramOpt.isPresent());
return paramOpt.get().getValue();
}
private static class MyCodeSystemProvider implements IResourceProvider {
private UriType myLastUrl;
private CodeType myLastCode;
private StringType myLastDisplay;
private Parameters myNextReturnParams;
@Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = StringType.class),
@OperationParam(name = "display", type = StringType.class)
})
public Parameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "url", min = 0, max = 1) UriType theCodeSystemUrl,
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay
) {
myLastUrl = theCodeSystemUrl;
myLastCode = theCode;
myLastDisplay = theDisplay;
return myNextReturnParams;
}
@Operation(name = JpaConstants.OPERATION_LOOKUP, idempotent = true, returnParameters= {
@OperationParam(name = "name", type = StringType.class, min = 1),
@OperationParam(name = "version", type = StringType.class),
@OperationParam(name = "display", type = StringType.class, min = 1),
@OperationParam(name = "abstract", type = BooleanType.class, min = 1),
@OperationParam(name = "property", type = StringType.class, min = 0, max = OperationParam.MAX_UNLIMITED)
})
public IBaseParameters lookup(
HttpServletRequest theServletRequest,
@OperationParam(name = "code", max = 1) CodeType theCode,
@OperationParam(name = "system",max = 1) UriType theSystem,
@OperationParam(name = "coding", max = 1) Coding theCoding,
@OperationParam(name = "version", max = 1) StringType theVersion,
@OperationParam(name = "displayLanguage", max = 1) CodeType theDisplayLanguage,
@OperationParam(name = "property", max = OperationParam.MAX_UNLIMITED) List<CodeType> thePropertyNames,
RequestDetails theRequestDetails
) {
myLastCode = theCode;
return myNextReturnParams;
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return CodeSystem.class;
}
}
private static class MyValueSetProvider implements IResourceProvider {
private Parameters myNextReturnParams;
private UriType myLastUrl;
private CodeType myLastCode;
private StringType myLastDisplay;
@Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = StringType.class),
@OperationParam(name = "display", type = StringType.class)
})
public Parameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "url", min = 0, max = 1) UriType theValueSetUrl,
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@OperationParam(name = "system", min = 0, max = 1) UriType theSystem,
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay,
@OperationParam(name = "valueSet") ValueSet theValueSet
) {
myLastUrl = theValueSetUrl;
myLastCode = theCode;
myLastDisplay = theDisplay;
return myNextReturnParams;
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return ValueSet.class;
}
}
}

View File

@ -0,0 +1,126 @@
package org.hl7.fhir.dstu3.hapi.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import ca.uhn.fhir.util.ClasspathUtil;
import org.hl7.fhir.common.hapi.validation.IRemoteTerminologyValidateCodeTest;
import org.hl7.fhir.common.hapi.validation.IValidationProviders;
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
import org.hl7.fhir.dstu3.model.BooleanType;
import org.hl7.fhir.dstu3.model.OperationOutcome;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.Resource;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension;
import java.util.List;
import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM;
import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.ERROR_CODE_UNKNOWN_CODE_IN_VALUE_SET;
/**
* Version specific tests for validation using RemoteTerminologyValidationSupport.
* The tests in this class simulate the call to a remote server and therefore, only tests the code in
* the RemoteTerminologyServiceValidationSupport itself. The remote client call is simulated using the test providers.
* @see RemoteTerminologyServiceValidationSupport
*
* Other operations are tested separately.
* @see RemoteTerminologyLookupCodeDstu3Test
*/
public class RemoteTerminologyValidateCodeDstu3Test implements IRemoteTerminologyValidateCodeTest {
private static final FhirContext ourCtx = FhirContext.forDstu3Cached();
@RegisterExtension
public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx);
private IValidateCodeProvidersDstu3.MyCodeSystemProviderDstu3 myCodeSystemProvider;
private IValidateCodeProvidersDstu3.MyValueSetProviderDstu3 myValueSetProvider;
private RemoteTerminologyServiceValidationSupport mySvc;
private String myCodeSystemError, myValueSetError;
@BeforeEach
public void before() {
String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort();
myCodeSystemError = ourCtx.getLocalizer().getMessage(
RemoteTerminologyServiceValidationSupport.class,
ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM, IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, baseUrl, IValidationProviders.ERROR_MESSAGE);
myValueSetError = ourCtx.getLocalizer().getMessage(
RemoteTerminologyServiceValidationSupport.class,
ERROR_CODE_UNKNOWN_CODE_IN_VALUE_SET, IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, IValidationProviders.VALUE_SET_URL, baseUrl, IValidationProviders.ERROR_MESSAGE);
mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl);
mySvc.addClientInterceptor(new LoggingInterceptor(false).setLogRequestSummary(true).setLogResponseSummary(true));
myCodeSystemProvider = new IValidateCodeProvidersDstu3.MyCodeSystemProviderDstu3();
myValueSetProvider = new IValidateCodeProvidersDstu3.MyValueSetProviderDstu3();
ourRestfulServerExtension.getRestfulServer().registerProviders(myCodeSystemProvider, myValueSetProvider);
}
@AfterEach
public void after() {
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.ONCE);
ourRestfulServerExtension.getRestfulServer().getInterceptorService().unregisterAllInterceptors();
ourRestfulServerExtension.getRestfulServer().unregisterProviders(List.of(myCodeSystemProvider, myValueSetProvider));
}
@Override
public RemoteTerminologyServiceValidationSupport getService() {
return mySvc;
}
@Override
public String getCodeSystemError() {
return myCodeSystemError;
}
@Override
public String getValueSetError() {
return myValueSetError;
}
@Override
public IValidateCodeProvidersDstu3.MyCodeSystemProviderDstu3 getCodeSystemProvider() {
return myCodeSystemProvider;
}
@Override
public IValidateCodeProvidersDstu3.MyValueSetProviderDstu3 getValueSetProvider() {
return myValueSetProvider;
}
@Override
public IBaseOperationOutcome getCodeSystemInvalidCodeOutcome() {
return ClasspathUtil.loadResource(getService().getFhirContext(), OperationOutcome.class, "/terminology/OperationOutcome-CodeSystem-invalid-code.json");
}
@Override
public IBaseOperationOutcome getValueSetInvalidCodeOutcome() {
return ClasspathUtil.loadResource(getService().getFhirContext(), OperationOutcome.class, "/terminology/OperationOutcome-ValueSet-invalid-code.json");
}
@Override
public Parameters createParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) {
Parameters parameters = new Parameters();
parameters.addParameter().setName("result").setValue(new BooleanType(theResult));
parameters.addParameter().setName("code").setValue(new StringType(IValidationProviders.CODE));
parameters.addParameter().setName("system").setValue(new UriType(IValidationProviders.CODE_SYSTEM));
parameters.addParameter().setName("version").setValue(new StringType(IValidationProviders.CODE_SYSTEM_VERSION));
parameters.addParameter().setName("display").setValue(new StringType(theDisplay));
parameters.addParameter().setName("message").setValue(new StringType(theMessage));
parameters.addParameter().setName("issues").setResource((Resource) theIssuesResource);
return parameters;
}
@Override
public void createCodeSystemReturnParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) {
myCodeSystemProvider.setReturnParams(createParameters(theResult, theDisplay, theMessage, theIssuesResource));
}
@Override
public void createValueSetReturnParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) {
myValueSetProvider.setReturnParams(createParameters(theResult, theDisplay, theMessage, theIssuesResource));
}
}

View File

@ -33,6 +33,7 @@ import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.conformance.ProfileUtilities; import org.hl7.fhir.r4.conformance.ProfileUtilities;
import org.hl7.fhir.r4.context.IWorkerContext; import org.hl7.fhir.r4.context.IWorkerContext;
import org.hl7.fhir.r4.fhirpath.FHIRPathEngine;
import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext; import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.r4.model.AllergyIntolerance; import org.hl7.fhir.r4.model.AllergyIntolerance;
import org.hl7.fhir.r4.model.Base; import org.hl7.fhir.r4.model.Base;
@ -68,7 +69,6 @@ import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.r4.terminologies.ValueSetExpander; import org.hl7.fhir.r4.terminologies.ValueSetExpander;
import org.hl7.fhir.r4.fhirpath.FHIRPathEngine;
import org.hl7.fhir.r5.elementmodel.JsonParser; import org.hl7.fhir.r5.elementmodel.JsonParser;
import org.hl7.fhir.r5.test.utils.ClassesLoadedFlags; import org.hl7.fhir.r5.test.utils.ClassesLoadedFlags;
import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor;
@ -83,6 +83,8 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
@ -128,13 +130,22 @@ public class FhirInstanceValidatorR4Test extends BaseValidationTestWithInlineMoc
private FhirValidator myFhirValidator; private FhirValidator myFhirValidator;
private ArrayList<String> myValidConcepts; private ArrayList<String> myValidConcepts;
private Set<String> myValidSystems = new HashSet<>(); private Set<String> myValidSystems = new HashSet<>();
private Set<String> myValidSystemsNotReturningIssues = new HashSet<>();
private Set<String> myValidValueSets = new HashSet<>(); private Set<String> myValidValueSets = new HashSet<>();
private Map<String, StructureDefinition> myStructureDefinitionMap = new HashMap<>(); private Map<String, StructureDefinition> myStructureDefinitionMap = new HashMap<>();
private CachingValidationSupport myValidationSupport; private CachingValidationSupport myValidationSupport;
private IValidationSupport myMockSupport; private IValidationSupport myMockSupport;
private void addValidConcept(String theSystem, String theCode) { private void addValidConcept(String theSystem, String theCode) {
myValidSystems.add(theSystem); addValidConcept(theSystem, theCode, true);
}
private void addValidConcept(String theSystem, String theCode, boolean theShouldSystemReturnIssuesForInvalidCode) {
if (theShouldSystemReturnIssuesForInvalidCode) {
myValidSystems.add(theSystem);
} else {
myValidSystemsNotReturningIssues.add(theSystem);
}
myValidConcepts.add(theSystem + "___" + theCode); myValidConcepts.add(theSystem + "___" + theCode);
} }
@ -296,7 +307,10 @@ public class FhirInstanceValidatorR4Test extends BaseValidationTestWithInlineMoc
retVal = new IValidationSupport.CodeValidationResult().setCode(code); retVal = new IValidationSupport.CodeValidationResult().setCode(code);
} else if (myValidSystems.contains(system)) { } else if (myValidSystems.contains(system)) {
final String message = "Unknown code (for '" + system + "#" + code + "')"; final String message = "Unknown code (for '" + system + "#" + code + "')";
return new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message).setCodeValidationIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(message, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE))); retVal = new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message).setCodeValidationIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(message, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE)));
} else if (myValidSystemsNotReturningIssues.contains(system)) {
final String message = "Unknown code (for '" + system + "#" + code + "')";
retVal = new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message);
} else { } else {
retVal = myDefaultValidationSupport.validateCode(new ValidationSupportContext(myDefaultValidationSupport), options, system, code, display, valueSetUrl); retVal = myDefaultValidationSupport.validateCode(new ValidationSupportContext(myDefaultValidationSupport), options, system, code, display, valueSetUrl);
} }
@ -1226,14 +1240,15 @@ public class FhirInstanceValidatorR4Test extends BaseValidationTestWithInlineMoc
} }
@Test // TODO: uncomment value false when https://github.com/hapifhir/org.hl7.fhir.core/issues/1766 is fixed
public void testValidateResourceContainingLoincCode() { @ParameterizedTest
addValidConcept("http://loinc.org", "1234567"); @ValueSource(booleans = {true, /*false*/})
public void testValidateResourceContainingLoincCode(boolean theShouldSystemReturnIssuesForInvalidCode) {
addValidConcept("http://loinc.org", "1234567", theShouldSystemReturnIssuesForInvalidCode);
Observation input = createObservationWithDefaultSubjectPerfomerEffective(); Observation input = createObservationWithDefaultSubjectPerfomerEffective();
input.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED); input.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
input.addIdentifier().setSystem("http://acme").setValue("12345"); input.addIdentifier().setSystem("http://acme").setValue("12345");
input.getEncounter().setReference("http://foo.com/Encounter/9"); input.getEncounter().setReference("http://foo.com/Encounter/9");
input.setStatus(ObservationStatus.FINAL); input.setStatus(ObservationStatus.FINAL);

View File

@ -0,0 +1,165 @@
package org.hl7.fhir.r4.validation;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import jakarta.servlet.http.HttpServletRequest;
import org.hl7.fhir.common.hapi.validation.IValidationProviders;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.UriType;
import org.hl7.fhir.r4.model.ValueSet;
import java.util.List;
public interface IValidateCodeProvidersR4 {
@SuppressWarnings("unused")
class MyCodeSystemProviderR4 implements IValidationProviders.IMyCodeSystemProvider {
private UriType mySystemUrl;
private CodeType myCode;
private StringType myDisplay;
private Exception myException;
private Parameters myReturnParams;
@Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = StringType.class),
@OperationParam(name = "display", type = StringType.class)
})
public Parameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "url", min = 0, max = 1) UriType theCodeSystemUrl,
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay
) throws Exception {
mySystemUrl = theCodeSystemUrl;
myCode = theCode;
myDisplay = theDisplay;
if (myException != null) {
throw myException;
}
return myReturnParams;
}
@Operation(name = JpaConstants.OPERATION_LOOKUP, idempotent = true, returnParameters= {
@OperationParam(name = "name", type = StringType.class, min = 1),
@OperationParam(name = "version", type = StringType.class),
@OperationParam(name = "display", type = StringType.class, min = 1),
@OperationParam(name = "abstract", type = BooleanType.class, min = 1),
@OperationParam(name = "property", type = StringType.class, min = 0, max = OperationParam.MAX_UNLIMITED)
})
public IBaseParameters lookup(
HttpServletRequest theServletRequest,
@OperationParam(name = "code", max = 1) CodeType theCode,
@OperationParam(name = "system",max = 1) UriType theSystem,
@OperationParam(name = "coding", max = 1) Coding theCoding,
@OperationParam(name = "version", max = 1) StringType ignoredTheVersion,
@OperationParam(name = "displayLanguage", max = 1) CodeType theDisplayLanguage,
@OperationParam(name = "property", max = OperationParam.MAX_UNLIMITED) List<CodeType> thePropertyNames,
RequestDetails theRequestDetails
) throws Exception {
mySystemUrl = theSystem;
myCode = theCode;
if (myException != null) {
throw myException;
}
return myReturnParams;
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return CodeSystem.class;
}
public void setException(Exception theException) {
myException = theException;
}
@Override
public void setReturnParams(IBaseParameters theParameters) {
myReturnParams = (Parameters) theParameters;
}
@Override
public String getCode() {
return myCode != null ? myCode.getValueAsString() : null;
}
@Override
public String getSystem() {
return mySystemUrl != null ? mySystemUrl.getValueAsString() : null;
}
public String getDisplay() {
return myDisplay != null ? myDisplay.getValue() : null;
}
}
@SuppressWarnings("unused")
class MyValueSetProviderR4 implements IValidationProviders.IMyValueSetProvider {
private Exception myException;
private Parameters myReturnParams;
private UriType mySystemUrl;
private UriType myValueSetUrl;
private CodeType myCode;
private StringType myDisplay;
@Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = StringType.class),
@OperationParam(name = "display", type = StringType.class)
})
public Parameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "url", min = 0, max = 1) UriType theValueSetUrl,
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@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 {
mySystemUrl = theSystem;
myValueSetUrl = theValueSetUrl;
myCode = theCode;
myDisplay = theDisplay;
if (myException != null) {
throw myException;
}
return myReturnParams;
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return ValueSet.class;
}
public void setException(Exception theException) {
myException = theException;
}
@Override
public void setReturnParams(IBaseParameters theParameters) {
myReturnParams = (Parameters) theParameters;
}
@Override
public String getCode() {
return myCode != null ? myCode.getValueAsString() : null;
}
@Override
public String getSystem() {
return mySystemUrl != null ? mySystemUrl.getValueAsString() : null;
}
@Override
public String getValueSet() {
return myValueSetUrl != null ? myValueSetUrl.getValueAsString() : null;
}
public String getDisplay() {
return myDisplay != null ? myDisplay.getValue() : null;
}
}
}

View File

@ -2,6 +2,7 @@ package org.hl7.fhir.r4.validation;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport.LookupCodeResult;
import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.annotation.OperationParam;
@ -12,6 +13,7 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.hl7.fhir.common.hapi.validation.IRemoteTerminologyLookupCodeTest; import org.hl7.fhir.common.hapi.validation.IRemoteTerminologyLookupCodeTest;
import org.hl7.fhir.common.hapi.validation.IValidationProviders;
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.IBaseParameters;
@ -27,6 +29,7 @@ import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.Type; import org.hl7.fhir.r4.model.Type;
import org.hl7.fhir.r4.model.UriType; import org.hl7.fhir.r4.model.UriType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
@ -39,7 +42,6 @@ import java.util.List;
import java.util.stream.Stream; import java.util.stream.Stream;
import static ca.uhn.fhir.context.support.IValidationSupport.ConceptDesignation; import static ca.uhn.fhir.context.support.IValidationSupport.ConceptDesignation;
import static ca.uhn.fhir.context.support.IValidationSupport.LookupCodeResult;
/** /**
* Version specific tests for CodeSystem $lookup against RemoteTerminologyValidationSupport. * Version specific tests for CodeSystem $lookup against RemoteTerminologyValidationSupport.
@ -50,19 +52,27 @@ public class RemoteTerminologyLookupCodeR4Test implements IRemoteTerminologyLook
@RegisterExtension @RegisterExtension
public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx); public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx);
private final RemoteTerminologyServiceValidationSupport mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx); private final RemoteTerminologyServiceValidationSupport mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx);
private final MyCodeSystemProviderR4 myCodeSystemProvider = new MyCodeSystemProviderR4(); private IValidateCodeProvidersR4.MyCodeSystemProviderR4 myCodeSystemProvider;
private MyLookupCodeProviderR4 myLookupCodeProviderR4;
@BeforeEach @BeforeEach
public void before() { public void before() {
String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort(); String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort();
mySvc.setBaseUrl(baseUrl); mySvc.setBaseUrl(baseUrl);
mySvc.addClientInterceptor(new LoggingInterceptor(true)); mySvc.addClientInterceptor(new LoggingInterceptor(true));
ourRestfulServerExtension.getRestfulServer().registerProvider(myCodeSystemProvider); myCodeSystemProvider = new IValidateCodeProvidersR4.MyCodeSystemProviderR4();
myLookupCodeProviderR4 = new MyLookupCodeProviderR4();
ourRestfulServerExtension.getRestfulServer().registerProviders(myCodeSystemProvider, myLookupCodeProviderR4);
}
@AfterEach
public void after() {
ourRestfulServerExtension.getRestfulServer().unregisterProvider(List.of(myCodeSystemProvider, myLookupCodeProviderR4));
} }
@Override @Override
public IMyCodeSystemProvider getCodeSystemProvider() { public IValidationProviders.IMyLookupCodeProvider getLookupCodeProvider() {
return myCodeSystemProvider; return myLookupCodeProviderR4;
} }
@Override @Override
@ -87,7 +97,6 @@ public class RemoteTerminologyLookupCodeR4Test implements IRemoteTerminologyLook
verifyLookupWithEmptyPropertyValue(thePropertyValue); verifyLookupWithEmptyPropertyValue(thePropertyValue);
} }
public static Stream<Arguments> getPropertyValueArguments() { public static Stream<Arguments> getPropertyValueArguments() {
return Stream.of( return Stream.of(
// FHIR R4 spec types // FHIR R4 spec types
@ -155,7 +164,8 @@ public class RemoteTerminologyLookupCodeR4Test implements IRemoteTerminologyLook
verifyLookupWithConceptDesignation(theConceptDesignation); verifyLookupWithConceptDesignation(theConceptDesignation);
} }
static class MyCodeSystemProviderR4 implements IMyCodeSystemProvider { @SuppressWarnings("unused")
static class MyLookupCodeProviderR4 implements IValidationProviders.IMyLookupCodeProvider {
private UriType mySystemUrl; private UriType mySystemUrl;
private CodeType myCode; private CodeType myCode;
private LookupCodeResult myLookupCodeResult; private LookupCodeResult myLookupCodeResult;

View File

@ -0,0 +1,69 @@
package org.hl7.fhir.r4.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.LookupCodeRequest;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import ca.uhn.fhir.util.ClasspathUtil;
import org.hl7.fhir.common.hapi.validation.IValidationProviders;
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class RemoteTerminologyLookupCodeWithResponseFileR4Test {
private static final FhirContext ourCtx = FhirContext.forR4Cached();
private IValidateCodeProvidersR4.MyCodeSystemProviderR4 myCodeSystemProvider;
@RegisterExtension
public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx);
private RemoteTerminologyServiceValidationSupport mySvc;
@BeforeEach
public void before() {
String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort();
mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl);
mySvc.addClientInterceptor(new LoggingInterceptor(false).setLogRequestSummary(true).setLogResponseSummary(true));
myCodeSystemProvider = new IValidateCodeProvidersR4.MyCodeSystemProviderR4();
ourRestfulServerExtension.getRestfulServer().registerProviders(myCodeSystemProvider);
}
@AfterEach
public void after() {
ourRestfulServerExtension.getRestfulServer().unregisterProvider(List.of(myCodeSystemProvider));
}
@Test
void lookupCode_withParametersOutput_convertsCorrectly() {
String paramsAsString = ClasspathUtil.loadResource("/terminology/CodeSystem-lookup-output-with-subproperties.json");
IBaseResource baseResource = ourCtx.newJsonParser().parseResource(paramsAsString);
assertTrue(baseResource instanceof Parameters);
Parameters resultParameters = (Parameters) baseResource;
myCodeSystemProvider.setReturnParams(resultParameters);
LookupCodeRequest request = new LookupCodeRequest(IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, null, List.of("interfaces"));
// test
IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(null, request);
assertNotNull(outcome);
IBaseParameters theActualParameters = outcome.toParameters(ourCtx, request.getPropertyNames().stream().map(StringType::new).toList());
String actual = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(theActualParameters);
String expected = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resultParameters);
assertEquals(expected, actual);
}
}

View File

@ -1,284 +0,0 @@
package org.hl7.fhir.r4.validation;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertFalse;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.LookupCodeRequest;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import ca.uhn.fhir.util.ClasspathUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.UriType;
import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Version specific tests for validation using RemoteTerminologyValidationSupport.
* The tests in this class simulate the call to a remote server and therefore, only tests the code in
* the RemoteTerminologyServiceValidationSupport itself. The remote client call is simulated using the test providers.
* @see RemoteTerminologyServiceValidationSupport
*
* Other operations are tested separately.
* @see RemoteTerminologyLookupCodeR4Test
* @see RemoteTerminologyServiceValidationSupportR4Test
*/
public class RemoteTerminologyServiceResourceProviderR4Test {
private static final String DISPLAY = "DISPLAY";
private static final String CODE_SYSTEM = "CODE_SYS";
private static final String CODE = "CODE";
private static final String VALUE_SET_URL = "http://value.set/url";
private static final String SAMPLE_MESSAGE = "This is a sample message";
private static final FhirContext ourCtx = FhirContext.forR4Cached();
private static final MyCodeSystemProvider ourCodeSystemProvider = new MyCodeSystemProvider();
private static final MyValueSetProvider ourValueSetProvider = new MyValueSetProvider();
@RegisterExtension
public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx, ourCodeSystemProvider,
ourValueSetProvider);
private RemoteTerminologyServiceValidationSupport mySvc;
@BeforeEach
public void before_ConfigureService() {
String myBaseUrl = "http://localhost:" + ourRestfulServerExtension.getPort();
mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, myBaseUrl);
mySvc.addClientInterceptor(new LoggingInterceptor(false).setLogRequestSummary(true).setLogResponseSummary(true));
}
@AfterEach
public void after_UnregisterProviders() {
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.ONCE);
ourRestfulServerExtension.getRestfulServer().getInterceptorService().unregisterAllInterceptors();
}
@Test
public void testValidateCodeInCodeSystem_BlankCode_ReturnsNull() {
IValidationSupport.CodeValidationResult outcome = mySvc
.validateCode(null, null, CODE_SYSTEM, null, DISPLAY, null);
assertNull(outcome);
}
@Test
public void testValidateCodeInCodeSystem_ProvidingMinimalInputs_ReturnsSuccess() {
createNextCodeSystemReturnParameters(true, null, null);
IValidationSupport.CodeValidationResult outcome = mySvc
.validateCode(null, null, CODE_SYSTEM, CODE, null, null);
assertNotNull(outcome);
assertEquals(CODE, outcome.getCode());
assertNull(outcome.getSeverity());
assertNull(outcome.getMessage());
assertEquals(CODE, ourCodeSystemProvider.myLastCode.getCode());
assertEquals(CODE_SYSTEM, ourCodeSystemProvider.myLastUrl.getValueAsString());
}
@Test
public void testValidateCodeInCodeSystem_WithMessageValue_ReturnsMessage() {
createNextCodeSystemReturnParameters(true, DISPLAY, SAMPLE_MESSAGE);
IValidationSupport.CodeValidationResult outcome = mySvc
.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, null);
assertNotNull(outcome);
assertEquals(CODE, outcome.getCode());
assertEquals(DISPLAY, outcome.getDisplay());
assertNull(outcome.getSeverity());
assertNull(outcome.getMessage());
assertEquals(CODE, ourCodeSystemProvider.myLastCode.getCode());
assertEquals(DISPLAY, ourCodeSystemProvider.myLastDisplay.getValue());
assertEquals(CODE_SYSTEM, ourCodeSystemProvider.myLastUrl.getValueAsString());
assertEquals(SAMPLE_MESSAGE, ourCodeSystemProvider.myNextReturnParams.getParameterValue("message").toString());
}
@Test
public void testValidateCodeInCodeSystem_AssumeFailure_ReturnsFailureCodeAndFailureMessage() {
createNextCodeSystemReturnParameters(false, null, SAMPLE_MESSAGE);
IValidationSupport.CodeValidationResult outcome = mySvc
.validateCode(null, null, CODE_SYSTEM, CODE, null, null);
assertNotNull(outcome);
assertEquals(IValidationSupport.IssueSeverity.ERROR, outcome.getSeverity());
assertEquals(SAMPLE_MESSAGE, outcome.getMessage());
assertFalse(((BooleanType) ourCodeSystemProvider.myNextReturnParams.getParameterValue("result")).booleanValue());
}
@Test
public void testValidateCodeInValueSet_ProvidingMinimalInputs_ReturnsSuccess() {
ourValueSetProvider.myNextReturnParams = new Parameters().addParameter("result", true);
IValidationSupport.CodeValidationResult outcome = mySvc
.validateCode(null, null, CODE_SYSTEM, CODE, null, VALUE_SET_URL);
assertNotNull(outcome);
assertEquals(CODE, outcome.getCode());
assertNull(outcome.getSeverity());
assertNull(outcome.getMessage());
assertEquals(CODE, ourValueSetProvider.myLastCode.getCode());
assertEquals(VALUE_SET_URL, ourValueSetProvider.myLastUrl.getValueAsString());
}
@Test
public void testValidateCodeInValueSet_WithMessageValue_ReturnsMessage() {
ourValueSetProvider.myNextReturnParams = new Parameters().addParameter("result", true)
.addParameter("display", DISPLAY)
.addParameter("message", SAMPLE_MESSAGE);
IValidationSupport.CodeValidationResult outcome = mySvc
.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, VALUE_SET_URL);
assertNotNull(outcome);
assertEquals(CODE, outcome.getCode());
assertEquals(DISPLAY, outcome.getDisplay());
assertNull(outcome.getSeverity());
assertNull(outcome.getMessage());
assertEquals(CODE, ourValueSetProvider.myLastCode.getCode());
assertEquals(DISPLAY, ourValueSetProvider.myLastDisplay.getValue());
assertEquals(VALUE_SET_URL, ourValueSetProvider.myLastUrl.getValueAsString());
assertEquals(SAMPLE_MESSAGE, ourValueSetProvider.myNextReturnParams.getParameterValue("message").toString());
}
@Test
public void lookupCode_withParametersOutput_convertsCorrectly() {
String paramsAsString = ClasspathUtil.loadResource("/r4/CodeSystem-lookup-output-with-subproperties.json");
IBaseResource baseResource = ourCtx.newJsonParser().parseResource(paramsAsString);
assertTrue(baseResource instanceof Parameters);
Parameters resultParameters = (Parameters) baseResource;
ourCodeSystemProvider.myNextReturnParams = resultParameters;
LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, null, List.of("interfaces"));
// test
IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(null, request);
assertNotNull(outcome);
IBaseParameters theActualParameters = outcome.toParameters(ourCtx, request.getPropertyNames().stream().map(StringType::new).toList());
String actual = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(theActualParameters);
String expected = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resultParameters);
assertEquals(expected, actual);
}
private void createNextCodeSystemReturnParameters(boolean theResult, String theDisplay, String theMessage) {
ourCodeSystemProvider.myNextReturnParams = new Parameters();
ourCodeSystemProvider.myNextReturnParams.addParameter("result", theResult);
ourCodeSystemProvider.myNextReturnParams.addParameter("display", theDisplay);
if (theMessage != null) {
ourCodeSystemProvider.myNextReturnParams.addParameter("message", theMessage);
}
}
private static class MyCodeSystemProvider implements IResourceProvider {
private UriType myLastUrl;
private CodeType myLastCode;
private StringType myLastDisplay;
private Parameters myNextReturnParams;
@Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = StringType.class),
@OperationParam(name = "display", type = StringType.class)
})
public Parameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "url", min = 0, max = 1) UriType theCodeSystemUrl,
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay
) {
myLastUrl = theCodeSystemUrl;
myLastCode = theCode;
myLastDisplay = theDisplay;
return myNextReturnParams;
}
@Operation(name = JpaConstants.OPERATION_LOOKUP, idempotent = true, returnParameters= {
@OperationParam(name = "name", type = StringType.class, min = 1),
@OperationParam(name = "version", type = StringType.class),
@OperationParam(name = "display", type = StringType.class, min = 1),
@OperationParam(name = "abstract", type = BooleanType.class, min = 1),
@OperationParam(name = "property", type = StringType.class, min = 0, max = OperationParam.MAX_UNLIMITED)
})
public IBaseParameters lookup(
HttpServletRequest theServletRequest,
@OperationParam(name = "code", max = 1) CodeType theCode,
@OperationParam(name = "system",max = 1) UriType theSystem,
@OperationParam(name = "coding", max = 1) Coding theCoding,
@OperationParam(name = "version", max = 1) StringType theVersion,
@OperationParam(name = "displayLanguage", max = 1) CodeType theDisplayLanguage,
@OperationParam(name = "property", max = OperationParam.MAX_UNLIMITED) List<CodeType> thePropertyNames,
RequestDetails theRequestDetails
) {
myLastCode = theCode;
return myNextReturnParams;
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return CodeSystem.class;
}
}
private static class MyValueSetProvider implements IResourceProvider {
private Parameters myNextReturnParams;
private UriType myLastUrl;
private CodeType myLastCode;
private StringType myLastDisplay;
@Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = StringType.class),
@OperationParam(name = "display", type = StringType.class)
})
public Parameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "url", min = 0, max = 1) UriType theValueSetUrl,
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@OperationParam(name = "system", min = 0, max = 1) UriType theSystem,
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay,
@OperationParam(name = "valueSet") ValueSet theValueSet
) {
myLastUrl = theValueSetUrl;
myLastCode = theCode;
myLastDisplay = theDisplay;
return myNextReturnParams;
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return ValueSet.class;
}
}
}

View File

@ -1,13 +1,11 @@
package org.hl7.fhir.r4.validation; package org.hl7.fhir.r4.validation;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.TranslateConceptResult; import ca.uhn.fhir.context.support.TranslateConceptResult;
import ca.uhn.fhir.context.support.TranslateConceptResults; import ca.uhn.fhir.context.support.TranslateConceptResults;
import ca.uhn.fhir.fhirpath.BaseValidationTestWithInlineMocks; import ca.uhn.fhir.fhirpath.BaseValidationTestWithInlineMocks;
import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.parser.IJsonLikeParser;
import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.annotation.OperationParam;
@ -15,21 +13,13 @@ import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.SummaryEnum; import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.client.api.IClientInterceptor;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpResponse;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.param.UriParam; import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.IResourceProvider; 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.test.utilities.server.RestfulServerExtension;
import ca.uhn.fhir.util.ParametersUtil;
import com.google.common.collect.Lists;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
import org.hl7.fhir.instance.model.api.IBaseCoding; import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.BooleanType; import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem;
@ -42,22 +32,14 @@ import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.UriType; import org.hl7.fhir.r4.model.UriType;
import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; 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.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; 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; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -72,12 +54,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
* *
* Other operations are tested separately. * Other operations are tested separately.
* @see RemoteTerminologyLookupCodeR4Test * @see RemoteTerminologyLookupCodeR4Test
* @see RemoteTerminologyServiceResourceProviderR4Test * @see RemoteTerminologyValidateCodeR4Test
*/ */
public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidationTestWithInlineMocks { public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidationTestWithInlineMocks {
private static final String DISPLAY = "DISPLAY";
private static final String CODE_SYSTEM = "CODE_SYS"; private static final String CODE_SYSTEM = "CODE_SYS";
private static final String CODE_SYSTEM_NAME = "Code System";
private static final String CODE = "CODE"; private static final String CODE = "CODE";
private static final String VALUE_SET_URL = "http://value.set/url"; private static final String VALUE_SET_URL = "http://value.set/url";
private static final String TARGET_SYSTEM = "http://target.system/url"; private static final String TARGET_SYSTEM = "http://target.system/url";
@ -89,9 +69,7 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat
private static final String TARGET_CODE_DISPLAY = "code"; private static final String TARGET_CODE_DISPLAY = "code";
private static final boolean REVERSE = true; private static final boolean REVERSE = true;
private static final String EQUIVALENCE_CODE = "equivalent"; private static final String EQUIVALENCE_CODE = "equivalent";
private static final String ERROR_MESSAGE = "This is an error message"; private static final String ERROR_MESSAGE = "This is an error message";
private static final String SUCCESS_MESSAGE = "This is a success message";
private static final FhirContext ourCtx = FhirContext.forR4Cached(); private static final FhirContext ourCtx = FhirContext.forR4Cached();
@ -114,83 +92,8 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat
mySvc.addClientInterceptor(new LoggingInterceptor(true)); mySvc.addClientInterceptor(new LoggingInterceptor(true));
} }
@AfterEach
public void after() {
assertThat(myValueSetProvider.myInvocationCount).isLessThan(2);
}
@Test @Test
public void testValidateCode_withBlankCode_returnsNull() { void fetchValueSet_forcesSummaryFalse() {
IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(null, null, CODE_SYSTEM, "", DISPLAY, VALUE_SET_URL);
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);
IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, VALUE_SET_URL);
assertNotNull(outcome);
assertEquals(CODE, outcome.getCode());
assertEquals(DISPLAY, outcome.getDisplay());
assertNull(outcome.getSeverity());
assertNull(outcome.getMessage());
assertEquals(CODE, myValueSetProvider.myLastCode.getCode());
assertEquals(DISPLAY, myValueSetProvider.myLastDisplay.getValue());
assertEquals(CODE_SYSTEM, myValueSetProvider.myLastSystem.getValue());
assertEquals(VALUE_SET_URL, myValueSetProvider.myLastUrl.getValue());
assertNull(myValueSetProvider.myLastValueSet);
}
@Test
void testFetchValueSet_forcesSummaryFalse() {
// given // given
myValueSetProvider.myNextReturnValueSets = new ArrayList<>(); myValueSetProvider.myNextReturnValueSets = new ArrayList<>();
@ -202,67 +105,7 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat
} }
@Test @Test
public void testValidateCode_forSystemCodeWithError_returnsCorrectly() { void translateCode_AllInParams_AllOutParams() {
createNextValueSetReturnParameters(false, null, ERROR_MESSAGE);
IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, VALUE_SET_URL);
assertNotNull(outcome);
assertNull(outcome.getCode());
assertNull(outcome.getDisplay());
assertEquals(IValidationSupport.IssueSeverity.ERROR, outcome.getSeverity());
assertEquals(ERROR_MESSAGE, outcome.getMessage());
assertEquals(CODE, myValueSetProvider.myLastCode.getCode());
assertEquals(DISPLAY, myValueSetProvider.myLastDisplay.getValue());
assertEquals(CODE_SYSTEM, myValueSetProvider.myLastSystem.getValue());
assertEquals(VALUE_SET_URL, myValueSetProvider.myLastUrl.getValue());
assertNull(myValueSetProvider.myLastValueSet);
}
@Test
public void testValidateCode_forCodeSystem_returnsCorrectly() {
myCodeSystemProvider.myNextValidationResult = new IValidationSupport.CodeValidationResult();
myCodeSystemProvider.myNextValidationResult.setCodeSystemVersion(CODE_SYSTEM);
myCodeSystemProvider.myNextValidationResult.setCode(CODE);
myCodeSystemProvider.myNextValidationResult.setCodeSystemName(CODE_SYSTEM_NAME);
myCodeSystemProvider.myNextValidationResult.setDisplay(DISPLAY);
myCodeSystemProvider.myNextValidationResult.setMessage(SUCCESS_MESSAGE);
IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, null);
assertNotNull(outcome);
assertEquals(CODE, outcome.getCode());
assertEquals(DISPLAY, outcome.getDisplay());
assertNull(outcome.getSeverity());
assertNull(outcome.getMessage());
assertEquals(CODE, myCodeSystemProvider.myCode.getCode());
assertEquals(CODE_SYSTEM, myCodeSystemProvider.mySystemUrl.getValueAsString());
}
@Test
public void testValidateCodeInValueSet_SystemCodeDisplayVS_Good() {
createNextValueSetReturnParameters(true, DISPLAY, null);
ValueSet valueSet = new ValueSet();
valueSet.setUrl(VALUE_SET_URL);
IValidationSupport.CodeValidationResult outcome = mySvc.validateCodeInValueSet(null, new ConceptValidationOptions(), CODE_SYSTEM, CODE, DISPLAY, valueSet);
assertNotNull(outcome);
assertEquals(CODE, outcome.getCode());
assertEquals(DISPLAY, outcome.getDisplay());
assertNull(outcome.getSeverity());
assertNull(outcome.getMessage());
assertEquals(CODE, myValueSetProvider.myLastCode.getCode());
assertEquals(DISPLAY, myValueSetProvider.myLastDisplay.getValue());
assertEquals(CODE_SYSTEM, myValueSetProvider.myLastSystem.getValue());
assertEquals(VALUE_SET_URL, myValueSetProvider.myLastUrl.getValueAsString());
assertNull(myValueSetProvider.myLastValueSet);
}
@Test
public void testTranslateCode_AllInParams_AllOutParams() {
myConceptMapProvider.myNextReturnParams = new Parameters(); myConceptMapProvider.myNextReturnParams = new Parameters();
myConceptMapProvider.myNextReturnParams.addParameter("result", true); myConceptMapProvider.myNextReturnParams.addParameter("result", true);
myConceptMapProvider.myNextReturnParams.addParameter("message", ERROR_MESSAGE); myConceptMapProvider.myNextReturnParams.addParameter("message", ERROR_MESSAGE);
@ -303,7 +146,7 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat
assertNotNull(results); assertNotNull(results);
assertTrue(results.getResult()); assertTrue(results.getResult());
assertEquals(results.getResults().size(), 2); assertEquals(2, results.getResults().size());
for(TranslateConceptResult result : results.getResults()) { for(TranslateConceptResult result : results.getResults()) {
assertEquals(singleResult, result); assertEquals(singleResult, result);
} }
@ -318,7 +161,7 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat
} }
@Test @Test
public void testTranslateCode_NoInParams_NoOutParams() { void translateCode_NoInParams_NoOutParams() {
myConceptMapProvider.myNextReturnParams = new Parameters(); myConceptMapProvider.myNextReturnParams = new Parameters();
List<IBaseCoding> codings = new ArrayList<>(); List<IBaseCoding> codings = new ArrayList<>();
@ -328,7 +171,7 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat
TranslateConceptResults results = mySvc.translateConcept(request); TranslateConceptResults results = mySvc.translateConcept(request);
assertNotNull(results); assertNotNull(results);
assertFalse(results.getResult()); assertFalse(results.getResult());
assertEquals(results.getResults().size(), 0); assertEquals(0, results.getResults().size());
assertNull(myConceptMapProvider.myLastCodeableConcept); assertNull(myConceptMapProvider.myLastCodeableConcept);
assertNull(myConceptMapProvider.myLastTargetCodeSystem); assertNull(myConceptMapProvider.myLastTargetCodeSystem);
@ -340,7 +183,7 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat
} }
@Test @Test
void testFetchCodeSystem_forcesSummaryFalse() { void fetchCodeSystem_forcesSummaryFalse() {
// given // given
myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>(); myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>();
@ -360,180 +203,8 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat
matchParam.addPart().setName("source").setValue(new UriType(CONCEPT_MAP_URL)); matchParam.addPart().setName("source").setValue(new UriType(CONCEPT_MAP_URL));
} }
/**
* Remote terminology services can be used to validate codes when code system is present,
* even when inferSystem is true
*/
@Nested
public class ExtractCodeSystemFromValueSet {
@Test
public void testUniqueComposeInclude() {
createNextValueSetReturnParameters(true, DISPLAY, null);
ValueSet valueSet = new ValueSet();
valueSet.setUrl(VALUE_SET_URL);
String systemUrl = "http://hl7.org/fhir/ValueSet/administrative-gender";
valueSet.setCompose(new ValueSet.ValueSetComposeComponent().setInclude(
Collections.singletonList(new ValueSet.ConceptSetComponent().setSystem(systemUrl)) ));
IValidationSupport.CodeValidationResult outcome = mySvc.validateCodeInValueSet(null,
new ConceptValidationOptions().setInferSystem(true), null, CODE, DISPLAY, valueSet);
// 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 {
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())));
IValidationSupport.CodeValidationResult outcome = mySvc.validateCodeInValueSet(null,
new ConceptValidationOptions().setInferSystem(true), null, CODE, DISPLAY, valueSet);
String unknownCodeForValueSetError = "Unknown code \"null#CODE\" for ValueSet with URL \"http://value.set/url\". The Remote Terminology server http://";
validateValidationErrorResult(outcome, unknownCodeForValueSetError, theServerMessage);
}
@ParameterizedTest
@MethodSource(value = "getRemoteTerminologyServerExceptions")
public void systemPresentCodeNotPresent_returnsValidationResultWithError(Exception theException, String theServerMessage) {
myValueSetProvider.myNextValidateCodeException = theException;
createNextValueSetReturnParameters(true, DISPLAY, null);
ValueSet valueSet = new ValueSet();
valueSet.setUrl(VALUE_SET_URL);
String systemUrl = "http://hl7.org/fhir/ValueSet/administrative-gender";
String systemUrl2 = "http://hl7.org/fhir/ValueSet/other-valueset";
valueSet.setCompose(new ValueSet.ValueSetComposeComponent().setInclude(
Lists.newArrayList(
new ValueSet.ConceptSetComponent().setSystem(systemUrl),
new ValueSet.ConceptSetComponent().setSystem(systemUrl2))));
IValidationSupport.CodeValidationResult outcome = mySvc.validateCodeInValueSet(null,
new ConceptValidationOptions().setInferSystem(true), null, CODE, DISPLAY, valueSet);
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 SystemPresentCodePresentValidatesOKNoVersioned() {
createNextValueSetReturnParameters(true, DISPLAY, null);
ValueSet valueSet = new ValueSet();
valueSet.setUrl(VALUE_SET_URL);
String systemUrl = "http://hl7.org/fhir/ValueSet/administrative-gender";
String systemUrl2 = "http://hl7.org/fhir/ValueSet/other-valueset";
valueSet.setCompose(new ValueSet.ValueSetComposeComponent().setInclude(
Lists.newArrayList(
new ValueSet.ConceptSetComponent().setSystem(systemUrl),
new ValueSet.ConceptSetComponent().setSystem(systemUrl2).setConcept(
Lists.newArrayList(
new ValueSet.ConceptReferenceComponent().setCode("not-the-code"),
new ValueSet.ConceptReferenceComponent().setCode(CODE) )
)) ));
TestClientInterceptor requestInterceptor = new TestClientInterceptor();
mySvc.addClientInterceptor(requestInterceptor);
IValidationSupport.CodeValidationResult outcome = mySvc.validateCodeInValueSet(null,
new ConceptValidationOptions().setInferSystem(true), null, CODE, DISPLAY, valueSet);
assertNotNull(outcome);
assertEquals(systemUrl2, requestInterceptor.getCapturedSystemParameter());
}
@Test
public void SystemPresentCodePresentValidatesOKVersioned() {
createNextValueSetReturnParameters(true, DISPLAY, null);
ValueSet valueSet = new ValueSet();
valueSet.setUrl(VALUE_SET_URL);
String systemUrl = "http://hl7.org/fhir/ValueSet/administrative-gender";
String systemVersion = "3.0.2";
String systemUrl2 = "http://hl7.org/fhir/ValueSet/other-valueset";
String system2Version = "4.0.1";
valueSet.setCompose(new ValueSet.ValueSetComposeComponent().setInclude(
Lists.newArrayList(
new ValueSet.ConceptSetComponent().setSystem(systemUrl).setVersion(systemVersion),
new ValueSet.ConceptSetComponent().setSystem(systemUrl2).setVersion(system2Version).setConcept(
Lists.newArrayList(
new ValueSet.ConceptReferenceComponent().setCode("not-the-code"),
new ValueSet.ConceptReferenceComponent().setCode(CODE) )
)) ));
TestClientInterceptor requestInterceptor = new TestClientInterceptor();
mySvc.addClientInterceptor(requestInterceptor);
IValidationSupport.CodeValidationResult outcome = mySvc.validateCodeInValueSet(null,
new ConceptValidationOptions().setInferSystem(true), null, CODE, DISPLAY, valueSet);
assertNotNull(outcome);
assertEquals(systemUrl2 + "|" + system2Version, requestInterceptor.getCapturedSystemParameter());
}
}
/**
* Captures the system parameter of the request
*/
private static class TestClientInterceptor implements IClientInterceptor {
private String capturedSystemParameter;
@Override
public void interceptRequest(IHttpRequest theRequest) {
try {
String content = theRequest.getRequestBodyFromStream();
if (content != null) {
IJsonLikeParser parser = (IJsonLikeParser) ourCtx.newJsonParser();
Parameters params = parser.parseResource(Parameters.class, content);
List<String> systemValues = ParametersUtil.getNamedParameterValuesAsString(
ourCtx, params, "system");
assertThat(systemValues).hasSize(1);
capturedSystemParameter = systemValues.get(0);
}
} catch (IOException theE) {
// ignore
}
}
@Override
public void interceptResponse(IHttpResponse theResponse) { }
public String getCapturedSystemParameter() { return capturedSystemParameter; }
}
}
@Test @Test
public void testIsValueSetSupported_False() { void isValueSetSupported_False() {
myValueSetProvider.myNextReturnValueSets = new ArrayList<>(); myValueSetProvider.myNextReturnValueSets = new ArrayList<>();
boolean outcome = mySvc.isValueSetSupported(null, "http://loinc.org/VS"); boolean outcome = mySvc.isValueSetSupported(null, "http://loinc.org/VS");
@ -542,7 +213,7 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat
} }
@Test @Test
public void testIsValueSetSupported_True() { void isValueSetSupported_True() {
myValueSetProvider.myNextReturnValueSets = new ArrayList<>(); myValueSetProvider.myNextReturnValueSets = new ArrayList<>();
myValueSetProvider.myNextReturnValueSets.add((ValueSet) new ValueSet().setId("ValueSet/123")); myValueSetProvider.myNextReturnValueSets.add((ValueSet) new ValueSet().setId("ValueSet/123"));
@ -552,7 +223,7 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat
} }
@Test @Test
public void testIsCodeSystemSupported_False() { void isCodeSystemSupported_False() {
myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>(); myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>();
boolean outcome = mySvc.isCodeSystemSupported(null, "http://loinc.org"); boolean outcome = mySvc.isCodeSystemSupported(null, "http://loinc.org");
@ -561,7 +232,7 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat
} }
@Test @Test
public void testIsCodeSystemSupported_True() { void isCodeSystemSupported_True() {
myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>(); myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>();
myCodeSystemProvider.myNextReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/123")); myCodeSystemProvider.myNextReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/123"));
@ -570,47 +241,17 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat
assertEquals("http://loinc.org", myCodeSystemProvider.myLastUrlParam.getValue()); assertEquals("http://loinc.org", myCodeSystemProvider.myLastUrlParam.getValue());
} }
private void createNextValueSetReturnParameters(boolean theResult, String theDisplay, String theMessage) { @SuppressWarnings("unused")
myValueSetProvider.myNextReturnParams = new Parameters() private static class MyCodeSystemProvider implements IResourceProvider {
.addParameter("result", theResult)
.addParameter("display", theDisplay)
.addParameter("message", theMessage);
}
static private class MyCodeSystemProvider implements IResourceProvider {
private SummaryEnum myLastSummaryParam; private SummaryEnum myLastSummaryParam;
private Exception myNextValidateCodeException;
private UriParam myLastUrlParam; private UriParam myLastUrlParam;
private List<CodeSystem> myNextReturnCodeSystems; private List<CodeSystem> myNextReturnCodeSystems;
private UriType mySystemUrl;
private CodeType myCode;
private IValidationSupport.CodeValidationResult myNextValidationResult;
@Override @Override
public Class<? extends IBaseResource> getResourceType() { public Class<? extends IBaseResource> getResourceType() {
return CodeSystem.class; return CodeSystem.class;
} }
@Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = StringType.class),
@OperationParam(name = "display", type = StringType.class)
})
public IBaseParameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@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);
}
@Search @Search
public List<CodeSystem> find(@RequiredParam(name = "url") UriParam theUrlParam, SummaryEnum theSummaryParam) { public List<CodeSystem> find(@RequiredParam(name = "url") UriParam theUrlParam, SummaryEnum theSummaryParam) {
myLastUrlParam = theUrlParam; myLastUrlParam = theUrlParam;
@ -620,46 +261,12 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat
} }
} }
@SuppressWarnings("unused")
private static class MyValueSetProvider implements IResourceProvider { private static class MyValueSetProvider implements IResourceProvider {
private Parameters myNextReturnParams;
private Exception myNextValidateCodeException;
private List<ValueSet> myNextReturnValueSets; private List<ValueSet> myNextReturnValueSets;
private UriType myLastUrl;
private CodeType myLastCode;
private int myInvocationCount;
private UriType myLastSystem;
private StringType myLastDisplay;
private ValueSet myLastValueSet;
private UriParam myLastUrlParam; private UriParam myLastUrlParam;
private SummaryEnum myLastSummaryParam; private SummaryEnum myLastSummaryParam;
@Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = StringType.class),
@OperationParam(name = "display", type = StringType.class)
})
public Parameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "url", min = 0, max = 1) UriType theValueSetUrl,
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@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;
}
@Search @Search
public List<ValueSet> find(@RequiredParam(name = "url") UriParam theUrlParam, SummaryEnum theSummaryParam) { public List<ValueSet> find(@RequiredParam(name = "url") UriParam theUrlParam, SummaryEnum theSummaryParam) {
myLastUrlParam = theUrlParam; myLastUrlParam = theUrlParam;
@ -675,6 +282,7 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat
} }
@SuppressWarnings("unused")
private static class MyConceptMapProvider implements IResourceProvider { private static class MyConceptMapProvider implements IResourceProvider {
private UriType myLastConceptMapUrl; private UriType myLastConceptMapUrl;
private StringType myLastConceptMapVersion; private StringType myLastConceptMapVersion;
@ -715,7 +323,5 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat
public Class<? extends IBaseResource> getResourceType() { public Class<? extends IBaseResource> getResourceType() {
return ConceptMap.class; return ConceptMap.class;
} }
} }
} }

View File

@ -0,0 +1,343 @@
package org.hl7.fhir.r4.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport.CodeValidationResult;
import ca.uhn.fhir.parser.IJsonLikeParser;
import ca.uhn.fhir.rest.client.api.IClientInterceptor;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpResponse;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
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.ClasspathUtil;
import ca.uhn.fhir.util.ParametersUtil;
import com.google.common.collect.Lists;
import org.hl7.fhir.common.hapi.validation.IRemoteTerminologyValidateCodeTest;
import org.hl7.fhir.common.hapi.validation.IValidationProviders;
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Resource;
import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.AfterEach;
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.Collections;
import java.util.List;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM;
import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.ERROR_CODE_UNKNOWN_CODE_IN_VALUE_SET;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Version specific tests for validation using RemoteTerminologyValidationSupport.
* The tests in this class simulate the call to a remote server and therefore, only tests the code in
* the RemoteTerminologyServiceValidationSupport itself. The remote client call is simulated using the test providers.
* @see RemoteTerminologyServiceValidationSupport
*
* Other operations are tested separately.
* @see RemoteTerminologyLookupCodeR4Test
* @see RemoteTerminologyServiceValidationSupportR4Test
*/
public class RemoteTerminologyValidateCodeR4Test implements IRemoteTerminologyValidateCodeTest {
private static final FhirContext ourCtx = FhirContext.forR4Cached();
@RegisterExtension
public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx);
private IValidateCodeProvidersR4.MyCodeSystemProviderR4 myCodeSystemProvider;
private IValidateCodeProvidersR4.MyValueSetProviderR4 myValueSetProvider;
private RemoteTerminologyServiceValidationSupport mySvc;
private String myCodeSystemError, myValueSetError;
@BeforeEach
public void before() {
String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort();
myCodeSystemError = ourCtx.getLocalizer().getMessage(
RemoteTerminologyServiceValidationSupport.class,
ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM, IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, baseUrl, IValidationProviders.ERROR_MESSAGE);
myValueSetError = ourCtx.getLocalizer().getMessage(
RemoteTerminologyServiceValidationSupport.class,
ERROR_CODE_UNKNOWN_CODE_IN_VALUE_SET, IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, IValidationProviders.VALUE_SET_URL, baseUrl, IValidationProviders.ERROR_MESSAGE);
mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl);
mySvc.addClientInterceptor(new LoggingInterceptor(false).setLogRequestSummary(true).setLogResponseSummary(true));
myCodeSystemProvider = new IValidateCodeProvidersR4.MyCodeSystemProviderR4();
myValueSetProvider = new IValidateCodeProvidersR4.MyValueSetProviderR4();
ourRestfulServerExtension.getRestfulServer().registerProviders(myCodeSystemProvider, myValueSetProvider);
}
@AfterEach
public void after() {
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.ONCE);
ourRestfulServerExtension.getRestfulServer().getInterceptorService().unregisterAllInterceptors();
ourRestfulServerExtension.getRestfulServer().unregisterProvider(myCodeSystemProvider);
}
@Override
public RemoteTerminologyServiceValidationSupport getService() {
return mySvc;
}
@Override
public IValidationProviders.IMyCodeSystemProvider getCodeSystemProvider() {
return myCodeSystemProvider;
}
@Override
public IValidationProviders.IMyValueSetProvider getValueSetProvider() {
return myValueSetProvider;
}
@Override
public String getCodeSystemError() {
return myCodeSystemError;
}
@Override
public String getValueSetError() {
return myValueSetError;
}
@Override
public IBaseOperationOutcome getCodeSystemInvalidCodeOutcome() {
return ClasspathUtil.loadResource(getService().getFhirContext(), OperationOutcome.class, "/terminology/OperationOutcome-CodeSystem-invalid-code.json");
}
@Override
public IBaseOperationOutcome getValueSetInvalidCodeOutcome() {
return ClasspathUtil.loadResource(getService().getFhirContext(), OperationOutcome.class, "/terminology/OperationOutcome-ValueSet-invalid-code.json");
}
@Override
public List<IValidationSupport.CodeValidationIssue> getCodeValidationIssues(IBaseOperationOutcome theOperationOutcome) {
return ((OperationOutcome)theOperationOutcome).getIssue().stream()
.map(issueComponent -> new IValidationSupport.CodeValidationIssue(
issueComponent.getDetails().getText(),
IValidationSupport.IssueSeverity.ERROR,
/* assume issue type is OperationOutcome.IssueType#CODEINVALID as it is the only match */
IValidationSupport.CodeValidationIssueCode.INVALID,
IValidationSupport.CodeValidationIssueCoding.INVALID_CODE))
.toList();
}
@Test
void validateCodeInValueSet_success() {
createValueSetReturnParameters(true, IValidationProviders.DISPLAY, null, null);
ValueSet valueSet = new ValueSet();
valueSet.setUrl(IValidationProviders.VALUE_SET_URL);
CodeValidationResult outcome = mySvc.validateCodeInValueSet(null, new ConceptValidationOptions(), IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, IValidationProviders.DISPLAY, valueSet);
assertNotNull(outcome);
assertEquals(IValidationProviders.CODE, outcome.getCode());
assertEquals(IValidationProviders.DISPLAY, outcome.getDisplay());
assertNull(outcome.getSeverity());
assertNull(outcome.getMessage());
assertEquals(IValidationProviders.CODE, myValueSetProvider.getCode());
assertEquals(IValidationProviders.DISPLAY, myValueSetProvider.getDisplay());
assertEquals(IValidationProviders.VALUE_SET_URL, myValueSetProvider.getValueSet());
}
@Override
public Parameters createParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) {
Parameters parameters = new Parameters()
.addParameter("code", IValidationProviders.CODE)
.addParameter("system", IValidationProviders.CODE_SYSTEM)
.addParameter("version", IValidationProviders.CODE_SYSTEM_VERSION)
.addParameter("display", theDisplay)
.addParameter("message", theMessage);
if (theResult != null) {
parameters.addParameter("result", theResult);
}
if (theIssuesResource != null) {
parameters.addParameter().setName("issues").setResource((Resource) theIssuesResource);
}
return parameters;
}
/**
* Remote terminology services can be used to validate codes when code system is present,
* even when inferSystem is true
*/
@Nested
class ExtractCodeSystemFromValueSet {
@Test
void validateCodeInValueSet_uniqueComposeInclude() {
createValueSetReturnParameters(true, IValidationProviders.DISPLAY, null, null);
ValueSet valueSet = new ValueSet();
valueSet.setUrl(IValidationProviders.VALUE_SET_URL);
String systemUrl = "http://hl7.org/fhir/ValueSet/administrative-gender";
valueSet.setCompose(new ValueSet.ValueSetComposeComponent().setInclude(
Collections.singletonList(new ValueSet.ConceptSetComponent().setSystem(systemUrl)) ));
CodeValidationResult outcome = mySvc.validateCodeInValueSet(null,
new ConceptValidationOptions().setInferSystem(true), null, IValidationProviders.CODE, IValidationProviders.DISPLAY, valueSet);
// 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 {
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")
void validateCodeInValueSet_systemNotPresent_returnsValidationResultWithError(Exception theException, String theServerMessage) {
myValueSetProvider.setException(theException);
createValueSetReturnParameters(true, IValidationProviders.DISPLAY, null, null);
ValueSet valueSet = new ValueSet();
valueSet.setUrl(IValidationProviders.VALUE_SET_URL);
valueSet.setCompose(new ValueSet.ValueSetComposeComponent().setInclude(
Lists.newArrayList(new ValueSet.ConceptSetComponent(), new ValueSet.ConceptSetComponent())));
CodeValidationResult outcome = mySvc.validateCodeInValueSet(null,
new ConceptValidationOptions().setInferSystem(true), null, IValidationProviders.CODE, IValidationProviders.DISPLAY, valueSet);
String unknownCodeForValueSetError = "Unknown code \"null#CODE\" for ValueSet with URL \"http://value.set/url\". The Remote Terminology server http://";
verifyErrorResultFromException(outcome, unknownCodeForValueSetError, theServerMessage);
}
@ParameterizedTest
@MethodSource(value = "getRemoteTerminologyServerExceptions")
void validateCodeInValueSet_systemPresentCodeNotPresent_returnsValidationResultWithError(Exception theException, String theServerMessage) {
myValueSetProvider.setException(theException);
createValueSetReturnParameters(true, IValidationProviders.DISPLAY, null, null);
ValueSet valueSet = new ValueSet();
valueSet.setUrl(IValidationProviders.VALUE_SET_URL);
String systemUrl = "http://hl7.org/fhir/ValueSet/administrative-gender";
String systemUrl2 = "http://hl7.org/fhir/ValueSet/other-valueset";
valueSet.setCompose(new ValueSet.ValueSetComposeComponent().setInclude(
Lists.newArrayList(
new ValueSet.ConceptSetComponent().setSystem(systemUrl),
new ValueSet.ConceptSetComponent().setSystem(systemUrl2))));
CodeValidationResult outcome = mySvc.validateCodeInValueSet(null,
new ConceptValidationOptions().setInferSystem(true), null, IValidationProviders.CODE, IValidationProviders.DISPLAY, valueSet);
String unknownCodeForValueSetError = "Unknown code \"null#CODE\" for ValueSet with URL \"http://value.set/url\". The Remote Terminology server http://";
verifyErrorResultFromException(outcome, unknownCodeForValueSetError, theServerMessage);
}
@Test
void validateCodeInValueSet_systemPresentCodePresentValidatesOKNoVersioned() {
createValueSetReturnParameters(true, IValidationProviders.DISPLAY, null, null);
ValueSet valueSet = new ValueSet();
valueSet.setUrl(IValidationProviders.VALUE_SET_URL);
String systemUrl = "http://hl7.org/fhir/ValueSet/administrative-gender";
String systemUrl2 = "http://hl7.org/fhir/ValueSet/other-valueset";
valueSet.setCompose(new ValueSet.ValueSetComposeComponent().setInclude(
Lists.newArrayList(
new ValueSet.ConceptSetComponent().setSystem(systemUrl),
new ValueSet.ConceptSetComponent().setSystem(systemUrl2).setConcept(
Lists.newArrayList(
new ValueSet.ConceptReferenceComponent().setCode("not-the-code"),
new ValueSet.ConceptReferenceComponent().setCode(IValidationProviders.CODE) )
)) ));
TestClientInterceptor requestInterceptor = new TestClientInterceptor();
mySvc.addClientInterceptor(requestInterceptor);
CodeValidationResult outcome = mySvc.validateCodeInValueSet(null,
new ConceptValidationOptions().setInferSystem(true), null, IValidationProviders.CODE, IValidationProviders.DISPLAY, valueSet);
assertNotNull(outcome);
assertEquals(systemUrl2, requestInterceptor.getCapturedSystemParameter());
}
@Test
void validateCodeInValueSet_systemPresentCodePresentValidatesOKVersioned() {
createValueSetReturnParameters(true, IValidationProviders.DISPLAY, null, null);
ValueSet valueSet = new ValueSet();
valueSet.setUrl(IValidationProviders.VALUE_SET_URL);
String systemUrl = "http://hl7.org/fhir/ValueSet/administrative-gender";
String systemVersion = "3.0.2";
String systemUrl2 = "http://hl7.org/fhir/ValueSet/other-valueset";
String system2Version = "4.0.1";
valueSet.setCompose(new ValueSet.ValueSetComposeComponent().setInclude(
Lists.newArrayList(
new ValueSet.ConceptSetComponent().setSystem(systemUrl).setVersion(systemVersion),
new ValueSet.ConceptSetComponent().setSystem(systemUrl2).setVersion(system2Version).setConcept(
Lists.newArrayList(
new ValueSet.ConceptReferenceComponent().setCode("not-the-code"),
new ValueSet.ConceptReferenceComponent().setCode(IValidationProviders.CODE) )
)) ));
TestClientInterceptor requestInterceptor = new TestClientInterceptor();
mySvc.addClientInterceptor(requestInterceptor);
CodeValidationResult outcome = mySvc.validateCodeInValueSet(null,
new ConceptValidationOptions().setInferSystem(true), null, IValidationProviders.CODE, IValidationProviders.DISPLAY, valueSet);
assertNotNull(outcome);
assertEquals(systemUrl2 + "|" + system2Version, requestInterceptor.getCapturedSystemParameter());
}
}
/**
* Captures the system parameter of the request
*/
private static class TestClientInterceptor implements IClientInterceptor {
private String capturedSystemParameter;
@Override
public void interceptRequest(IHttpRequest theRequest) {
try {
String content = theRequest.getRequestBodyFromStream();
if (content != null) {
IJsonLikeParser parser = (IJsonLikeParser) ourCtx.newJsonParser();
Parameters params = parser.parseResource(Parameters.class, content);
List<String> systemValues = ParametersUtil.getNamedParameterValuesAsString(
ourCtx, params, "system");
assertThat(systemValues).hasSize(1);
capturedSystemParameter = systemValues.get(0);
}
} catch (IOException theE) {
// ignore
}
}
@Override
public void interceptResponse(IHttpResponse theResponse) { }
public String getCapturedSystemParameter() { return capturedSystemParameter; }
}
}
}

View File

@ -48,12 +48,15 @@ import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor;
import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher; import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher;
import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel; import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel;
import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
@ -97,10 +100,19 @@ public class FhirInstanceValidatorR5Test extends BaseValidationTestWithInlineMoc
private Set<String> mySupportedValueSets = new HashSet<>(); private Set<String> mySupportedValueSets = new HashSet<>();
private Set<String> myValidSystems = new HashSet<>(); private Set<String> myValidSystems = new HashSet<>();
private Set<String> myValidSystemsNotReturningIssues = new HashSet<>();
private CachingValidationSupport myValidationSupport; private CachingValidationSupport myValidationSupport;
private void addValidConcept(String theSystem, String theCode) { private void addValidConcept(String theSystem, String theCode) {
myValidSystems.add(theSystem); addValidConcept(theSystem, theCode, true);
}
private void addValidConcept(String theSystem, String theCode, boolean theShouldSystemReturnIssuesForInvalidCode) {
if (theShouldSystemReturnIssuesForInvalidCode) {
myValidSystems.add(theSystem);
} else {
myValidSystemsNotReturningIssues.add(theSystem);
}
myValidConcepts.add(theSystem + "___" + theCode); myValidConcepts.add(theSystem + "___" + theCode);
} }
@ -188,7 +200,10 @@ public class FhirInstanceValidatorR5Test extends BaseValidationTestWithInlineMoc
retVal = new IValidationSupport.CodeValidationResult().setCode(code); retVal = new IValidationSupport.CodeValidationResult().setCode(code);
} else if (myValidSystems.contains(system)) { } else if (myValidSystems.contains(system)) {
String theMessage = "Unknown code (for '" + system + "#" + code + "')"; String theMessage = "Unknown code (for '" + system + "#" + code + "')";
return new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(theMessage).setCodeValidationIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(theMessage, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE))); retVal = new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(theMessage).setCodeValidationIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(theMessage, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE)));
} else if (myValidSystemsNotReturningIssues.contains(system)) {
final String message = "Unknown code (for '" + system + "#" + code + "')";
retVal = new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message);
} else { } else {
retVal = myDefaultValidationSupport.validateCode(new ValidationSupportContext(myDefaultValidationSupport), options, system, code, display, valueSetUrl); retVal = myDefaultValidationSupport.validateCode(new ValidationSupportContext(myDefaultValidationSupport), options, system, code, display, valueSetUrl);
} }
@ -788,9 +803,11 @@ public class FhirInstanceValidatorR5Test extends BaseValidationTestWithInlineMoc
} }
@Test // TODO: uncomment value false when https://github.com/hapifhir/org.hl7.fhir.core/issues/1766 is fixed
public void testValidateResourceContainingLoincCode() { @ParameterizedTest
addValidConcept("http://loinc.org", "1234567"); @ValueSource(booleans = {true, /*false*/})
public void testValidateResourceContainingLoincCode(boolean theShouldSystemReturnIssuesForInvalidCode) {
addValidConcept("http://loinc.org", "1234567", theShouldSystemReturnIssuesForInvalidCode);
Observation input = new Observation(); Observation input = new Observation();
// input.getMeta().addProfile("http://hl7.org/fhir/StructureDefinition/devicemetricobservation"); // input.getMeta().addProfile("http://hl7.org/fhir/StructureDefinition/devicemetricobservation");
@ -806,7 +823,6 @@ public class FhirInstanceValidatorR5Test extends BaseValidationTestWithInlineMoc
ourLog.debug(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input)); ourLog.debug(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input));
assertEquals(ResultSeverityEnum.ERROR, errors.get(0).getSeverity()); assertEquals(ResultSeverityEnum.ERROR, errors.get(0).getSeverity());
assertEquals("Unknown code (for 'http://loinc.org#12345')", errors.get(0).getMessage()); assertEquals("Unknown code (for 'http://loinc.org#12345')", errors.get(0).getMessage());
} }

View File

@ -0,0 +1,24 @@
{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "code-invalid",
"details": {
"coding": [
{
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code": "invalid-code"
}
],
"text": "Unknown code 'CODE' in the CodeSystem 'http://code.system/url' version '1.0.0'"
},
"location": [
"code"
],
"expression": [
"code"
]
}
]
}

View File

@ -0,0 +1,43 @@
{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "code-invalid",
"details": {
"coding": [
{
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code": "not-in-vs"
}
],
"text": "The provided code 'http://code.system/url#CODE' was not found in the value set 'http://value.set/url%7C1.0.0'"
},
"location": [
"code"
],
"expression": [
"code"
]
},
{
"severity": "error",
"code": "code-invalid",
"details": {
"coding": [
{
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code": "invalid-code"
}
],
"text": "Unknown code 'CODE' in the CodeSystem 'http://code.system/url' version '1.0.0'"
},
"location": [
"code"
],
"expression": [
"code"
]
}
]
}