From 8fc52748940cfce2ecdffb015dc1e0e2929d34ba Mon Sep 17 00:00:00 2001 From: Martha Mitran Date: Wed, 16 Oct 2024 07:16:41 -0700 Subject: [PATCH] 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 --- .../context/support/IValidationSupport.java | 2 +- .../java/ca/uhn/fhir/util/ParametersUtil.java | 42 +- ...tion-does-not-error-for-invalid-codes.yaml | 5 + ...-issues-remote-terminology-validation.yaml | 5 + ...OperationWithRemoteTerminologyR4Test.java} | 165 +++---- .../uhn/fhir/util/ParametersUtilR4Test.java | 26 +- ...teTerminologyServiceValidationSupport.java | 207 ++++++--- .../hapi/validation/ILookupCodeTest.java | 50 +-- .../IRemoteTerminologyLookupCodeTest.java | 8 +- .../IRemoteTerminologyValidateCodeTest.java | 17 + .../hapi/validation/IValidateCodeTest.java | 331 ++++++++++++++ .../hapi/validation/IValidationProviders.java | 39 ++ .../FhirInstanceValidatorDstu3Test.java | 31 +- .../IValidateCodeProvidersDstu3.java | 159 +++++++ .../RemoteTerminologyLookupCodeDstu3Test.java | 26 +- ...gyLookupCodeWithResponseFileDstu3Test.java | 68 +++ ...ologyServiceResourceProviderDstu3Test.java | 294 ------------ ...emoteTerminologyValidateCodeDstu3Test.java | 126 ++++++ .../FhirInstanceValidatorR4Test.java | 29 +- .../validation/IValidateCodeProvidersR4.java | 165 +++++++ .../RemoteTerminologyLookupCodeR4Test.java | 24 +- ...ologyLookupCodeWithResponseFileR4Test.java | 69 +++ ...minologyServiceResourceProviderR4Test.java | 284 ------------ ...inologyServiceValidationSupportR4Test.java | 424 +----------------- .../RemoteTerminologyValidateCodeR4Test.java | 343 ++++++++++++++ .../FhirInstanceValidatorR5Test.java | 28 +- ...stem-lookup-output-with-subproperties.json | 0 ...rationOutcome-CodeSystem-invalid-code.json | 24 + ...perationOutcome-ValueSet-invalid-code.json | 43 ++ 29 files changed, 1817 insertions(+), 1217 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6359-remote-terminology-validation-does-not-error-for-invalid-codes.yaml create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6359-support-for-out-parameter-issues-remote-terminology-validation.yaml rename hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/{ResourceProviderR4RemoteTerminologyTest.java => ValidateCodeOperationWithRemoteTerminologyR4Test.java} (65%) create mode 100644 hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IRemoteTerminologyValidateCodeTest.java create mode 100644 hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IValidateCodeTest.java create mode 100644 hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IValidationProviders.java create mode 100644 hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/IValidateCodeProvidersDstu3.java create mode 100644 hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeWithResponseFileDstu3Test.java delete mode 100644 hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyServiceResourceProviderDstu3Test.java create mode 100644 hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyValidateCodeDstu3Test.java create mode 100644 hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/IValidateCodeProvidersR4.java create mode 100644 hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeWithResponseFileR4Test.java delete mode 100644 hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyServiceResourceProviderR4Test.java create mode 100644 hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyValidateCodeR4Test.java rename hapi-fhir-validation/src/test/resources/{r4 => terminology}/CodeSystem-lookup-output-with-subproperties.json (100%) create mode 100644 hapi-fhir-validation/src/test/resources/terminology/OperationOutcome-CodeSystem-invalid-code.json create mode 100644 hapi-fhir-validation/src/test/resources/terminology/OperationOutcome-ValueSet-invalid-code.json diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java index d0990842fa2..71d561c1db3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java @@ -727,7 +727,7 @@ public interface IValidationSupport { return this; } - String getCodeSystemName() { + public String getCodeSystemName() { return myCodeSystemName; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java index 11fb8d6c012..77670e4a0c1 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java @@ -43,6 +43,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; @@ -58,20 +59,20 @@ public class ParametersUtil { public static Optional getNamedParameterValueAsString( FhirContext theCtx, IBaseParameters theParameters, String theParameterName) { Function, String> mapper = t -> defaultIfBlank(t.getValueAsString(), null); - return extractNamedParameters(theCtx, theParameters, theParameterName, mapper).stream() + return extractNamedParameterValues(theCtx, theParameters, theParameterName, mapper).stream() .findFirst(); } public static List getNamedParameterValuesAsString( FhirContext theCtx, IBaseParameters theParameters, String theParameterName) { Function, String> mapper = t -> defaultIfBlank(t.getValueAsString(), null); - return extractNamedParameters(theCtx, theParameters, theParameterName, mapper); + return extractNamedParameterValues(theCtx, theParameters, theParameterName, mapper); } public static List getNamedParameterValuesAsInteger( FhirContext theCtx, IBaseParameters theParameters, String theParameterName) { Function, Integer> mapper = t -> (Integer) t.getValue(); - return extractNamedParameters(theCtx, theParameters, theParameterName, mapper); + return extractNamedParameterValues(theCtx, theParameters, theParameterName, mapper); } public static Optional getNamedParameterValueAsInteger( @@ -80,6 +81,19 @@ public class ParametersUtil { .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 getNamedParameterResource( + FhirContext theCtx, IBaseParameters theParameters, String theParameterName) { + return extractNamedParameterResources(theCtx, theParameters, theParameterName).stream() + .findFirst(); + } + public static Optional getNamedParameter( FhirContext theCtx, IBaseResource theParameters, String theParameterName) { return getNamedParameters(theCtx, theParameters, theParameterName).stream() @@ -153,7 +167,7 @@ public class ParametersUtil { .map(t -> (Integer) t); } - private static List extractNamedParameters( + private static List extractNamedParameterValues( FhirContext theCtx, IBaseParameters theParameters, String theParameterName, @@ -170,7 +184,25 @@ public class ParametersUtil { .filter(t -> t instanceof IPrimitiveType) .map(t -> ((IPrimitiveType) t)) .map(theMapper) - .filter(t -> t != null) + .filter(Objects::nonNull) + .forEach(retVal::add); + } + return retVal; + } + + private static List extractNamedParameterResources( + FhirContext theCtx, IBaseParameters theParameters, String theParameterName) { + List retVal = new ArrayList<>(); + + List namedParameters = getNamedParameters(theCtx, theParameters, theParameterName); + for (IBase nextParameter : namedParameters) { + BaseRuntimeElementCompositeDefinition nextParameterDef = + (BaseRuntimeElementCompositeDefinition) theCtx.getElementDefinition(nextParameter.getClass()); + BaseRuntimeChildDefinition resourceChild = nextParameterDef.getChildByName("resource"); + List resourceValues = resourceChild.getAccessor().getValues(nextParameter); + resourceValues.stream() + .filter(IBaseResource.class::isInstance) + .map(t -> ((IBaseResource) t)) .forEach(retVal::add); } return retVal; diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6359-remote-terminology-validation-does-not-error-for-invalid-codes.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6359-remote-terminology-validation-does-not-error-for-invalid-codes.yaml new file mode 100644 index 00000000000..067bace030b --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6359-remote-terminology-validation-does-not-error-for-invalid-codes.yaml @@ -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." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6359-support-for-out-parameter-issues-remote-terminology-validation.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6359-support-for-out-parameter-issues-remote-terminology-validation.yaml new file mode 100644 index 00000000000..a7bf1be5b37 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6359-support-for-out-parameter-issues-remote-terminology-validation.yaml @@ -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." diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4RemoteTerminologyTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ValidateCodeOperationWithRemoteTerminologyR4Test.java similarity index 65% rename from hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4RemoteTerminologyTest.java rename to hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ValidateCodeOperationWithRemoteTerminologyR4Test.java index 0ee4291e067..4da204217f0 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4RemoteTerminologyTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ValidateCodeOperationWithRemoteTerminologyR4Test.java @@ -1,7 +1,5 @@ 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.jpa.config.JpaConfig; 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.exceptions.InvalidRequestException; 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.ValidationSupportChain; 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.Qualifier; -import jakarta.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.List; 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.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} * into the ValidationSupportChain, which tests the logic of dynamically selecting the correct Remote Terminology * implementation. It also exercises the code found in * {@link org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport#invokeRemoteValidateCode} */ -public class ResourceProviderR4RemoteTerminologyTest extends BaseResourceProviderR4Test { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderR4RemoteTerminologyTest.class); +public class ValidateCodeOperationWithRemoteTerminologyR4Test extends BaseResourceProviderR4Test { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidateCodeOperationWithRemoteTerminologyR4Test.class); private static final String DISPLAY = "DISPLAY"; 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_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 UNKNOWN_VALUE_SYSTEM_URI = "http://hl7.org/fhir/ValueSet/unknown-value-set"; - private static FhirContext ourCtx = FhirContext.forR4(); - private MyCodeSystemProvider myCodeSystemProvider = new MyCodeSystemProvider(); - private MyValueSetProvider myValueSetProvider = new MyValueSetProvider(); + private static final FhirContext ourCtx = FhirContext.forR4(); @RegisterExtension - public RestfulServerExtension myRestfulServerExtension = new RestfulServerExtension(ourCtx, myCodeSystemProvider, - myValueSetProvider); + protected static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx); private RemoteTerminologyServiceValidationSupport mySvc; + private MyCodeSystemProvider myCodeSystemProvider; + private MyValueSetProvider myValueSetProvider; @Autowired @Qualifier(JpaConfig.JPA_VALIDATION_SUPPORT_CHAIN) private ValidationSupportChain myValidationSupportChain; @BeforeEach - public void before_addRemoteTerminologySupport() throws Exception { - String baseUrl = "http://localhost:" + myRestfulServerExtension.getPort(); + public void before() throws Exception { + String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort(); mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl); myValidationSupportChain.addValidationSupport(0, mySvc); + myCodeSystemProvider = new MyCodeSystemProvider(); + myValueSetProvider = new MyValueSetProvider(); + ourRestfulServerExtension.registerProvider(myCodeSystemProvider); + ourRestfulServerExtension.registerProvider(myValueSetProvider); } @AfterEach - public void after_removeRemoteTerminologySupport() { + public void after() { myValidationSupportChain.removeValidationSupport(mySvc); - myRestfulServerExtension.getRestfulServer().getInterceptorService().unregisterAllInterceptors(); + ourRestfulServerExtension.getRestfulServer().getInterceptorService().unregisterAllInterceptors(); + ourRestfulServerExtension.unregisterProvider(myCodeSystemProvider); + ourRestfulServerExtension.unregisterProvider(myValueSetProvider); } @Test - public void testValidateCodeOperationOnCodeSystem_byCodingAndUrlWhereSystemIsDifferent_throwsException() { - assertThatExceptionOfType(InvalidRequestException.class).isThrownBy(() -> { - Parameters respParam = myClient + public void validateCodeOperationOnCodeSystem_byCodingAndUrlWhereSystemIsDifferent_throwsException() { + assertThatExceptionOfType(InvalidRequestException.class).isThrownBy(() -> myClient .operation() .onType(CodeSystem.class) .named(JpaConstants.OPERATION_VALIDATE_CODE) .withParameter(Parameters.class, "coding", new Coding().setSystem(CODE_SYSTEM_V2_0247_URI).setCode("P")) .andParameter("url", new UriType(INVALID_CODE_SYSTEM_URI)) - .execute(); - }); + .execute()); } @Test - public void testValidateCodeOperationOnCodeSystem_byCodingAndUrl_usingBuiltInCodeSystems() { - myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>(); - myCodeSystemProvider.myNextReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/v2-0247")); - createNextCodeSystemReturnParameters(true, DISPLAY, null); + public void validateCodeOperationOnCodeSystem_byCodingAndUrl_usingBuiltInCodeSystems() { + myCodeSystemProvider.myReturnCodeSystems = new ArrayList<>(); + myCodeSystemProvider.myReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/v2-0247")); + myCodeSystemProvider.myReturnParams = new Parameters(); + myCodeSystemProvider.myReturnParams.addParameter("result", true); + myCodeSystemProvider.myReturnParams.addParameter("display", DISPLAY); logAllConcepts(); @@ -116,13 +122,13 @@ public class ResourceProviderR4RemoteTerminologyTest extends BaseResourceProvide String resp = myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); ourLog.info(resp); - assertEquals(true, ((BooleanType) respParam.getParameterValue("result")).booleanValue()); + assertTrue(((BooleanType) respParam.getParameterValue("result")).booleanValue()); assertEquals(DISPLAY, respParam.getParameterValue("display").toString()); } @Test - public void testValidateCodeOperationOnCodeSystem_byCodingAndUrlWhereCodeSystemIsUnknown_returnsFalse() { - myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>(); + public void validateCodeOperationOnCodeSystem_byCodingAndUrlWhereCodeSystemIsUnknown_returnsFalse() { + myCodeSystemProvider.myReturnCodeSystems = new ArrayList<>(); Parameters respParam = myClient .operation() @@ -142,7 +148,7 @@ public class ResourceProviderR4RemoteTerminologyTest extends BaseResourceProvide } @Test - public void testValidateCodeOperationOnValueSet_byCodingAndUrlWhereSystemIsDifferent_throwsException() { + public void validateCodeOperationOnValueSet_byCodingAndUrlWhereSystemIsDifferent_throwsException() { try { myClient.operation() .onType(ValueSet.class) @@ -159,12 +165,14 @@ public class ResourceProviderR4RemoteTerminologyTest extends BaseResourceProvide } @Test - public void testValidateCodeOperationOnValueSet_byUrlAndSystem_usingBuiltInCodeSystems() { - myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>(); - myCodeSystemProvider.myNextReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/list-example-use-codes")); - myValueSetProvider.myNextReturnValueSets = new ArrayList<>(); - myValueSetProvider.myNextReturnValueSets.add((ValueSet) new ValueSet().setId("ValueSet/list-example-codes")); - createNextValueSetReturnParameters(true, DISPLAY, null); + public void validateCodeOperationOnValueSet_byUrlAndSystem_usingBuiltInCodeSystems() { + myCodeSystemProvider.myReturnCodeSystems = new ArrayList<>(); + myCodeSystemProvider.myReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/list-example-use-codes")); + myValueSetProvider.myReturnValueSets = new ArrayList<>(); + myValueSetProvider.myReturnValueSets.add((ValueSet) new ValueSet().setId("ValueSet/list-example-codes")); + myValueSetProvider.myReturnParams = new Parameters(); + myValueSetProvider.myReturnParams.addParameter("result", true); + myValueSetProvider.myReturnParams.addParameter("display", DISPLAY); Parameters respParam = myClient .operation() @@ -179,17 +187,19 @@ public class ResourceProviderR4RemoteTerminologyTest extends BaseResourceProvide String resp = myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); ourLog.info(resp); - assertEquals(true, ((BooleanType) respParam.getParameterValue("result")).booleanValue()); + assertTrue(((BooleanType) respParam.getParameterValue("result")).booleanValue()); assertEquals(DISPLAY, respParam.getParameterValue("display").toString()); } @Test - public void testValidateCodeOperationOnValueSet_byUrlSystemAndCode() { - myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>(); - myCodeSystemProvider.myNextReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/list-example-use-codes")); - myValueSetProvider.myNextReturnValueSets = new ArrayList<>(); - myValueSetProvider.myNextReturnValueSets.add((ValueSet) new ValueSet().setId("ValueSet/list-example-codes")); - createNextValueSetReturnParameters(true, DISPLAY_BODY_MASS_INDEX, null); + public void validateCodeOperationOnValueSet_byUrlSystemAndCode() { + myCodeSystemProvider.myReturnCodeSystems = new ArrayList<>(); + myCodeSystemProvider.myReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/list-example-use-codes")); + myValueSetProvider.myReturnValueSets = new ArrayList<>(); + myValueSetProvider.myReturnValueSets.add((ValueSet) new ValueSet().setId("ValueSet/list-example-codes")); + myValueSetProvider.myReturnParams = new Parameters(); + myValueSetProvider.myReturnParams.addParameter("result", true); + myValueSetProvider.myReturnParams.addParameter("display", DISPLAY_BODY_MASS_INDEX); Parameters respParam = myClient .operation() @@ -203,13 +213,13 @@ public class ResourceProviderR4RemoteTerminologyTest extends BaseResourceProvide String resp = myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); 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()); } @Test - public void testValidateCodeOperationOnValueSet_byCodingAndUrlWhereValueSetIsUnknown_returnsFalse() { - myValueSetProvider.myNextReturnValueSets = new ArrayList<>(); + public void validateCodeOperationOnValueSet_byCodingAndUrlWhereValueSetIsUnknown_returnsFalse() { + myValueSetProvider.myReturnValueSets = new ArrayList<>(); Parameters respParam = myClient .operation() @@ -228,33 +238,10 @@ public class ResourceProviderR4RemoteTerminologyTest extends BaseResourceProvide " - Unknown or unusable ValueSet[" + UNKNOWN_VALUE_SYSTEM_URI + "]"); } - private void createNextCodeSystemReturnParameters(boolean theResult, String theDisplay, String theMessage) { - 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); - } - } - + @SuppressWarnings("unused") private static class MyCodeSystemProvider implements IResourceProvider { - - private UriParam myLastUrlParam; - private List myNextReturnCodeSystems; - private int myInvocationCount; - private UriType myLastUrl; - private CodeType myLastCode; - private StringType myLastDisplay; - private Parameters myNextReturnParams; + private List myReturnCodeSystems; + private Parameters myReturnParams; @Operation(name = "validate-code", idempotent = true, returnParameters = { @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 = "display", min = 0, max = 1) StringType theDisplay ) { - myInvocationCount++; - myLastUrl = theCodeSystemUrl; - myLastCode = theCode; - myLastDisplay = theDisplay; - return myNextReturnParams; + return myReturnParams; } @Search public List find(@RequiredParam(name = "url") UriParam theUrlParam) { - myLastUrlParam = theUrlParam; - assert myNextReturnCodeSystems != null; - return myNextReturnCodeSystems; + assert myReturnCodeSystems != null; + return myReturnCodeSystems; } @Override @@ -288,16 +270,10 @@ public class ResourceProviderR4RemoteTerminologyTest extends BaseResourceProvide } } + @SuppressWarnings("unused") private static class MyValueSetProvider implements IResourceProvider { - private Parameters myNextReturnParams; - private List myNextReturnValueSets; - private UriType myLastUrl; - private CodeType myLastCode; - private int myInvocationCount; - private UriType myLastSystem; - private StringType myLastDisplay; - private ValueSet myLastValueSet; - private UriParam myLastUrlParam; + private Parameters myReturnParams; + private List myReturnValueSets; @Operation(name = "validate-code", idempotent = true, returnParameters = { @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 = "valueSet") ValueSet theValueSet ) { - myInvocationCount++; - myLastUrl = theValueSetUrl; - myLastCode = theCode; - myLastSystem = theSystem; - myLastDisplay = theDisplay; - myLastValueSet = theValueSet; - return myNextReturnParams; + return myReturnParams; } @Search public List find(@RequiredParam(name = "url") UriParam theUrlParam) { - myLastUrlParam = theUrlParam; - assert myNextReturnValueSets != null; - return myNextReturnValueSets; + assert myReturnValueSets != null; + return myReturnValueSets; } @Override diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/ParametersUtilR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/ParametersUtilR4Test.java index 86abec59f39..53498925d22 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/ParametersUtilR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/ParametersUtilR4Test.java @@ -3,7 +3,9 @@ package ca.uhn.fhir.util; import ca.uhn.fhir.context.FhirContext; import org.hl7.fhir.instance.model.api.IBase; 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.OperationOutcome; import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.StringType; import org.junit.jupiter.api.Test; @@ -17,7 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; public class ParametersUtilR4Test { private static final String TEST_PERSON_ID = "Person/32768"; - private static FhirContext ourFhirContext = FhirContext.forR4(); + private static final FhirContext ourFhirContext = FhirContext.forR4Cached(); @Test public void testCreateParameters() { @@ -47,7 +49,7 @@ public class ParametersUtilR4Test { p.addParameter() .setValue(new StringType("VALUE4")); - List values = ParametersUtil.getNamedParameterValuesAsString(FhirContext.forR4(), p, "foo"); + List values = ParametersUtil.getNamedParameterValuesAsString(ourFhirContext, p, "foo"); assertThat(values).containsExactly("VALUE1", "VALUE2"); } @@ -58,7 +60,7 @@ public class ParametersUtilR4Test { .setName("foo") .setValue(new IntegerType(123)); - Optional value = ParametersUtil.getNamedParameterValueAsInteger(FhirContext.forR4(), p, "foo"); + Optional value = ParametersUtil.getNamedParameterValueAsInteger(ourFhirContext, p, "foo"); assertThat(value).isPresent(); assertEquals(123, value.get().intValue()); } @@ -80,7 +82,7 @@ public class ParametersUtilR4Test { @Test public void testAddPartDecimalNoScientificNotation() { // setup - Double decimalValue = Double.valueOf("10000000"); + double decimalValue = 10000000; IBaseParameters parameters = ParametersUtil.newInstance(ourFhirContext); IBase resultPart = ParametersUtil.addParameterToParameters(ourFhirContext, parameters, "link"); @@ -92,4 +94,20 @@ public class ParametersUtilR4Test { List results = ParametersUtil.getNamedParameterPartAsString(ourFhirContext, parameters, "link", "linkCreated"); 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 fooResource = ParametersUtil.getNamedParameterResource(ourFhirContext,p, "foo"); + assertThat(fooResource).isPresent(); + assertThat(fooResource.get()).isEqualTo(outcome); + + Optional barResource = ParametersUtil.getNamedParameterResource(ourFhirContext,p, "bar"); + assertThat(barResource).isEmpty(); + } } diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupport.java index b53a0c70dcd..370f8b423dd 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupport.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupport.java @@ -22,12 +22,14 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IBaseBundle; 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.IBaseResource; import org.hl7.fhir.r4.model.Base; 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.OperationOutcome; import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent; import org.hl7.fhir.r4.model.Property; @@ -37,9 +39,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; +import java.util.Collection; import java.util.List; 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.isNotBlank; @@ -52,6 +59,9 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; public class RemoteTerminologyServiceValidationSupport extends BaseValidationSupport implements IValidationSupport { 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 final List myClientInterceptors = new ArrayList<>(); @@ -192,8 +202,8 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup // e.g. ClientResponseInterceptorModificationTemplate ourLog.error(e.getMessage(), e); LookupCodeResult result = LookupCodeResult.notFound(system, code); - result.setErrorMessage( - getErrorMessage("unknownCodeInSystem", system, code, client.getServerBase(), e.getMessage())); + result.setErrorMessage(getErrorMessage( + ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM, system, code, getBaseUrl(), e.getMessage())); return result; } if (outcome != null && !outcome.isEmpty()) { @@ -575,6 +585,21 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup 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 = buildValidateCodeInputParameters(theCodeSystem, theCode, theDisplay, theValueSetUrl, theValueSet); @@ -583,93 +608,167 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup resourceType = "CodeSystem"; } - IBaseParameters output; try { - output = client.operation() + IBaseParameters output = client.operation() .onType(resourceType) .named("validate-code") .withParameters(input) .execute(); + return createCodeValidationResult(output, errorMessageBuilder, theCode); } catch (ResourceNotFoundException | InvalidRequestException ex) { ourLog.error(ex.getMessage(), ex); - CodeValidationResult result = new CodeValidationResult(); - result.setSeverity(IssueSeverity.ERROR); - String errorMessage = buildErrorMessage( - theCodeSystem, theCode, theValueSetUrl, theValueSet, client.getServerBase(), ex.getMessage()); - result.setMessage(errorMessage); + String errorMessage = errorMessageBuilder.buildErrorMessage(ex.getMessage()); + CodeValidationIssueCode issueCode = ex instanceof ResourceNotFoundException + ? CodeValidationIssueCode.NOT_FOUND + : CodeValidationIssueCode.CODE_INVALID; + 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 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 systemValue = getNamedParameterValueAsString(fhirContext, theOutput, "system"); + systemValue.ifPresent(result::setCodeSystemName); + Optional versionValue = getNamedParameterValueAsString(fhirContext, theOutput, "version"); + versionValue.ifPresent(result::setCodeSystemVersion); + Optional 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; } - List resultValues = ParametersUtil.getNamedParameterValuesAsString(getFhirContext(), output, "result"); - if (resultValues.isEmpty() || isBlank(resultValues.get(0))) { - return null; - } - Validate.isTrue(resultValues.size() == 1, "Response contained %d 'result' values", resultValues.size()); + // for now assume severity ERROR, we may need to process the following for success cases as well + result.setSeverity(IssueSeverity.ERROR); - boolean success = "true".equalsIgnoreCase(resultValues.get(0)); - - CodeValidationResult retVal = new CodeValidationResult(); - if (success) { - - retVal.setCode(theCode); - List displayValues = - ParametersUtil.getNamedParameterValuesAsString(getFhirContext(), output, "display"); - if (!displayValues.isEmpty()) { - retVal.setDisplay(displayValues.get(0)); - } + Optional messageValue = getNamedParameterValueAsString(fhirContext, theOutput, "message"); + messageValue.ifPresent(value -> result.setMessage(theMessageBuilder.buildErrorMessage(value))); + Optional 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 { - - retVal.setSeverity(IssueSeverity.ERROR); - List messageValues = - ParametersUtil.getNamedParameterValuesAsString(getFhirContext(), output, "message"); - if (!messageValues.isEmpty()) { - retVal.setMessage(messageValues.get(0)); - } + // create a validation issue out of the message + // this is a workaround to overcome an issue in the FHIR Validator library + // where ValueSet bindings are only reading issues but not messages + // @see https://github.com/hapifhir/org.hl7.fhir.core/issues/1766 + result.addCodeValidationIssue(createCodeValidationIssue(result.getMessage())); } - return retVal; + return result; } - private String buildErrorMessage( - String theCodeSystem, - String theCode, - String theValueSetUrl, - IBaseResource theValueSet, - String theServerUrl, - String theServerMessage) { - if (theValueSetUrl == null && theValueSet == null) { - return getErrorMessage("unknownCodeInSystem", theCodeSystem, theCode, theServerUrl, theServerMessage); - } else { - return getErrorMessage( - "unknownCodeInValueSet", theCodeSystem, theCode, theValueSetUrl, theServerUrl, theServerMessage); + /** + * Creates a list of {@link ca.uhn.fhir.context.support.IValidationSupport.CodeValidationIssue} from the issues + * returned by the $validate-code operation. + * Please note that this method should only be used for Remote Terminology for now as it only translates + * issues text/message and assumes all other fields. + * When issues will be supported across all validators in hapi-fhir, a proper generic conversion method should + * be available and this method will be deleted. + * + * @param theOperationOutcome the outcome of the $validate-code operation + * @param theFhirVersion the FHIR version + * @return the list of {@link ca.uhn.fhir.context.support.IValidationSupport.CodeValidationIssue} + */ + public static Optional> 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 createCodeValidationIssuesR4(OperationOutcome theOperationOutcome) { + return theOperationOutcome.getIssue().stream() + .map(issueComponent -> + createCodeValidationIssue(issueComponent.getDetails().getText())) + .collect(Collectors.toList()); + } + + private static Collection 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( 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) { - ParametersUtil.addParameterToParametersUri(getFhirContext(), params, "url", theCodeSystem); - ParametersUtil.addParameterToParametersString(getFhirContext(), params, "code", theCode); + ParametersUtil.addParameterToParametersUri(fhirContext, params, "url", theCodeSystem); + ParametersUtil.addParameterToParametersString(fhirContext, params, "code", theCode); if (isNotBlank(theDisplay)) { - ParametersUtil.addParameterToParametersString(getFhirContext(), params, "display", theDisplay); + ParametersUtil.addParameterToParametersString(fhirContext, params, "display", theDisplay); } return params; } 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)) { - ParametersUtil.addParameterToParametersUri(getFhirContext(), params, "system", theCodeSystem); + ParametersUtil.addParameterToParametersUri(fhirContext, params, "system", theCodeSystem); } if (isNotBlank(theDisplay)) { - ParametersUtil.addParameterToParametersString(getFhirContext(), params, "display", theDisplay); + ParametersUtil.addParameterToParametersString(fhirContext, params, "display", theDisplay); } if (theValueSet != null) { - ParametersUtil.addParameterToParameters(getFhirContext(), params, "valueSet", theValueSet); + ParametersUtil.addParameterToParameters(fhirContext, params, "valueSet", theValueSet); } return params; } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/ILookupCodeTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/ILookupCodeTest.java index 0b20821af34..bb7eaf1c17b 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/ILookupCodeTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/ILookupCodeTest.java @@ -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.StringConceptProperty; import ca.uhn.fhir.context.support.LookupCodeRequest; -import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import org.hl7.fhir.instance.model.api.IBaseDatatype; 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 java.util.stream.IntStream.range; 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.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -40,20 +45,15 @@ import static org.junit.jupiter.api.Assertions.fail; * e.g. assertEqualConceptProperty */ 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(); - IMyCodeSystemProvider getCodeSystemProvider(); + IValidationProviders.IMyLookupCodeProvider getLookupCodeProvider(); @Test default void lookupCode_forCodeSystemWithBlankCode_throwsException() { + IValidationSupport service = getService(); + LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, ""); try { - getService().lookupCode(null, new LookupCodeRequest(CODE_SYSTEM, "")); + service.lookupCode(null, request); fail(); } catch (IllegalArgumentException e) { assertEquals("theCode must be provided", e.getMessage()); @@ -70,11 +70,14 @@ public interface ILookupCodeTest { return "someUnsupportedType"; } }); - getCodeSystemProvider().setLookupCodeResult(result); + getLookupCodeProvider().setLookupCodeResult(result); + + IValidationSupport service = getService(); + LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, null); // test and verify try { - getService().lookupCode(null, new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, null)); + service.lookupCode(null, request); fail(); } catch (InternalErrorException e) { 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.setCodeSystemDisplayName(CODE_SYSTEM_NAME); result.setCodeDisplay(DISPLAY); - getCodeSystemProvider().setLookupCodeResult(result); + getLookupCodeProvider().setLookupCodeResult(result); // test and verify LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, null); @@ -107,7 +110,7 @@ public interface ILookupCodeTest { result.setFound(true); result.getDesignations().add(designation1); result.getDesignations().add(designation2); - getCodeSystemProvider().setLookupCodeResult(result); + getLookupCodeProvider().setLookupCodeResult(result); // test and verify LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, null); @@ -120,7 +123,7 @@ public interface ILookupCodeTest { BaseConceptProperty property = createConceptProperty(propertyName, thePropertyValue); LookupCodeResult result = new LookupCodeResult(); result.getProperties().add(property); - getCodeSystemProvider().setLookupCodeResult(result); + getLookupCodeProvider().setLookupCodeResult(result); // test LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, List.of(propertyName)); @@ -148,7 +151,7 @@ public interface ILookupCodeTest { propertyNamesToFilter.add(currentPropertyName); } } - getCodeSystemProvider().setLookupCodeResult(result); + getLookupCodeProvider().setLookupCodeResult(result); // test LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, propertyNamesToFilter); @@ -172,7 +175,7 @@ public interface ILookupCodeTest { group.addSubProperty(createConceptProperty(subPropertyName + i, thePropertyValues.get(i))); } result.getProperties().add(group); - getCodeSystemProvider().setLookupCodeResult(result); + getLookupCodeProvider().setLookupCodeResult(result); // test and verify LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, List.of(groupName)); @@ -186,8 +189,8 @@ public interface ILookupCodeTest { // verify assertNotNull(outcome); - assertEquals(theRequest.getCode(), getCodeSystemProvider().getCode()); - assertEquals(theRequest.getSystem(), getCodeSystemProvider().getSystem()); + assertEquals(theRequest.getCode(), getLookupCodeProvider().getCode()); + assertEquals(theRequest.getSystem(), getLookupCodeProvider().getSystem()); assertEquals(theExpectedResult.isFound(), outcome.isFound()); assertEquals(theExpectedResult.getErrorMessage(), outcome.getErrorMessage()); assertEquals(theExpectedResult.getCodeSystemDisplayName(), outcome.getCodeSystemDisplayName()); @@ -207,7 +210,7 @@ public interface ILookupCodeTest { LookupCodeResult result = new LookupCodeResult(); result.setFound(true); result.getDesignations().add(theConceptDesignation); - getCodeSystemProvider().setLookupCodeResult(result); + getLookupCodeProvider().setLookupCodeResult(result); // test and verify LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, null); @@ -247,11 +250,4 @@ public interface ILookupCodeTest { assertEquals(theActualDesignation.getUseSystem(), theExpectedDesignation.getUseSystem()); assertEquals(theActualDesignation.getUseDisplay(), theExpectedDesignation.getUseDisplay()); } - - interface IMyCodeSystemProvider extends IResourceProvider { - String getCode(); - String getSystem(); - - void setLookupCodeResult(LookupCodeResult theLookupCodeResult); - } } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IRemoteTerminologyLookupCodeTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IRemoteTerminologyLookupCodeTest.java index 156ecea62fa..5ba79bd3e6f 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IRemoteTerminologyLookupCodeTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IRemoteTerminologyLookupCodeTest.java @@ -23,7 +23,7 @@ public interface IRemoteTerminologyLookupCodeTest extends ILookupCodeTest { default void lookupCode_forCodeSystemWithCodeNotFound_returnsNotFound() { String baseUrl = getService().getBaseUrl(); final String codeNotFound = "a"; - final String system = CODE_SYSTEM; + final String system = IValidationProviders.CODE_SYSTEM; final String codeAndSystem = system + "#" + codeNotFound; final String exceptionMessage = MessageFormat.format(MESSAGE_RESPONSE_NOT_FOUND, codeNotFound); LookupCodeResult result = new LookupCodeResult() @@ -31,7 +31,7 @@ public interface IRemoteTerminologyLookupCodeTest extends ILookupCodeTest { .setSearchedForCode(codeNotFound) .setSearchedForSystem(system) .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); verifyLookupCodeResult(request, result); @@ -49,9 +49,11 @@ public interface IRemoteTerminologyLookupCodeTest extends ILookupCodeTest { .setSearchedForCode(codeNotFound) .setSearchedForSystem(system) .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); verifyLookupCodeResult(request, result); } + + } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IRemoteTerminologyValidateCodeTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IRemoteTerminologyValidateCodeTest.java new file mode 100644 index 00000000000..cb6bb02ac07 --- /dev/null +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IRemoteTerminologyValidateCodeTest.java @@ -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 getCodeValidationIssues(IBaseOperationOutcome theOperationOutcome) { + // this method should be removed once support for issues is fully implemented across all validator types + Optional> issues = RemoteTerminologyServiceValidationSupport.createCodeValidationIssues(theOperationOutcome, getService().getFhirContext().getVersion().getVersion()); + return issues.map(theCodeValidationIssues -> theCodeValidationIssues.stream().toList()).orElseGet(List::of); + } +} diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IValidateCodeTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IValidateCodeTest.java new file mode 100644 index 00000000000..52dbf1177a8 --- /dev/null +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IValidateCodeTest.java @@ -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 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 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 getCodeValidationIssues(IBaseOperationOutcome theOperationOutcome); +} diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IValidationProviders.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IValidationProviders.java new file mode 100644 index 00000000000..1537f8e5c00 --- /dev/null +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IValidationProviders.java @@ -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); + } +} diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java index 6817fd74dbd..d04e90d2d7a 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java @@ -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.ValidationSupportChain; 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.hapi.ctx.HapiWorkerContext; import org.hl7.fhir.dstu3.model.Base; import org.hl7.fhir.dstu3.model.BooleanType; 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.ValueSetExpansionComponent; import org.hl7.fhir.instance.model.api.IBaseResource; - import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher; 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.Test; 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.quality.Strictness; import org.mockito.stubbing.Answer; @@ -110,6 +111,7 @@ public class FhirInstanceValidatorDstu3Test extends BaseValidationTestWithInline private FhirValidator myVal; private ArrayList myValidConcepts; private Set myValidSystems = new HashSet<>(); + private Set myValidSystemsNotReturningIssues = new HashSet<>(); private HashMap myStructureDefinitions; private HashMap myCodeSystems; private HashMap myValueSets; @@ -117,7 +119,15 @@ public class FhirInstanceValidatorDstu3Test extends BaseValidationTestWithInline private CachingValidationSupport myValidationSupport; 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); } @@ -219,9 +229,10 @@ public class FhirInstanceValidatorDstu3Test extends BaseValidationTestWithInline retVal = new IValidationSupport.CodeValidationResult().setCode(code); } else if (myValidSystems.contains(system)) { 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 if (myCodeSystems.containsKey(system)) { CodeSystem cs = myCodeSystems.get(system); Optional found = cs.getConcept().stream().filter(t -> t.getCode().equals(code)).findFirst(); @@ -1161,9 +1172,11 @@ public class FhirInstanceValidatorDstu3Test extends BaseValidationTestWithInline } - @Test - public void testValidateResourceContainingLoincCode() { - addValidConcept("http://loinc.org", "1234567"); + // TODO: uncomment value false when https://github.com/hapifhir/org.hl7.fhir.core/issues/1766 is fixed + @ParameterizedTest + @ValueSource(booleans = {true, /*false*/}) + public void testValidateResourceContainingLoincCode(boolean theShouldSystemReturnIssuesForInvalidCode) { + addValidConcept("http://loinc.org", "1234567", theShouldSystemReturnIssuesForInvalidCode); Observation input = new Observation(); // input.getMeta().addProfile("http://hl7.org/fhir/StructureDefinition/devicemetricobservation"); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/IValidateCodeProvidersDstu3.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/IValidateCodeProvidersDstu3.java new file mode 100644 index 00000000000..0c639e310ee --- /dev/null +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/IValidateCodeProvidersDstu3.java @@ -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 thePropertyNames, + RequestDetails theRequestDetails + ) { + myCode = theCode; + return myReturnParams; + } + + @Override + public Class 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 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; + } + } +} diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeDstu3Test.java index 7bdf0c42518..6e98c4b31a9 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeDstu3Test.java @@ -14,6 +14,7 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; import jakarta.servlet.http.HttpServletRequest; 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.dstu3.model.BooleanType; 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.InstantType; import org.hl7.fhir.r4.model.IntegerType; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; @@ -49,20 +51,25 @@ public class RemoteTerminologyLookupCodeDstu3Test implements IRemoteTerminologyL @RegisterExtension public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx); private final RemoteTerminologyServiceValidationSupport mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx); + private IValidationProviders.IMyLookupCodeProvider myLookupCodeProvider; @BeforeEach public void before() { String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort(); mySvc.setBaseUrl(baseUrl); 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 - public IMyCodeSystemProvider getCodeSystemProvider() { - return myCodeSystemProvider; + public IValidationProviders.IMyLookupCodeProvider getLookupCodeProvider() { + return myLookupCodeProvider; } @Override @@ -83,7 +90,7 @@ public class RemoteTerminologyLookupCodeDstu3Test implements IRemoteTerminologyL @ParameterizedTest @MethodSource(value = "getEmptyPropertyValues") - public void lookupCode_forCodeSystemWithPropertyEmptyValue_returnsCorrectParameters(IBaseDatatype thePropertyValue) { + void lookupCode_forCodeSystemWithPropertyEmptyValue_returnsCorrectParameters(IBaseDatatype thePropertyValue) { verifyLookupWithEmptyPropertyValue(thePropertyValue); } @@ -122,20 +129,20 @@ public class RemoteTerminologyLookupCodeDstu3Test implements IRemoteTerminologyL @ParameterizedTest @MethodSource(value = "getPropertyValueArguments") - public void lookupCode_forCodeSystemWithProperty_returnsCorrectProperty(IBaseDatatype thePropertyValue) { + void lookupCode_forCodeSystemWithProperty_returnsCorrectProperty(IBaseDatatype thePropertyValue) { verifyLookupWithProperty(List.of(thePropertyValue), List.of()); } @ParameterizedTest @MethodSource(value = "getPropertyValueListArguments") - public void lookupCode_forCodeSystemWithPropertyFilter_returnsCorrectProperty(List thePropertyValues) { + void lookupCode_forCodeSystemWithPropertyFilter_returnsCorrectProperty(List thePropertyValues) { verifyLookupWithProperty(thePropertyValues, List.of()); verifyLookupWithProperty(thePropertyValues, List.of(thePropertyValues.size() - 1)); } @ParameterizedTest @MethodSource(value = "getPropertyValueListArguments") - public void lookupCode_forCodeSystemWithPropertyGroup_returnsCorrectProperty(List thePropertyValues) { + void lookupCode_forCodeSystemWithPropertyGroup_returnsCorrectProperty(List thePropertyValues) { verifyLookupWithSubProperties(thePropertyValues); } @@ -155,7 +162,8 @@ public class RemoteTerminologyLookupCodeDstu3Test implements IRemoteTerminologyL verifyLookupWithConceptDesignation(theConceptDesignation); } - static class MyCodeSystemProviderDstu3 implements IMyCodeSystemProvider { + @SuppressWarnings("unused") + static class MyLookupCodeProviderDstu3 implements IValidationProviders.IMyLookupCodeProvider { private UriType mySystemUrl; private CodeType myCode; private LookupCodeResult myLookupCodeResult; diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeWithResponseFileDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeWithResponseFileDstu3Test.java new file mode 100644 index 00000000000..48a99f260d0 --- /dev/null +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeWithResponseFileDstu3Test.java @@ -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); + } +} diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyServiceResourceProviderDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyServiceResourceProviderDstu3Test.java deleted file mode 100644 index f71552dc750..00000000000 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyServiceResourceProviderDstu3Test.java +++ /dev/null @@ -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 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 thePropertyNames, - RequestDetails theRequestDetails - ) { - myLastCode = theCode; - return myNextReturnParams; - } - - @Override - public Class 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 getResourceType() { - return ValueSet.class; - } - - } -} diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyValidateCodeDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyValidateCodeDstu3Test.java new file mode 100644 index 00000000000..af4f39f0926 --- /dev/null +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyValidateCodeDstu3Test.java @@ -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)); + } +} diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java index 5b9d9fc6ad5..d03c3aa974d 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java @@ -33,6 +33,7 @@ import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.conformance.ProfileUtilities; 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.model.AllergyIntolerance; 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.ValueSetExpansionComponent; 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.test.utils.ClassesLoadedFlags; 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.Test; 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.stubbing.Answer; @@ -128,13 +130,22 @@ public class FhirInstanceValidatorR4Test extends BaseValidationTestWithInlineMoc private FhirValidator myFhirValidator; private ArrayList myValidConcepts; private Set myValidSystems = new HashSet<>(); + private Set myValidSystemsNotReturningIssues = new HashSet<>(); private Set myValidValueSets = new HashSet<>(); private Map myStructureDefinitionMap = new HashMap<>(); private CachingValidationSupport myValidationSupport; private IValidationSupport myMockSupport; 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); } @@ -296,7 +307,10 @@ public class FhirInstanceValidatorR4Test extends BaseValidationTestWithInlineMoc retVal = new IValidationSupport.CodeValidationResult().setCode(code); } else if (myValidSystems.contains(system)) { 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 { retVal = myDefaultValidationSupport.validateCode(new ValidationSupportContext(myDefaultValidationSupport), options, system, code, display, valueSetUrl); } @@ -1226,14 +1240,15 @@ public class FhirInstanceValidatorR4Test extends BaseValidationTestWithInlineMoc } - @Test - public void testValidateResourceContainingLoincCode() { - addValidConcept("http://loinc.org", "1234567"); + // TODO: uncomment value false when https://github.com/hapifhir/org.hl7.fhir.core/issues/1766 is fixed + @ParameterizedTest + @ValueSource(booleans = {true, /*false*/}) + public void testValidateResourceContainingLoincCode(boolean theShouldSystemReturnIssuesForInvalidCode) { + addValidConcept("http://loinc.org", "1234567", theShouldSystemReturnIssuesForInvalidCode); Observation input = createObservationWithDefaultSubjectPerfomerEffective(); input.getText().setDiv(new XhtmlNode().setValue("
AA
")).setStatus(Narrative.NarrativeStatus.GENERATED); - input.addIdentifier().setSystem("http://acme").setValue("12345"); input.getEncounter().setReference("http://foo.com/Encounter/9"); input.setStatus(ObservationStatus.FINAL); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/IValidateCodeProvidersR4.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/IValidateCodeProvidersR4.java new file mode 100644 index 00000000000..fd03a8163ff --- /dev/null +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/IValidateCodeProvidersR4.java @@ -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 thePropertyNames, + RequestDetails theRequestDetails + ) throws Exception { + mySystemUrl = theSystem; + myCode = theCode; + if (myException != null) { + throw myException; + } + return myReturnParams; + } + + @Override + public Class 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 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; + } + } +} diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeR4Test.java index 3fc83b2042a..d67966df6d4 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeR4Test.java @@ -2,6 +2,7 @@ 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.IValidationSupport.LookupCodeResult; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.rest.annotation.Operation; 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 jakarta.servlet.http.HttpServletRequest; 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.instance.model.api.IBaseDatatype; 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.Type; import org.hl7.fhir.r4.model.UriType; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; @@ -39,7 +42,6 @@ import java.util.List; import java.util.stream.Stream; 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. @@ -50,19 +52,27 @@ public class RemoteTerminologyLookupCodeR4Test implements IRemoteTerminologyLook @RegisterExtension public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx); private final RemoteTerminologyServiceValidationSupport mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx); - private final MyCodeSystemProviderR4 myCodeSystemProvider = new MyCodeSystemProviderR4(); + private IValidateCodeProvidersR4.MyCodeSystemProviderR4 myCodeSystemProvider; + private MyLookupCodeProviderR4 myLookupCodeProviderR4; @BeforeEach public void before() { String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort(); mySvc.setBaseUrl(baseUrl); 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 - public IMyCodeSystemProvider getCodeSystemProvider() { - return myCodeSystemProvider; + public IValidationProviders.IMyLookupCodeProvider getLookupCodeProvider() { + return myLookupCodeProviderR4; } @Override @@ -87,7 +97,6 @@ public class RemoteTerminologyLookupCodeR4Test implements IRemoteTerminologyLook verifyLookupWithEmptyPropertyValue(thePropertyValue); } - public static Stream getPropertyValueArguments() { return Stream.of( // FHIR R4 spec types @@ -155,7 +164,8 @@ public class RemoteTerminologyLookupCodeR4Test implements IRemoteTerminologyLook verifyLookupWithConceptDesignation(theConceptDesignation); } - static class MyCodeSystemProviderR4 implements IMyCodeSystemProvider { + @SuppressWarnings("unused") + static class MyLookupCodeProviderR4 implements IValidationProviders.IMyLookupCodeProvider { private UriType mySystemUrl; private CodeType myCode; private LookupCodeResult myLookupCodeResult; diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeWithResponseFileR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeWithResponseFileR4Test.java new file mode 100644 index 00000000000..37eba91d0ca --- /dev/null +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeWithResponseFileR4Test.java @@ -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); + } +} diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyServiceResourceProviderR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyServiceResourceProviderR4Test.java deleted file mode 100644 index 6bfdd2a55ad..00000000000 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyServiceResourceProviderR4Test.java +++ /dev/null @@ -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 thePropertyNames, - RequestDetails theRequestDetails - ) { - myLastCode = theCode; - return myNextReturnParams; - } - - @Override - public Class 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 getResourceType() { - return ValueSet.class; - } - - } -} diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyServiceValidationSupportR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyServiceValidationSupportR4Test.java index bfc82a10978..81e34c7ebb7 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyServiceValidationSupportR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyServiceValidationSupportR4Test.java @@ -1,13 +1,11 @@ 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.TranslateConceptResult; import ca.uhn.fhir.context.support.TranslateConceptResults; import ca.uhn.fhir.fhirpath.BaseValidationTestWithInlineMocks; 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.Operation; 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.api.SummaryEnum; 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.param.UriParam; import ca.uhn.fhir.rest.server.IResourceProvider; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; -import ca.uhn.fhir.util.ParametersUtil; -import com.google.common.collect.Lists; import jakarta.servlet.http.HttpServletRequest; import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; 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.r4.model.BooleanType; 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.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.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.stream.Stream; -import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; 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. * @see RemoteTerminologyLookupCodeR4Test - * @see RemoteTerminologyServiceResourceProviderR4Test + * @see RemoteTerminologyValidateCodeR4Test */ 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_NAME = "Code System"; private static final String CODE = "CODE"; private static final String VALUE_SET_URL = "http://value.set/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 boolean REVERSE = true; private static final String EQUIVALENCE_CODE = "equivalent"; - 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(); @@ -114,83 +92,8 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat mySvc.addClientInterceptor(new LoggingInterceptor(true)); } - @AfterEach - public void after() { - assertThat(myValueSetProvider.myInvocationCount).isLessThan(2); - } - @Test - public void testValidateCode_withBlankCode_returnsNull() { - IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(null, null, CODE_SYSTEM, "", DISPLAY, VALUE_SET_URL); - assertNull(outcome); - } - - public static Stream 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() { + void fetchValueSet_forcesSummaryFalse() { // given myValueSetProvider.myNextReturnValueSets = new ArrayList<>(); @@ -202,67 +105,7 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat } @Test - public void testValidateCode_forSystemCodeWithError_returnsCorrectly() { - 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() { + void translateCode_AllInParams_AllOutParams() { myConceptMapProvider.myNextReturnParams = new Parameters(); myConceptMapProvider.myNextReturnParams.addParameter("result", true); myConceptMapProvider.myNextReturnParams.addParameter("message", ERROR_MESSAGE); @@ -303,7 +146,7 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat assertNotNull(results); assertTrue(results.getResult()); - assertEquals(results.getResults().size(), 2); + assertEquals(2, results.getResults().size()); for(TranslateConceptResult result : results.getResults()) { assertEquals(singleResult, result); } @@ -318,7 +161,7 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat } @Test - public void testTranslateCode_NoInParams_NoOutParams() { + void translateCode_NoInParams_NoOutParams() { myConceptMapProvider.myNextReturnParams = new Parameters(); List codings = new ArrayList<>(); @@ -328,7 +171,7 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat TranslateConceptResults results = mySvc.translateConcept(request); assertNotNull(results); assertFalse(results.getResult()); - assertEquals(results.getResults().size(), 0); + assertEquals(0, results.getResults().size()); assertNull(myConceptMapProvider.myLastCodeableConcept); assertNull(myConceptMapProvider.myLastTargetCodeSystem); @@ -340,7 +183,7 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat } @Test - void testFetchCodeSystem_forcesSummaryFalse() { + void fetchCodeSystem_forcesSummaryFalse() { // given myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>(); @@ -360,180 +203,8 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat 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 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 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 - public void testIsValueSetSupported_False() { + void isValueSetSupported_False() { myValueSetProvider.myNextReturnValueSets = new ArrayList<>(); boolean outcome = mySvc.isValueSetSupported(null, "http://loinc.org/VS"); @@ -542,7 +213,7 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat } @Test - public void testIsValueSetSupported_True() { + void isValueSetSupported_True() { myValueSetProvider.myNextReturnValueSets = new ArrayList<>(); myValueSetProvider.myNextReturnValueSets.add((ValueSet) new ValueSet().setId("ValueSet/123")); @@ -552,7 +223,7 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat } @Test - public void testIsCodeSystemSupported_False() { + void isCodeSystemSupported_False() { myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>(); boolean outcome = mySvc.isCodeSystemSupported(null, "http://loinc.org"); @@ -561,7 +232,7 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat } @Test - public void testIsCodeSystemSupported_True() { + void isCodeSystemSupported_True() { myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>(); 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()); } - private void createNextValueSetReturnParameters(boolean theResult, String theDisplay, String theMessage) { - myValueSetProvider.myNextReturnParams = new Parameters() - .addParameter("result", theResult) - .addParameter("display", theDisplay) - .addParameter("message", theMessage); - } - - static private class MyCodeSystemProvider implements IResourceProvider { + @SuppressWarnings("unused") + private static class MyCodeSystemProvider implements IResourceProvider { private SummaryEnum myLastSummaryParam; - private Exception myNextValidateCodeException; private UriParam myLastUrlParam; private List myNextReturnCodeSystems; - private UriType mySystemUrl; - private CodeType myCode; - private IValidationSupport.CodeValidationResult myNextValidationResult; @Override public Class getResourceType() { 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 public List find(@RequiredParam(name = "url") UriParam theUrlParam, SummaryEnum theSummaryParam) { myLastUrlParam = theUrlParam; @@ -620,46 +261,12 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat } } - + @SuppressWarnings("unused") private static class MyValueSetProvider implements IResourceProvider { - private Parameters myNextReturnParams; - private Exception myNextValidateCodeException; private List myNextReturnValueSets; - private UriType myLastUrl; - private CodeType myLastCode; - private int myInvocationCount; - private UriType myLastSystem; - private StringType myLastDisplay; - private ValueSet myLastValueSet; private UriParam myLastUrlParam; 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 public List find(@RequiredParam(name = "url") UriParam theUrlParam, SummaryEnum theSummaryParam) { myLastUrlParam = theUrlParam; @@ -675,6 +282,7 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat } + @SuppressWarnings("unused") private static class MyConceptMapProvider implements IResourceProvider { private UriType myLastConceptMapUrl; private StringType myLastConceptMapVersion; @@ -715,7 +323,5 @@ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidat public Class getResourceType() { return ConceptMap.class; } - } - } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyValidateCodeR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyValidateCodeR4Test.java new file mode 100644 index 00000000000..08f6c251869 --- /dev/null +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyValidateCodeR4Test.java @@ -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 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 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 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; } + } + } +} diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java index f47af042ce1..f0ea48686e1 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java @@ -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.constants.BestPracticeWarningLevel; 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.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; 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.ValueSource; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -97,10 +100,19 @@ public class FhirInstanceValidatorR5Test extends BaseValidationTestWithInlineMoc private Set mySupportedValueSets = new HashSet<>(); private Set myValidSystems = new HashSet<>(); + private Set myValidSystemsNotReturningIssues = new HashSet<>(); private CachingValidationSupport myValidationSupport; 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); } @@ -188,7 +200,10 @@ public class FhirInstanceValidatorR5Test extends BaseValidationTestWithInlineMoc retVal = new IValidationSupport.CodeValidationResult().setCode(code); } else if (myValidSystems.contains(system)) { 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 { retVal = myDefaultValidationSupport.validateCode(new ValidationSupportContext(myDefaultValidationSupport), options, system, code, display, valueSetUrl); } @@ -788,9 +803,11 @@ public class FhirInstanceValidatorR5Test extends BaseValidationTestWithInlineMoc } - @Test - public void testValidateResourceContainingLoincCode() { - addValidConcept("http://loinc.org", "1234567"); + // TODO: uncomment value false when https://github.com/hapifhir/org.hl7.fhir.core/issues/1766 is fixed + @ParameterizedTest + @ValueSource(booleans = {true, /*false*/}) + public void testValidateResourceContainingLoincCode(boolean theShouldSystemReturnIssuesForInvalidCode) { + addValidConcept("http://loinc.org", "1234567", theShouldSystemReturnIssuesForInvalidCode); Observation input = new Observation(); // 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)); - assertEquals(ResultSeverityEnum.ERROR, errors.get(0).getSeverity()); assertEquals("Unknown code (for 'http://loinc.org#12345')", errors.get(0).getMessage()); } diff --git a/hapi-fhir-validation/src/test/resources/r4/CodeSystem-lookup-output-with-subproperties.json b/hapi-fhir-validation/src/test/resources/terminology/CodeSystem-lookup-output-with-subproperties.json similarity index 100% rename from hapi-fhir-validation/src/test/resources/r4/CodeSystem-lookup-output-with-subproperties.json rename to hapi-fhir-validation/src/test/resources/terminology/CodeSystem-lookup-output-with-subproperties.json diff --git a/hapi-fhir-validation/src/test/resources/terminology/OperationOutcome-CodeSystem-invalid-code.json b/hapi-fhir-validation/src/test/resources/terminology/OperationOutcome-CodeSystem-invalid-code.json new file mode 100644 index 00000000000..7b752d65e2a --- /dev/null +++ b/hapi-fhir-validation/src/test/resources/terminology/OperationOutcome-CodeSystem-invalid-code.json @@ -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" + ] + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-validation/src/test/resources/terminology/OperationOutcome-ValueSet-invalid-code.json b/hapi-fhir-validation/src/test/resources/terminology/OperationOutcome-ValueSet-invalid-code.json new file mode 100644 index 00000000000..0d321953895 --- /dev/null +++ b/hapi-fhir-validation/src/test/resources/terminology/OperationOutcome-ValueSet-invalid-code.json @@ -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" + ] + } + ] +} \ No newline at end of file