From bff59b6c50333bbe053c126cb34256b69fa7e840 Mon Sep 17 00:00:00 2001 From: Martha Mitran Date: Thu, 16 May 2024 14:02:53 -0700 Subject: [PATCH] Add support for returning subproperties for RemoteTerminologyService lookup (#5936) * Add support for returning subproperties in the RemoteTerminologyService * Add clarifying comments to code. Simplify logic to filter properties. * Update sub-property iteration logic in the conversion code to make it more readable * Fix spotless errors * Add a few more test cases to test property filtering for the lookup --- .../context/support/IValidationSupport.java | 121 ++++-- .../java/ca/uhn/fhir/util/ParametersUtil.java | 13 +- ...-subproperty-codesystem-lookup-output.yaml | 5 + ...teTerminologyServiceValidationSupport.java | 144 ++++--- .../hapi/validation/ILookupCodeTest.java | 400 ++++++++++-------- .../hapi/validation/LookupCodeDstu3Test.java | 251 ----------- .../RemoteTerminologyLookupCodeDstu3Test.java | 202 +++++++++ ...ologyServiceResourceProviderDstu3Test.java | 294 +++++++++++++ .../fhir/r4/validation/LookupCodeR4Test.java | 242 ----------- .../RemoteTerminologyLookupCodeR4Test.java | 201 +++++++++ ...minologyServiceResourceProviderR4Test.java | 74 +++- ...inologyServiceValidationSupportR4Test.java | 10 + ...stem-lookup-output-with-subproperties.json | 113 +++++ 13 files changed, 1273 insertions(+), 797 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/5935-add-subproperty-codesystem-lookup-output.yaml delete mode 100644 hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/LookupCodeDstu3Test.java create mode 100644 hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeDstu3Test.java create mode 100644 hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyServiceResourceProviderDstu3Test.java delete mode 100644 hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/LookupCodeR4Test.java create mode 100644 hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeR4Test.java rename {hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4 => hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation}/RemoteTerminologyServiceResourceProviderR4Test.java (73%) create mode 100644 hapi-fhir-validation/src/test/resources/r4/CodeSystem-lookup-output-with-subproperties.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 36dba38c0d7..328cd53f445 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 @@ -530,8 +530,13 @@ public interface IValidationSupport { public abstract String getType(); } + // The reason these cannot be declared within an enum is because a Remote Terminology Service + // can support arbitrary types. We do not restrict against the types in the spec. + // Some of the types in the spec are not yet implemented as well. + // @see https://github.com/hapifhir/hapi-fhir/issues/5700 String TYPE_STRING = "string"; String TYPE_CODING = "Coding"; + String TYPE_GROUP = "group"; class StringConceptProperty extends BaseConceptProperty { private final String myValue; @@ -589,6 +594,31 @@ public interface IValidationSupport { } } + class GroupConceptProperty extends BaseConceptProperty { + public GroupConceptProperty(String thePropertyName) { + super(thePropertyName); + } + + private List subProperties; + + public BaseConceptProperty addSubProperty(BaseConceptProperty theProperty) { + if (subProperties == null) { + subProperties = new ArrayList<>(); + } + subProperties.add(theProperty); + return this; + } + + public List getSubProperties() { + return subProperties != null ? subProperties : Collections.emptyList(); + } + + @Override + public String getType() { + return TYPE_GROUP; + } + } + class CodeValidationResult { public static final String SOURCE_DETAILS = "sourceDetails"; public static final String RESULT = "result"; @@ -871,8 +901,15 @@ public interface IValidationSupport { } } + /** + * Converts the current LookupCodeResult instance into a IBaseParameters instance which is returned + * to the client of the $lookup operation. + * @param theContext the FHIR context used for running the operation + * @param thePropertyNamesToFilter the properties which are passed as parameter to filter the result. + * @return the output for the lookup operation. + */ public IBaseParameters toParameters( - FhirContext theContext, List> thePropertyNames) { + FhirContext theContext, List> thePropertyNamesToFilter) { IBaseParameters retVal = ParametersUtil.newInstance(theContext); if (isNotBlank(getCodeSystemDisplayName())) { @@ -886,50 +923,29 @@ public interface IValidationSupport { if (myProperties != null) { - Set properties = Collections.emptySet(); - if (thePropertyNames != null) { - properties = thePropertyNames.stream() + final List propertiesToReturn; + if (thePropertyNamesToFilter != null && !thePropertyNamesToFilter.isEmpty()) { + // TODO MM: The logic to filter of properties could actually be moved to the lookupCode provider. + // That is where the rest of the lookupCode input parameter handling is done. + // This was left as is for now but can be done with next opportunity. + Set propertyNameList = thePropertyNamesToFilter.stream() .map(IPrimitiveType::getValueAsString) .collect(Collectors.toSet()); + propertiesToReturn = myProperties.stream() + .filter(p -> propertyNameList.contains(p.getPropertyName())) + .collect(Collectors.toList()); + } else { + propertiesToReturn = myProperties; } - for (BaseConceptProperty next : myProperties) { - String propertyName = next.getPropertyName(); - - if (!properties.isEmpty() && !properties.contains(propertyName)) { - continue; - } - + for (BaseConceptProperty next : propertiesToReturn) { IBase property = ParametersUtil.addParameterToParameters(theContext, retVal, "property"); - ParametersUtil.addPartCode(theContext, property, "code", propertyName); - - String propertyType = next.getType(); - switch (propertyType) { - case TYPE_STRING: - StringConceptProperty stringConceptProperty = (StringConceptProperty) next; - ParametersUtil.addPartString( - theContext, property, "value", stringConceptProperty.getValue()); - break; - case TYPE_CODING: - CodingConceptProperty codingConceptProperty = (CodingConceptProperty) next; - ParametersUtil.addPartCoding( - theContext, - property, - "value", - codingConceptProperty.getCodeSystem(), - codingConceptProperty.getCode(), - codingConceptProperty.getDisplay()); - break; - default: - throw new IllegalStateException( - Msg.code(1739) + "Don't know how to handle " + next.getClass()); - } + populateProperty(theContext, property, next); } } if (myDesignations != null) { for (ConceptDesignation next : myDesignations) { - IBase property = ParametersUtil.addParameterToParameters(theContext, retVal, "designation"); ParametersUtil.addPartCode(theContext, property, "language", next.getLanguage()); ParametersUtil.addPartCoding( @@ -941,6 +957,41 @@ public interface IValidationSupport { return retVal; } + private void populateProperty( + FhirContext theContext, IBase theProperty, BaseConceptProperty theConceptProperty) { + ParametersUtil.addPartCode(theContext, theProperty, "code", theConceptProperty.getPropertyName()); + String propertyType = theConceptProperty.getType(); + switch (propertyType) { + case TYPE_STRING: + StringConceptProperty stringConceptProperty = (StringConceptProperty) theConceptProperty; + ParametersUtil.addPartString(theContext, theProperty, "value", stringConceptProperty.getValue()); + break; + case TYPE_CODING: + CodingConceptProperty codingConceptProperty = (CodingConceptProperty) theConceptProperty; + ParametersUtil.addPartCoding( + theContext, + theProperty, + "value", + codingConceptProperty.getCodeSystem(), + codingConceptProperty.getCode(), + codingConceptProperty.getDisplay()); + break; + case TYPE_GROUP: + GroupConceptProperty groupConceptProperty = (GroupConceptProperty) theConceptProperty; + if (groupConceptProperty.getSubProperties().isEmpty()) { + break; + } + groupConceptProperty.getSubProperties().forEach(p -> { + IBase subProperty = ParametersUtil.addPart(theContext, theProperty, "subproperty", null); + populateProperty(theContext, subProperty, p); + }); + break; + default: + throw new IllegalStateException( + Msg.code(1739) + "Don't know how to handle " + theConceptProperty.getClass()); + } + } + public void setErrorMessage(String theErrorMessage) { myErrorMessage = theErrorMessage; } 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 63b36e069b1..11fb8d6c012 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 @@ -433,7 +433,7 @@ public class ParametersUtil { addPart(theContext, theParameter, theName, coding); } - public static void addPart(FhirContext theContext, IBase theParameter, String theName, IBase theValue) { + public static IBase addPart(FhirContext theContext, IBase theParameter, String theName, @Nullable IBase theValue) { BaseRuntimeElementCompositeDefinition def = (BaseRuntimeElementCompositeDefinition) theContext.getElementDefinition(theParameter.getClass()); BaseRuntimeChildDefinition partChild = def.getChildByName("part"); @@ -448,11 +448,14 @@ public class ParametersUtil { name.setValue(theName); partChildElem.getChildByName("name").getMutator().addValue(part, name); - if (theValue instanceof IBaseResource) { - partChildElem.getChildByName("resource").getMutator().addValue(part, theValue); - } else { - partChildElem.getChildByName("value[x]").getMutator().addValue(part, theValue); + if (theValue != null) { + if (theValue instanceof IBaseResource) { + partChildElem.getChildByName("resource").getMutator().addValue(part, theValue); + } else { + partChildElem.getChildByName("value[x]").getMutator().addValue(part, theValue); + } } + return part; } public static void addPartResource( diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/5935-add-subproperty-codesystem-lookup-output.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/5935-add-subproperty-codesystem-lookup-output.yaml new file mode 100644 index 00000000000..ea3e05b38c3 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/5935-add-subproperty-codesystem-lookup-output.yaml @@ -0,0 +1,5 @@ +--- +type: add +issue: 5935 +title: "Remote Terminology Service can now return subproperty fields for a CodeSystem lookup operation. +This can be done in DSTU3 and R4. R5 is not yet implemented." 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 2e69b1a46cf..a75e7c73406 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 @@ -216,7 +216,7 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup ParametersUtil.addParameterToParametersString(fhirContext, params, "language", displayLanguage); } for (String propertyName : theLookupCodeRequest.getPropertyNames()) { - ParametersUtil.addParameterToParametersString(fhirContext, params, "property", propertyName); + ParametersUtil.addParameterToParametersCode(fhirContext, params, "property", propertyName); } Class codeSystemClass = myCtx.getResourceDefinition("CodeSystem").getImplementingClass(); @@ -229,7 +229,7 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup if (outcome != null && !outcome.isEmpty()) { switch (fhirVersion) { case DSTU3: - return generateLookupCodeResultDSTU3( + return generateLookupCodeResultDstu3( code, system, (org.hl7.fhir.dstu3.model.Parameters) outcome); case R4: return generateLookupCodeResultR4(code, system, (Parameters) outcome); @@ -243,10 +243,10 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup return null; } - private LookupCodeResult generateLookupCodeResultDSTU3( + private LookupCodeResult generateLookupCodeResultDstu3( String theCode, String theSystem, org.hl7.fhir.dstu3.model.Parameters outcomeDSTU3) { // NOTE: I wanted to put all of this logic into the IValidationSupport Class, but it would've required adding - // several new dependencies on version-specific libraries and that is explicitly forbidden (see comment in + // several new dependencies on version-specific libraries and that is explicitly forbidden (see comment in // POM). LookupCodeResult result = new LookupCodeResult(); result.setSearchedForCode(theCode); @@ -257,13 +257,7 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup String parameterTypeAsString = Objects.toString(parameterComponent.getValue(), null); switch (parameterComponent.getName()) { case "property": - org.hl7.fhir.dstu3.model.Property part = parameterComponent.getChildByName("part"); - // The assumption here is that we may only have 2 elements in this part, and if so, these 2 will be - // saved - if (part == null || part.getValues().size() < 2) { - continue; - } - BaseConceptProperty conceptProperty = createBaseConceptPropertyDstu3(part.getValues()); + BaseConceptProperty conceptProperty = createConceptPropertyDstu3(parameterComponent); if (conceptProperty != null) { result.getProperties().add(conceptProperty); } @@ -289,36 +283,47 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup return result; } - private static BaseConceptProperty createBaseConceptPropertyDstu3(List theValues) { - org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent part1 = - (org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent) theValues.get(0); - String propertyName = ((org.hl7.fhir.dstu3.model.CodeType) part1.getValue()).getValue(); + private static BaseConceptProperty createConceptPropertyDstu3( + org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent theParameterComponent) { + org.hl7.fhir.dstu3.model.Property property = theParameterComponent.getChildByName("part"); - BaseConceptProperty conceptProperty = null; - org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent part2 = - (org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent) theValues.get(1); + // The assumption here is that we may at east 2 elements in this part + if (property == null || property.getValues().size() < 2) { + return null; + } - org.hl7.fhir.dstu3.model.Type value = part2.getValue(); - if (value == null) { - return conceptProperty; + List values = property.getValues(); + org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent firstPart = + (org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent) values.get(0); + String propertyName = ((org.hl7.fhir.dstu3.model.CodeType) firstPart.getValue()).getValue(); + + org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent secondPart = + (org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent) values.get(1); + org.hl7.fhir.dstu3.model.Type value = secondPart.getValue(); + + if (value != null) { + return createConceptPropertyDstu3(propertyName, value); } - String fhirType = value.fhirType(); - switch (fhirType) { - case TYPE_STRING: - org.hl7.fhir.dstu3.model.StringType stringType = (org.hl7.fhir.dstu3.model.StringType) part2.getValue(); - conceptProperty = new StringConceptProperty(propertyName, stringType.getValue()); - break; - case TYPE_CODING: - org.hl7.fhir.dstu3.model.Coding coding = (org.hl7.fhir.dstu3.model.Coding) part2.getValue(); - conceptProperty = new CodingConceptProperty( - propertyName, coding.getSystem(), coding.getCode(), coding.getDisplay()); - break; - // TODO: add other property types as per FHIR spec https://github.com/hapifhir/hapi-fhir/issues/5699 - default: - // other types will not fail for Remote Terminology - conceptProperty = new StringConceptProperty(propertyName, value.toString()); + + String groupName = secondPart.getName(); + if (!"subproperty".equals(groupName)) { + return null; } - return conceptProperty; + + // handle property group (a property containing sub-properties) + GroupConceptProperty groupConceptProperty = new GroupConceptProperty(propertyName); + + // we already retrieved the property name (group name) as first element, next will be the sub-properties. + // there is no dedicated value for a property group as it is an aggregate + for (int i = 1; i < values.size(); i++) { + org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent nextPart = + (org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent) values.get(i); + BaseConceptProperty subProperty = createConceptPropertyDstu3(nextPart); + if (subProperty != null) { + groupConceptProperty.addSubProperty(subProperty); + } + } + return groupConceptProperty; } public static BaseConceptProperty createConceptProperty(final String theName, final IBaseDatatype theValue) { @@ -396,13 +401,7 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup String parameterTypeAsString = Objects.toString(parameterComponent.getValue(), null); switch (parameterComponent.getName()) { case "property": - Property part = parameterComponent.getChildByName("part"); - // The assumption here is that we may only have 2 elements in this part, and if so, these 2 will be - // saved - if (part == null || part.getValues().size() < 2) { - continue; - } - BaseConceptProperty conceptProperty = createBaseConceptPropertyR4(part.getValues()); + BaseConceptProperty conceptProperty = createConceptPropertyR4(parameterComponent); if (conceptProperty != null) { result.getProperties().add(conceptProperty); } @@ -428,34 +427,43 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup return result; } - private static BaseConceptProperty createBaseConceptPropertyR4(List values) { - ParametersParameterComponent part1 = (ParametersParameterComponent) values.get(0); - String propertyName = ((CodeType) part1.getValue()).getValue(); + private static BaseConceptProperty createConceptPropertyR4(ParametersParameterComponent thePropertyComponent) { + Property property = thePropertyComponent.getChildByName("part"); - ParametersParameterComponent part2 = (ParametersParameterComponent) values.get(1); - - Type value = part2.getValue(); - if (value == null) { + // The assumption here is that we may at east 2 elements in this part + if (property == null || property.getValues().size() < 2) { return null; } - BaseConceptProperty conceptProperty; - String fhirType = value.fhirType(); - switch (fhirType) { - case IValidationSupport.TYPE_STRING: - StringType stringType = (StringType) part2.getValue(); - conceptProperty = new StringConceptProperty(propertyName, stringType.getValue()); - break; - case IValidationSupport.TYPE_CODING: - Coding coding = (Coding) part2.getValue(); - conceptProperty = new CodingConceptProperty( - propertyName, coding.getSystem(), coding.getCode(), coding.getDisplay()); - break; - // TODO: add other property types as per FHIR spec https://github.com/hapifhir/hapi-fhir/issues/5699 - default: - // other types will not fail for Remote Terminology - conceptProperty = new StringConceptProperty(propertyName, value.toString()); + + List values = property.getValues(); + ParametersParameterComponent firstPart = (ParametersParameterComponent) values.get(0); + String propertyName = ((CodeType) firstPart.getValue()).getValue(); + + ParametersParameterComponent secondPart = (ParametersParameterComponent) values.get(1); + Type value = secondPart.getValue(); + + if (value != null) { + return createConceptPropertyR4(propertyName, value); } - return conceptProperty; + + String groupName = secondPart.getName(); + if (!"subproperty".equals(groupName)) { + return null; + } + + // handle property group (a property containing sub-properties) + GroupConceptProperty groupConceptProperty = new GroupConceptProperty(propertyName); + + // we already retrieved the property name (group name) as first element, next will be the sub-properties. + // there is no dedicated value for a property group as it is an aggregate + for (int i = 1; i < values.size(); i++) { + ParametersParameterComponent nextPart = (ParametersParameterComponent) values.get(i); + BaseConceptProperty subProperty = createConceptPropertyR4(nextPart); + if (subProperty != null) { + groupConceptProperty.addSubProperty(subProperty); + } + } + return groupConceptProperty; } private static BaseConceptProperty createConceptPropertyR4(final String theName, final Type theValue) { 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 0b4624d733f..a282a611ceb 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 @@ -1,23 +1,26 @@ package org.hl7.fhir.common.hapi.validation; import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport.BaseConceptProperty; +import ca.uhn.fhir.context.support.IValidationSupport.CodingConceptProperty; +import ca.uhn.fhir.context.support.IValidationSupport.ConceptDesignation; +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.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; import org.hl7.fhir.instance.model.api.IBaseDatatype; -import org.junit.jupiter.api.Nested; 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.Collection; +import java.util.ArrayList; import java.util.List; import java.util.Optional; -import java.util.stream.Stream; +import static ca.uhn.fhir.context.support.IValidationSupport.TYPE_CODING; +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.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.createConceptProperty; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -25,6 +28,17 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +/** + * Contains basic test setup for the $lookup operation against a {@link org.hl7.fhir.dstu3.model.CodeSystem}. + * The provider for CodeSystem and the validation service needs to be configured by the implementing class. + * This test interface contains the following: + * (1) basic tests which are version independent, without any parameters, declared as default with @Test annotation + * e.g. lookupCode_forCodeSystemWithBlankCode_throwsException + * (2) test template methods for running version dependent tests with parameters, declared as default, without annotations + * e.g. @see #verifyLookupCodeResult + * (3) methods which help to assert part of the output (designation, property), declared as private + * e.g. assertEqualConceptProperty + */ public interface ILookupCodeTest { String DISPLAY = "DISPLAY"; String LANGUAGE = "en"; @@ -33,186 +47,202 @@ public interface ILookupCodeTest { String CODE_SYSTEM_NAME = "Code System"; String CODE = "CODE"; - interface IValidationTest { + IValidationSupport getService(); + IMyCodeSystemProvider getCodeSystemProvider(); - RemoteTerminologyServiceValidationSupport getService(); - IResourceProvider getCodeSystemProvider(); - } - - @Nested - interface ILookupCodeSupportedPropertyTest extends IValidationTest { - IMyCodeSystemProvider getCodeSystemProvider(); - - Stream getEmptyPropertyValues(); - - Stream getPropertyValues(); - - Stream getDesignations(); - - void verifyProperty(IValidationSupport.BaseConceptProperty theConceptProperty, String theExpectedPropertName, IBaseDatatype theExpectedValue); - - @Test - default void testLookupCode_forCodeSystemWithBlankCode_throwsException() { - try { - getService().lookupCode(null, new LookupCodeRequest(CODE_SYSTEM, "")); - fail(); - } catch (IllegalArgumentException e) { - assertEquals("theCode must be provided", e.getMessage()); - } - } - - @Test - default void testLookupCode_forCodeSystemWithPropertyInvalidType_throwsException() { - LookupCodeResult result = new LookupCodeResult(); - result.getProperties().add(new IValidationSupport.BaseConceptProperty("someProperty") { - public String getType() { - return "someUnsupportedType"; - } - }); - getCodeSystemProvider().setLookupCodeResult(result); - - try { - getService().lookupCode(null, new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, null)); - fail(); - } catch (InternalErrorException e) { - assertTrue(e.getMessage().contains("HAPI-1739: Don't know how to handle ")); - } - } - - @ParameterizedTest - @MethodSource(value = "getEmptyPropertyValues") - default void testLookupCode_forCodeSystemWithPropertyEmptyValue_returnsCorrectParameters(IBaseDatatype thePropertyValue) { - // setup - final String propertyName = "someProperty"; - IValidationSupport.BaseConceptProperty property = createConceptProperty(propertyName, thePropertyValue); - LookupCodeResult result = new LookupCodeResult(); - result.getProperties().add(property); - getCodeSystemProvider().setLookupCodeResult(result); - - // test - LookupCodeResult outcome = getService().lookupCode(null, new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, List.of(propertyName))); - - // verify - assertNotNull(outcome); - Optional propertyOptional = outcome.getProperties().stream().findFirst().filter(a -> propertyName.equals(a.getPropertyName())); - assertFalse(propertyOptional.isPresent()); - } - - @Test - default void testLookupCode_forCodeSystemWithParameters_returnsCorrectParameters() { - // setup - LookupCodeResult result = new LookupCodeResult().setFound(true).setSearchedForCode(CODE).setSearchedForSystem(CODE_SYSTEM); - result.setCodeIsAbstract(false); - result.setCodeSystemVersion(CODE_SYSTEM_VERSION); - result.setCodeSystemDisplayName(CODE_SYSTEM_NAME); - result.setCodeDisplay(DISPLAY); - getCodeSystemProvider().setLookupCodeResult(result); - - // test - LookupCodeResult outcome = getService().lookupCode(null, new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, null)); - - // verify - assertNotNull(outcome); - assertEquals(CODE, getCodeSystemProvider().getCode()); - assertEquals(CODE_SYSTEM, getCodeSystemProvider().getSystem()); - assertEquals(result.getCodeSystemDisplayName(), outcome.getCodeSystemDisplayName()); - assertEquals(result.getCodeDisplay(), outcome.getCodeDisplay()); - assertEquals(result.getCodeSystemVersion(), outcome.getCodeSystemVersion()); - assertEquals(result.isCodeIsAbstract(), outcome.isCodeIsAbstract()); - } - - @ParameterizedTest - @MethodSource(value = "getPropertyValues") - default void testLookupCode_forCodeSystemWithProperty_returnsCorrectProperty(IBaseDatatype thePropertyValue) { - // setup - final String propertyName = "someProperty"; - LookupCodeResult result = new LookupCodeResult() - .setFound(true).setSearchedForCode(CODE).setSearchedForSystem(CODE_SYSTEM); - result.setCodeIsAbstract(false); - result.setCodeSystemVersion(CODE_SYSTEM_VERSION); - result.setCodeSystemDisplayName(CODE_SYSTEM_NAME); - result.setCodeDisplay(DISPLAY); - IValidationSupport.BaseConceptProperty property = createConceptProperty(propertyName, thePropertyValue); - result.getProperties().add(property); - getCodeSystemProvider().setLookupCodeResult(result); - - // test - LookupCodeResult outcome = getService().lookupCode(null, new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, List.of(propertyName))); - - // verify - assertNotNull(outcome); - assertEquals(CODE, getCodeSystemProvider().getCode()); - assertEquals(CODE_SYSTEM, getCodeSystemProvider().getSystem()); - assertEquals(result.getCodeSystemDisplayName(), outcome.getCodeSystemDisplayName()); - assertEquals(result.getCodeDisplay(), outcome.getCodeDisplay()); - assertEquals(result.getCodeSystemVersion(), outcome.getCodeSystemVersion()); - assertEquals(result.isCodeIsAbstract(), outcome.isCodeIsAbstract()); - - Optional propertyOptional = outcome.getProperties().stream().findFirst().filter(a -> propertyName.equals(a.getPropertyName())); - assertTrue(propertyOptional.isPresent()); - IValidationSupport.BaseConceptProperty outputProperty = propertyOptional.get(); - - verifyProperty(outputProperty, propertyName, thePropertyValue); - } - - @ParameterizedTest - @MethodSource(value = "getDesignations") - default void testLookupCode_withCodeSystemWithDesignation_returnsCorrectDesignation(final IValidationSupport.ConceptDesignation theConceptDesignation) { - // setup - LookupCodeResult result = new LookupCodeResult(); - result.getDesignations().add(theConceptDesignation); - getCodeSystemProvider().setLookupCodeResult(result); - - // test - LookupCodeResult outcome = getService().lookupCode(null, new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, null)); - - // verify - assertNotNull(outcome); - - Collection designations = outcome.getDesignations(); - assertEquals(1, designations.size()); - - IValidationSupport.ConceptDesignation designation = designations.iterator().next(); - assertEquals(theConceptDesignation.getValue(), designation.getValue()); - assertEquals(theConceptDesignation.getLanguage(), designation.getLanguage()); - assertEquals(theConceptDesignation.getUseCode(), designation.getUseCode()); - assertEquals(theConceptDesignation.getUseSystem(), designation.getUseSystem()); - assertEquals(theConceptDesignation.getUseDisplay(), designation.getUseDisplay()); - } - - @Test - default void testLookupCode_withCodeSystemWithMultipleDesignations_returnsCorrectDesignations() { - // setup - final String code1 = "code1"; - final String code2 = "code2"; - - IValidationSupport.ConceptDesignation designation1 = new IValidationSupport.ConceptDesignation().setUseCode(code1).setUseSystem("system1").setValue("value1").setLanguage("en"); - IValidationSupport.ConceptDesignation designation2 = new IValidationSupport.ConceptDesignation().setUseCode(code2).setUseSystem("system2").setValue("value2").setLanguage("es"); - LookupCodeResult result = new LookupCodeResult(); - result.getDesignations().add(designation1); - result.getDesignations().add(designation2); - getCodeSystemProvider().setLookupCodeResult(result); - - // test - LookupCodeResult outcome = getService().lookupCode(null, new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, null)); - - // verify - assertNotNull(outcome); - - Collection designations = outcome.getDesignations(); - assertEquals(2, designations.size()); - - for (IValidationSupport.ConceptDesignation designation : designations) { - IValidationSupport.ConceptDesignation expectedDesignation = code1.equals(designation.getUseCode()) ? designation1 : designation2; - assertEquals(expectedDesignation.getValue(), designation.getValue()); - assertEquals(expectedDesignation.getLanguage(), designation.getLanguage()); - assertEquals(expectedDesignation.getUseCode(), designation.getUseCode()); - assertEquals(expectedDesignation.getUseSystem(), designation.getUseSystem()); - assertEquals(expectedDesignation.getUseDisplay(), designation.getUseDisplay()); - } + @Test + default void lookupCode_forCodeSystemWithBlankCode_throwsException() { + try { + getService().lookupCode(null, new LookupCodeRequest(CODE_SYSTEM, "")); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("theCode must be provided", e.getMessage()); } } + @Test + default void lookupCode_forCodeSystemWithPropertyInvalidType_throwsException() { + // test + LookupCodeResult result = new LookupCodeResult(); + result.getProperties().add(new BaseConceptProperty("someProperty") { + public String getType() { + return "someUnsupportedType"; + } + }); + getCodeSystemProvider().setLookupCodeResult(result); + + // test and verify + try { + LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, null); + getService().lookupCode(null, request); + fail(); + } catch (InternalErrorException e) { + assertTrue(e.getMessage().contains("HAPI-1739: Don't know how to handle ")); + } + } + + @Test + default void lookupCode_forCodeSystem_returnsCorrectResult() { + LookupCodeResult result = new LookupCodeResult().setFound(true).setSearchedForCode(CODE).setSearchedForSystem(CODE_SYSTEM); + result.setCodeIsAbstract(false); + result.setCodeSystemVersion(CODE_SYSTEM_VERSION); + result.setCodeSystemDisplayName(CODE_SYSTEM_NAME); + result.setCodeDisplay(DISPLAY); + getCodeSystemProvider().setLookupCodeResult(result); + + // test and verify + LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, null); + verifyLookupCodeResult(request, result); + } + + @Test + default void lookupCode_withCodeSystemWithMultipleDesignations_returnsCorrectDesignations() { + // setup + final String code1 = "code1"; + final String code2 = "code2"; + + ConceptDesignation designation1 = new ConceptDesignation().setUseCode(code1).setUseSystem("system1").setValue("value1").setLanguage("en"); + ConceptDesignation designation2 = new ConceptDesignation().setUseCode(code2).setUseSystem("system2").setValue("value2").setLanguage("es"); + LookupCodeResult result = new LookupCodeResult(); + result.getDesignations().add(designation1); + result.getDesignations().add(designation2); + getCodeSystemProvider().setLookupCodeResult(result); + + // test and verify + LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, null); + verifyLookupCodeResult(request, result); + } + + default void verifyLookupWithEmptyPropertyValue(IBaseDatatype thePropertyValue) { + // setup + final String propertyName = "someProperty"; + BaseConceptProperty property = createConceptProperty(propertyName, thePropertyValue); + LookupCodeResult result = new LookupCodeResult(); + result.getProperties().add(property); + getCodeSystemProvider().setLookupCodeResult(result); + + // test + LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, List.of(propertyName)); + LookupCodeResult outcome = getService().lookupCode(null, request); + + // verify + assertNotNull(outcome); + Optional propertyOptional = outcome.getProperties().stream().findFirst().filter(a -> propertyName.equals(a.getPropertyName())); + assertFalse(propertyOptional.isPresent()); + } + + default void verifyLookupWithProperty(List thePropertyValues, List thePropertyIndexesToFilter) { + // setup + final String propertyName = "someProperty"; + LookupCodeResult result = new LookupCodeResult().setFound(true).setSearchedForCode(CODE).setSearchedForSystem(CODE_SYSTEM); + result.setCodeIsAbstract(false); + result.setCodeSystemVersion(CODE_SYSTEM_VERSION); + result.setCodeSystemDisplayName(CODE_SYSTEM_NAME); + result.setCodeDisplay(DISPLAY); + List propertyNamesToFilter = new ArrayList<>(); + for (int i = 0; i < thePropertyValues.size(); i++) { + String currentPropertyName = propertyName + i; + result.getProperties().add(createConceptProperty(currentPropertyName, thePropertyValues.get(i))); + if (thePropertyIndexesToFilter.contains(i)) { + propertyNamesToFilter.add(currentPropertyName); + } + } + getCodeSystemProvider().setLookupCodeResult(result); + + // test + LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, propertyNamesToFilter); + + // verify + result.getProperties().removeIf(p -> !propertyNamesToFilter.contains(p.getPropertyName())); + verifyLookupCodeResult(request, result); + } + + default void verifyLookupWithSubProperties(List thePropertyValues) { + // setup + final String groupName = "group"; + LookupCodeResult result = new LookupCodeResult().setFound(true).setSearchedForCode(CODE).setSearchedForSystem(CODE_SYSTEM); + result.setCodeIsAbstract(false); + result.setCodeSystemVersion(CODE_SYSTEM_VERSION); + result.setCodeSystemDisplayName(CODE_SYSTEM_NAME); + result.setCodeDisplay(DISPLAY); + final String subPropertyName = "someSubProperty"; + GroupConceptProperty group = new GroupConceptProperty(groupName); + for (int i = 0; i < thePropertyValues.size(); i++) { + group.addSubProperty(createConceptProperty(subPropertyName + i, thePropertyValues.get(i))); + } + result.getProperties().add(group); + getCodeSystemProvider().setLookupCodeResult(result); + + // test and verify + LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, List.of(groupName)); + verifyLookupCodeResult(request, result); + } + + default void verifyLookupCodeResult(LookupCodeRequest theRequest, LookupCodeResult theExpectedResult) { + // test + LookupCodeResult outcome = getService().lookupCode(null, theRequest); + assertNotNull(outcome); + + // verify + assertNotNull(outcome); + assertEquals(theRequest.getCode(), getCodeSystemProvider().getCode()); + assertEquals(theRequest.getSystem(), getCodeSystemProvider().getSystem()); + assertEquals(theExpectedResult.getCodeSystemDisplayName(), outcome.getCodeSystemDisplayName()); + assertEquals(theExpectedResult.getCodeDisplay(), outcome.getCodeDisplay()); + assertEquals(theExpectedResult.getCodeSystemVersion(), outcome.getCodeSystemVersion()); + assertEquals(theExpectedResult.isCodeIsAbstract(), outcome.isCodeIsAbstract()); + + assertEquals(theExpectedResult.getProperties().size(), outcome.getProperties().size()); + range(0, outcome.getProperties().size()).forEach(i -> assertEqualConceptProperty(theExpectedResult.getProperties().get(i), outcome.getProperties().get(i))); + + assertEquals(theExpectedResult.getDesignations().size(), outcome.getDesignations().size()); + range(0, outcome.getDesignations().size()).forEach(i -> assertEqualConceptDesignation(theExpectedResult.getDesignations().get(i), outcome.getDesignations().get(i))); + } + + default void verifyLookupWithConceptDesignation(final ConceptDesignation theConceptDesignation) { + // setup + LookupCodeResult result = new LookupCodeResult(); + result.getDesignations().add(theConceptDesignation); + getCodeSystemProvider().setLookupCodeResult(result); + + // test and verify + LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, null); + verifyLookupCodeResult(request, result); + } + + private void assertEqualConceptProperty(BaseConceptProperty theProperty, BaseConceptProperty theExpectedProperty) { + assertEquals(theExpectedProperty.getPropertyName(), theProperty.getPropertyName()); + assertEquals(theExpectedProperty.getType(), theProperty.getType()); + switch (theProperty.getType()) { + case TYPE_STRING -> { + StringConceptProperty expected = (StringConceptProperty) theExpectedProperty; + StringConceptProperty actual = (StringConceptProperty) theProperty; + assertEquals(expected.getValue(), actual.getValue()); + } + case TYPE_CODING -> { + CodingConceptProperty expected = (CodingConceptProperty) theExpectedProperty; + CodingConceptProperty actual = (CodingConceptProperty) theProperty; + assertEquals(expected.getCode(), actual.getCode()); + assertEquals(expected.getCodeSystem(), actual.getCodeSystem()); + assertEquals(expected.getDisplay(), actual.getDisplay()); + } + case TYPE_GROUP -> { + GroupConceptProperty expected = (GroupConceptProperty) theExpectedProperty; + GroupConceptProperty actual = (GroupConceptProperty) theProperty; + assertEquals(expected.getSubProperties().size(), actual.getSubProperties().size()); + range(0, actual.getSubProperties().size()).forEach(i -> assertEqualConceptProperty(expected.getSubProperties().get(i), actual.getSubProperties().get(i))); + } + default -> fail(); + } + } + + private void assertEqualConceptDesignation(final ConceptDesignation theActualDesignation, final ConceptDesignation theExpectedDesignation) { + assertEquals(theActualDesignation.getValue(), theExpectedDesignation.getValue()); + assertEquals(theActualDesignation.getLanguage(), theExpectedDesignation.getLanguage()); + assertEquals(theActualDesignation.getUseCode(), theExpectedDesignation.getUseCode()); + assertEquals(theActualDesignation.getUseSystem(), theExpectedDesignation.getUseSystem()); + assertEquals(theActualDesignation.getUseDisplay(), theExpectedDesignation.getUseDisplay()); + } interface IMyCodeSystemProvider extends IResourceProvider { String getCode(); @@ -220,8 +250,4 @@ public interface ILookupCodeTest { void setLookupCodeResult(LookupCodeResult theLookupCodeResult); } - - interface IMySimpleCodeSystemProvider extends IResourceProvider { - IBaseDatatype getPropertyValue(); - } } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/LookupCodeDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/LookupCodeDstu3Test.java deleted file mode 100644 index 404d971928e..00000000000 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/LookupCodeDstu3Test.java +++ /dev/null @@ -1,251 +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.IValidationSupport.ConceptDesignation; -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; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; -import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; -import jakarta.servlet.http.HttpServletRequest; -import org.hl7.fhir.common.hapi.validation.ILookupCodeTest.ILookupCodeSupportedPropertyTest; -import org.hl7.fhir.common.hapi.validation.ILookupCodeTest.IMyCodeSystemProvider; -import org.hl7.fhir.common.hapi.validation.ILookupCodeTest.IMySimpleCodeSystemProvider; -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.Parameters; -import org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent; -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.instance.model.api.IBaseDatatype; -import org.hl7.fhir.instance.model.api.IBaseResource; -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.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.params.provider.Arguments; - -import java.util.Calendar; -import java.util.List; -import java.util.stream.Stream; - -import static ca.uhn.fhir.context.support.IValidationSupport.BaseConceptProperty; -import static ca.uhn.fhir.context.support.IValidationSupport.CodingConceptProperty; -import static ca.uhn.fhir.context.support.IValidationSupport.StringConceptProperty; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class LookupCodeDstu3Test { - private static final FhirContext ourCtx = FhirContext.forDstu3Cached(); - @RegisterExtension - public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx); - private final RemoteTerminologyServiceValidationSupport mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx); - - @BeforeEach - public void before() { - String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort(); - mySvc.setBaseUrl(baseUrl); - mySvc.addClientInterceptor(new LoggingInterceptor(true)); - } - - @TestInstance(TestInstance.Lifecycle.PER_CLASS) - @Nested - class ILookupCodeSupportedPropertyDstu3Test implements ILookupCodeSupportedPropertyTest { - private final MyCodeSystemProviderDstu3 myCodeSystemProvider = new MyCodeSystemProviderDstu3(); - - @Override - public IMyCodeSystemProvider getCodeSystemProvider() { - return myCodeSystemProvider; - } - - @Override - public RemoteTerminologyServiceValidationSupport getService() { - return mySvc; - } - - @BeforeEach - public void before() { - ourRestfulServerExtension.getRestfulServer().registerProvider(myCodeSystemProvider); - } - - public void verifyProperty(BaseConceptProperty theConceptProperty, String theExpectedPropertName, IBaseDatatype theExpectedValue) { - assertEquals(theExpectedPropertName, theConceptProperty.getPropertyName()); - String type = theConceptProperty.getType(); - switch (type) { - case IValidationSupport.TYPE_STRING -> { - if (!(theExpectedValue instanceof StringType stringValue)) { - // TODO: remove this branch to test other property types as per FHIR spec https://github.com/hapifhir/hapi-fhir/issues/5699 - IValidationSupport.StringConceptProperty stringConceptProperty = (IValidationSupport.StringConceptProperty) theConceptProperty; - assertEquals(theExpectedValue.toString(), stringConceptProperty.getValue()); - break; - } - // StringType stringValue = (StringType) theExpectedValue; - assertTrue(theConceptProperty instanceof StringConceptProperty); - StringConceptProperty stringConceptProperty = (StringConceptProperty) theConceptProperty; - assertEquals(stringValue.getValue(), stringConceptProperty.getValue()); - } - case IValidationSupport.TYPE_CODING -> { - assertTrue(theExpectedValue instanceof Coding); - Coding coding = (Coding) theExpectedValue; - assertTrue(theConceptProperty instanceof CodingConceptProperty); - CodingConceptProperty codingConceptProperty = (CodingConceptProperty) theConceptProperty; - assertEquals(coding.getCode(), codingConceptProperty.getCode()); - assertEquals(coding.getSystem(), codingConceptProperty.getCodeSystem()); - assertEquals(coding.getDisplay(), codingConceptProperty.getDisplay()); - } - default -> { - IValidationSupport.StringConceptProperty stringConceptProperty = (IValidationSupport.StringConceptProperty) theConceptProperty; - assertEquals(theExpectedValue.toString(), stringConceptProperty.getValue()); - } - } - } - - public Stream getEmptyPropertyValues() { - return Stream.of( - Arguments.arguments(new StringType()), - Arguments.arguments(new StringType("")), - Arguments.arguments(new Coding()), - Arguments.arguments(new Coding("", null, null)), - Arguments.arguments(new Coding("", "", null)), - Arguments.arguments(new Coding(null, "", null)) - ); - } - - public Stream getPropertyValues() { - return Stream.of( - // FHIR DSTU3 spec types - Arguments.arguments(new StringType("value")), - Arguments.arguments(new Coding("code", "system", "display")), - Arguments.arguments(new CodeType("code")), - Arguments.arguments(new BooleanType(true)), - Arguments.arguments(new IntegerType(1)), - Arguments.arguments(new DateTimeType(Calendar.getInstance())), - // other types will also not fail for Remote Terminology - Arguments.arguments(new DecimalType(1.1)), - Arguments.arguments(new InstantType(Calendar.getInstance())), - Arguments.arguments(new Type() { - @Override - protected Type typedCopy() { - return this; - } - - @Override - public String toString() { - return "randomType"; - } - }) - ); - } - - public Stream getDesignations() { - return Stream.of( - Arguments.arguments(new ConceptDesignation().setLanguage("en").setUseCode("code1").setUseSystem("system-1").setUseDisplay("display").setValue("some value")), - Arguments.arguments(new ConceptDesignation().setUseCode("code2").setUseSystem("system1").setUseDisplay("display").setValue("someValue")), - Arguments.arguments(new ConceptDesignation().setUseCode("code2").setUseSystem("system1").setValue("someValue")), - Arguments.arguments(new ConceptDesignation().setUseCode("code2").setUseSystem("system1")), - Arguments.arguments(new ConceptDesignation().setUseCode("code2")) - ); - } - } - - static class MySimplePropertyCodeSystemProviderDstu3 implements IMySimpleCodeSystemProvider { - - String myPropertyName; - Type myPropertyValue; - - @Override - public IBaseDatatype getPropertyValue() { - return myPropertyValue; - } - - @Override - public Class getResourceType() { - return CodeSystem.class; - } - - @Operation(name = JpaConstants.OPERATION_LOOKUP, idempotent = true, returnParameters= { - @OperationParam(name = "name", type = StringType.class, min = 1), - @OperationParam(name = "version", type = StringType.class, min = 0), - @OperationParam(name = "display", type = StringType.class, min = 1), - @OperationParam(name = "abstract", type = BooleanType.class, min = 1), - @OperationParam(name = "property", min = 0, max = OperationParam.MAX_UNLIMITED) - }) - public Parameters 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 - ) { - ParametersParameterComponent component = new ParametersParameterComponent(); - component.setName("property"); - component.addPart(new ParametersParameterComponent().setName("code").setValue(new CodeType(myPropertyName))); - component.addPart(new ParametersParameterComponent().setName("value").setValue(myPropertyValue)); - return new Parameters().addParameter(component); - } - - } - - static class MyCodeSystemProviderDstu3 implements IMyCodeSystemProvider { - private UriType mySystemUrl; - private CodeType myCode; - private LookupCodeResult myLookupCodeResult; - - @Override - public Class getResourceType() { - return CodeSystem.class; - } - - @Override - public String getCode() { - return myCode != null ? myCode.getValueAsString() : null; - } - - @Override - public String getSystem() { - return mySystemUrl != null ? mySystemUrl.getValueAsString() : null; - } - - @Override - public void setLookupCodeResult(LookupCodeResult theLookupCodeResult) { - myLookupCodeResult = theLookupCodeResult; - } - - @Operation(name = JpaConstants.OPERATION_LOOKUP, idempotent = true, returnParameters= { - @OperationParam(name = "name", type = StringType.class, min = 1), - @OperationParam(name = "version", type = StringType.class, min = 0), - @OperationParam(name = "display", type = StringType.class, min = 1), - @OperationParam(name = "abstract", type = BooleanType.class, min = 1), - @OperationParam(name = "property", min = 0, max = OperationParam.MAX_UNLIMITED) - }) - public Parameters 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 - ) { - myCode = theCode; - mySystemUrl = theSystem; - - return (Parameters)myLookupCodeResult.toParameters(theRequestDetails.getFhirContext(), thePropertyNames); - } - } -} 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 new file mode 100644 index 00000000000..6269733654e --- /dev/null +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeDstu3Test.java @@ -0,0 +1,202 @@ +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.IValidationSupport.ConceptDesignation; +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; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; +import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; +import jakarta.servlet.http.HttpServletRequest; +import org.hl7.fhir.common.hapi.validation.ILookupCodeTest; +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.StringType; +import org.hl7.fhir.dstu3.model.Type; +import org.hl7.fhir.dstu3.model.UriType; +import org.hl7.fhir.instance.model.api.IBaseDatatype; +import org.hl7.fhir.instance.model.api.IBaseParameters; +import org.hl7.fhir.instance.model.api.IBaseResource; +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.BeforeEach; +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.util.Calendar; +import java.util.List; +import java.util.stream.Stream; + +/** + * Version specific tests for CodeSystem $lookup against RemoteTerminologyValidationSupport. + * @see RemoteTerminologyServiceValidationSupport + */ +public class RemoteTerminologyLookupCodeDstu3Test implements ILookupCodeTest { + private static final FhirContext ourCtx = FhirContext.forDstu3Cached(); + @RegisterExtension + public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx); + private final RemoteTerminologyServiceValidationSupport mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx); + + @BeforeEach + public void before() { + String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort(); + mySvc.setBaseUrl(baseUrl); + mySvc.addClientInterceptor(new LoggingInterceptor(true)); + ourRestfulServerExtension.getRestfulServer().registerProvider(myCodeSystemProvider); + } + + private final MyCodeSystemProviderDstu3 myCodeSystemProvider = new MyCodeSystemProviderDstu3(); + + @Override + public IMyCodeSystemProvider getCodeSystemProvider() { + return myCodeSystemProvider; + } + + @Override + public RemoteTerminologyServiceValidationSupport getService() { + return mySvc; + } + + public static Stream getEmptyPropertyValues() { + return Stream.of( + Arguments.of(new StringType()), + Arguments.of(new StringType("")), + Arguments.of(new Coding()), + Arguments.of(new Coding("", null, null)), + Arguments.of(new Coding("", "", null)), + Arguments.of(new Coding(null, "", null)) + ); + } + + @ParameterizedTest + @MethodSource(value = "getEmptyPropertyValues") + public void lookupCode_forCodeSystemWithPropertyEmptyValue_returnsCorrectParameters(IBaseDatatype thePropertyValue) { + verifyLookupWithEmptyPropertyValue(thePropertyValue); + } + + public static Stream getPropertyValueArguments() { + return Stream.of( + // FHIR DSTU3 spec types + Arguments.of(new StringType("value")), + Arguments.of(new Coding("code", "system", "display")), + Arguments.of(new CodeType("code")), + Arguments.of(new BooleanType(true)), + Arguments.of(new IntegerType(1)), + Arguments.of(new DateTimeType(Calendar.getInstance())), + // other types will also not fail for Remote Terminology + Arguments.of(new DecimalType(1.1)), + Arguments.of(new InstantType(Calendar.getInstance())), + Arguments.of(new Type() { + @Override + protected Type typedCopy() { + return this; + } + + @Override + public String toString() { + return "randomType"; + } + }) + ); + } + + public static Stream getPropertyValueListArguments() { + return Stream.of( + Arguments.of(List.of(new StringType("value1")), new StringType("value2")), + Arguments.of(List.of(new StringType("value1")), new Coding("code", "system", "display")) + ); + } + + @ParameterizedTest + @MethodSource(value = "getPropertyValueArguments") + public void lookupCode_forCodeSystemWithProperty_returnsCorrectProperty(IBaseDatatype thePropertyValue) { + verifyLookupWithProperty(List.of(thePropertyValue), List.of()); + } + + @ParameterizedTest + @MethodSource(value = "getPropertyValueListArguments") + public 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) { + verifyLookupWithSubProperties(thePropertyValues); + } + + public static Stream getDesignations() { + return Stream.of( + Arguments.of(new ConceptDesignation().setLanguage("en").setUseCode("code1").setUseSystem("system-1").setUseDisplay("display").setValue("some value")), + Arguments.of(new ConceptDesignation().setUseCode("code2").setUseSystem("system1").setUseDisplay("display").setValue("someValue")), + Arguments.of(new ConceptDesignation().setUseCode("code2").setUseSystem("system1").setValue("someValue")), + Arguments.of(new ConceptDesignation().setUseCode("code2").setUseSystem("system1")), + Arguments.of(new ConceptDesignation().setUseCode("code2")) + ); + } + + @ParameterizedTest + @MethodSource(value = "getDesignations") + void lookupCode_withCodeSystemWithDesignation_returnsCorrectDesignation(final IValidationSupport.ConceptDesignation theConceptDesignation) { + verifyLookupWithConceptDesignation(theConceptDesignation); + } + + static class MyCodeSystemProviderDstu3 implements IMyCodeSystemProvider { + private UriType mySystemUrl; + private CodeType myCode; + private LookupCodeResult myLookupCodeResult; + + @Override + public void setLookupCodeResult(LookupCodeResult theLookupCodeResult) { + myLookupCodeResult = theLookupCodeResult; + } + + @Operation(name = JpaConstants.OPERATION_LOOKUP, idempotent = true, returnParameters= { + @OperationParam(name = "name", type = StringType.class, min = 1), + @OperationParam(name = "version", type = StringType.class, min = 0), + @OperationParam(name = "display", type = StringType.class, min = 1), + @OperationParam(name = "abstract", type = BooleanType.class, min = 1), + @OperationParam(name = "property", 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 + ) { + myCode = theCode; + mySystemUrl = theSystem; + return myLookupCodeResult.toParameters(theRequestDetails.getFhirContext(), thePropertyNames); + } + + @Override + public Class getResourceType() { + return CodeSystem.class; + } + + @Override + public String getCode() { + return myCode != null ? myCode.getValueAsString() : null; + } + + @Override + public String getSystem() { + return mySystemUrl != null ? mySystemUrl.getValueAsString() : null; + } + } +} 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 new file mode 100644 index 00000000000..f71552dc750 --- /dev/null +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyServiceResourceProviderDstu3Test.java @@ -0,0 +1,294 @@ +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/r4/validation/LookupCodeR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/LookupCodeR4Test.java deleted file mode 100644 index f01e66c16c2..00000000000 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/LookupCodeR4Test.java +++ /dev/null @@ -1,242 +0,0 @@ -package org.hl7.fhir.r4.validation; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.support.IValidationSupport; -import ca.uhn.fhir.jpa.model.util.JpaConstants; -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.interceptor.LoggingInterceptor; -import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; -import jakarta.servlet.http.HttpServletRequest; -import org.hl7.fhir.common.hapi.validation.ILookupCodeTest.ILookupCodeSupportedPropertyTest; -import org.hl7.fhir.common.hapi.validation.ILookupCodeTest.IMyCodeSystemProvider; -import org.hl7.fhir.common.hapi.validation.ILookupCodeTest.IMySimpleCodeSystemProvider; -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; -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.DateTimeType; -import org.hl7.fhir.r4.model.DecimalType; -import org.hl7.fhir.r4.model.InstantType; -import org.hl7.fhir.r4.model.IntegerType; -import org.hl7.fhir.r4.model.Parameters; -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.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.params.provider.Arguments; - -import java.util.Calendar; -import java.util.List; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class LookupCodeR4Test { - private static final FhirContext ourCtx = FhirContext.forR4Cached(); - @RegisterExtension - public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx); - private final RemoteTerminologyServiceValidationSupport mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx); - - @BeforeEach - public void before() { - String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort(); - mySvc.setBaseUrl(baseUrl); - mySvc.addClientInterceptor(new LoggingInterceptor(true)); - } - - @TestInstance(TestInstance.Lifecycle.PER_CLASS) - @Nested - class ILookupCodeSupportedPropertyR4Test implements ILookupCodeSupportedPropertyTest { - private final MyCodeSystemProviderR4 myCodeSystemProvider = new MyCodeSystemProviderR4(); - - @Override - public IMyCodeSystemProvider getCodeSystemProvider() { - return myCodeSystemProvider; - } - - @Override - public RemoteTerminologyServiceValidationSupport getService() { - return mySvc; - } - - @BeforeEach - public void before() { - ourRestfulServerExtension.getRestfulServer().registerProvider(myCodeSystemProvider); - } - - @Override - public void verifyProperty(IValidationSupport.BaseConceptProperty theConceptProperty, String theExpectedPropertName, IBaseDatatype theExpectedValue) { - assertEquals(theExpectedPropertName, theConceptProperty.getPropertyName()); - String type = theConceptProperty.getType(); - switch (type) { - case IValidationSupport.TYPE_STRING -> { - if (!(theExpectedValue instanceof StringType stringValue)) { - // TODO: remove this branch to test other property types as per FHIR spec https://github.com/hapifhir/hapi-fhir/issues/5699 - IValidationSupport.StringConceptProperty stringConceptProperty = (IValidationSupport.StringConceptProperty) theConceptProperty; - assertEquals(theExpectedValue.toString(), stringConceptProperty.getValue()); - break; - } - // StringType stringValue = (StringType) theExpectedValue; - assertTrue(theConceptProperty instanceof IValidationSupport.StringConceptProperty); - IValidationSupport.StringConceptProperty stringConceptProperty = (IValidationSupport.StringConceptProperty) theConceptProperty; - assertEquals(stringValue.getValue(), stringConceptProperty.getValue()); - } - case IValidationSupport.TYPE_CODING -> { - assertTrue(theExpectedValue instanceof Coding); - Coding coding = (Coding) theExpectedValue; - assertTrue(theConceptProperty instanceof IValidationSupport.CodingConceptProperty); - IValidationSupport.CodingConceptProperty codingConceptProperty = (IValidationSupport.CodingConceptProperty) theConceptProperty; - assertEquals(coding.getCode(), codingConceptProperty.getCode()); - assertEquals(coding.getSystem(), codingConceptProperty.getCodeSystem()); - assertEquals(coding.getDisplay(), codingConceptProperty.getDisplay()); - } - default -> { - IValidationSupport.StringConceptProperty stringConceptProperty = (IValidationSupport.StringConceptProperty) theConceptProperty; - assertEquals(theExpectedValue.toString(), stringConceptProperty.getValue()); - } - } - } - - public Stream getEmptyPropertyValues() { - return Stream.of( - Arguments.arguments(new StringType()), - Arguments.arguments(new StringType("")), - Arguments.arguments(new Coding()), - Arguments.arguments(new Coding("", null, null)), - Arguments.arguments(new Coding("", "", null)), - Arguments.arguments(new Coding(null, "", null)) - ); - } - - public Stream getPropertyValues() { - return Stream.of( - // FHIR R4 spec types - Arguments.arguments(new StringType("value")), - Arguments.arguments(new Coding("code", "system", "display")), - Arguments.arguments(new CodeType("code")), - Arguments.arguments(new BooleanType(true)), - Arguments.arguments(new IntegerType(1)), - Arguments.arguments(new DateTimeType(Calendar.getInstance())), - Arguments.arguments(new DecimalType(1.1)), - // other types will also not fail for Remote Terminology - Arguments.arguments(new InstantType(Calendar.getInstance())), - Arguments.arguments(new Type() { - @Override - protected Type typedCopy() { - return this; - } - @Override - public String toString() { - return "randomType"; - } - }) - ); - } - - public Stream getDesignations() { - return Stream.of( - Arguments.arguments(new IValidationSupport.ConceptDesignation().setLanguage("en").setUseCode("code1").setUseSystem("system-1").setUseDisplay("display").setValue("some value")), - Arguments.arguments(new IValidationSupport.ConceptDesignation().setUseCode("code2").setUseSystem("system1").setUseDisplay("display").setValue("someValue")), - Arguments.arguments(new IValidationSupport.ConceptDesignation().setUseCode("code2").setUseSystem("system1").setValue("someValue")), - Arguments.arguments(new IValidationSupport.ConceptDesignation().setUseCode("code2").setUseSystem("system1")), - Arguments.arguments(new IValidationSupport.ConceptDesignation().setUseCode("code2")) - ); - } - } - - static class MySimplePropertyCodeSystemProviderR4 implements IMySimpleCodeSystemProvider { - String myPropertyName; - Type myPropertyValue; - - @Override - public IBaseDatatype getPropertyValue() { - return myPropertyValue; - } - - @Override - public Class getResourceType() { - return CodeSystem.class; - } - - @Operation(name = JpaConstants.OPERATION_LOOKUP, idempotent = true, returnParameters= { - @OperationParam(name = "name", type = StringType.class, min = 1), - @OperationParam(name = "version", type = StringType.class, min = 0), - @OperationParam(name = "display", type = StringType.class, min = 1), - @OperationParam(name = "abstract", type = BooleanType.class, min = 1), - @OperationParam(name = "property", min = 0, max = OperationParam.MAX_UNLIMITED) - }) - public Parameters 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 - ) { - Parameters.ParametersParameterComponent component = new Parameters.ParametersParameterComponent(); - component.setName("property"); - component.addPart(new Parameters.ParametersParameterComponent().setName("code").setValue(new CodeType(myPropertyName))); - component.addPart(new Parameters.ParametersParameterComponent().setName("value").setValue(myPropertyValue)); - return new Parameters().addParameter(component); - } - } - - static class MyCodeSystemProviderR4 implements IMyCodeSystemProvider { - private UriType mySystemUrl; - private CodeType myCode; - private IValidationSupport.LookupCodeResult myLookupCodeResult; - - @Override - public void setLookupCodeResult(IValidationSupport.LookupCodeResult theLookupCodeResult) { - myLookupCodeResult = theLookupCodeResult; - } - - @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 - ) { - myCode = theCode; - mySystemUrl = theSystem; - return myLookupCodeResult.toParameters(theRequestDetails.getFhirContext(), thePropertyNames); - } - @Override - public Class getResourceType() { - return CodeSystem.class; - } - - @Override - public String getCode() { - return myCode != null ? myCode.getValueAsString() : null; - } - - @Override - public String getSystem() { - return mySystemUrl != null ? mySystemUrl.getValueAsString() : 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 new file mode 100644 index 00000000000..ec6c018bb5f --- /dev/null +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeR4Test.java @@ -0,0 +1,201 @@ +package org.hl7.fhir.r4.validation; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.model.util.JpaConstants; +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.interceptor.LoggingInterceptor; +import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; +import jakarta.servlet.http.HttpServletRequest; +import org.hl7.fhir.common.hapi.validation.ILookupCodeTest; +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; +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.DateTimeType; +import org.hl7.fhir.r4.model.DecimalType; +import org.hl7.fhir.r4.model.InstantType; +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.BeforeEach; +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.util.Calendar; +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. + * @see RemoteTerminologyServiceValidationSupport + */ +public class RemoteTerminologyLookupCodeR4Test implements ILookupCodeTest { + private static final FhirContext ourCtx = FhirContext.forR4Cached(); + @RegisterExtension + public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx); + private final RemoteTerminologyServiceValidationSupport mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx); + private final MyCodeSystemProviderR4 myCodeSystemProvider = new MyCodeSystemProviderR4(); + + @BeforeEach + public void before() { + String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort(); + mySvc.setBaseUrl(baseUrl); + mySvc.addClientInterceptor(new LoggingInterceptor(true)); + ourRestfulServerExtension.getRestfulServer().registerProvider(myCodeSystemProvider); + } + + @Override + public IMyCodeSystemProvider getCodeSystemProvider() { + return myCodeSystemProvider; + } + + @Override + public RemoteTerminologyServiceValidationSupport getService() { + return mySvc; + } + + public static Stream getEmptyPropertyValues() { + return Stream.of( + Arguments.of(new StringType()), + Arguments.of(new StringType("")), + Arguments.of(new Coding()), + Arguments.of(new Coding("", null, null)), + Arguments.of(new Coding("", "", null)), + Arguments.of(new Coding(null, "", null)) + ); + } + + @ParameterizedTest + @MethodSource(value = "getEmptyPropertyValues") + public void lookupCode_forCodeSystemWithPropertyEmptyValue_returnsCorrectParameters(IBaseDatatype thePropertyValue) { + verifyLookupWithEmptyPropertyValue(thePropertyValue); + } + + + public static Stream getPropertyValueArguments() { + return Stream.of( + // FHIR R4 spec types + Arguments.of(new StringType("test-value")), + Arguments.of(new Coding("test-system", "test-code", "test-display")), + Arguments.of(new CodeType("test-code")), + Arguments.of(new BooleanType(true)), + Arguments.of(new IntegerType(1)), + Arguments.of(new DateTimeType(Calendar.getInstance())), + Arguments.of(new DecimalType(1.1)), + // other types will also not fail for Remote Terminology + Arguments.of(new InstantType(Calendar.getInstance())), + Arguments.of(new Type() { + @Override + protected Type typedCopy() { + return this; + } + @Override + public String toString() { + return "randomType"; + } + }) + ); + } + + public static Stream getPropertyValueListArguments() { + return Stream.of( + Arguments.of(List.of(new StringType("value1")), new StringType("value2")), + Arguments.of(List.of(new StringType("value1")), new Coding("code", "system", "display")) + ); + } + + @ParameterizedTest + @MethodSource(value = "getPropertyValueArguments") + public void lookupCode_forCodeSystemWithProperty_returnsCorrectProperty(IBaseDatatype thePropertyValue) { + verifyLookupWithProperty(List.of(thePropertyValue), List.of()); + } + + @ParameterizedTest + @MethodSource(value = "getPropertyValueListArguments") + public 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) { + verifyLookupWithSubProperties(thePropertyValues); + } + + public static Stream getDesignations() { + return Stream.of( + Arguments.of(new ConceptDesignation().setLanguage("en").setUseCode("code1").setUseSystem("system-1").setUseDisplay("display").setValue("some value")), + Arguments.of(new ConceptDesignation().setUseCode("code2").setUseSystem("system1").setUseDisplay("display").setValue("someValue")), + Arguments.of(new ConceptDesignation().setUseCode("code2").setUseSystem("system1").setValue("someValue")), + Arguments.of(new ConceptDesignation().setUseCode("code2").setUseSystem("system1")), + Arguments.of(new ConceptDesignation().setUseCode("code2")) + ); + } + + @ParameterizedTest + @MethodSource(value = "getDesignations") + void lookupCode_withCodeSystemWithDesignation_returnsCorrectDesignation(final IValidationSupport.ConceptDesignation theConceptDesignation) { + verifyLookupWithConceptDesignation(theConceptDesignation); + } + + static class MyCodeSystemProviderR4 implements IMyCodeSystemProvider { + private UriType mySystemUrl; + private CodeType myCode; + private LookupCodeResult myLookupCodeResult; + + @Override + public void setLookupCodeResult(LookupCodeResult theLookupCodeResult) { + myLookupCodeResult = theLookupCodeResult; + } + + @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 + ) { + myCode = theCode; + mySystemUrl = theSystem; + return myLookupCodeResult.toParameters(theRequestDetails.getFhirContext(), thePropertyNames); + } + @Override + public Class getResourceType() { + return CodeSystem.class; + } + + @Override + public String getCode() { + return myCode != null ? myCode.getValueAsString() : null; + } + + @Override + public String getSystem() { + return mySystemUrl != null ? mySystemUrl.getValueAsString() : null; + } + } +} diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/RemoteTerminologyServiceResourceProviderR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyServiceResourceProviderR4Test.java similarity index 73% rename from hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/RemoteTerminologyServiceResourceProviderR4Test.java rename to hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyServiceResourceProviderR4Test.java index 823438e450e..488a404850e 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/RemoteTerminologyServiceResourceProviderR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyServiceResourceProviderR4Test.java @@ -1,20 +1,26 @@ -package ca.uhn.fhir.jpa.provider.r4; +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.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; @@ -25,15 +31,23 @@ 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.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; -/* - * This set of Unit Tests simulates the call to a remote server and therefore, only tests the code in the - * {@link org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport#invokeRemoteValidateCode} - * method, before and after it makes that remote client call. +/** + * 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"; @@ -79,8 +93,8 @@ public class RemoteTerminologyServiceResourceProviderR4Test { .validateCode(null, null, CODE_SYSTEM, CODE, null, null); assertNotNull(outcome); assertEquals(CODE, outcome.getCode()); - assertNull(outcome.getSeverity()); - assertNull(outcome.getMessage()); + assertNull(outcome.getSeverity()); + assertNull(outcome.getMessage()); assertEquals(CODE, ourCodeSystemProvider.myLastCode.getCode()); assertEquals(CODE_SYSTEM, ourCodeSystemProvider.myLastUrl.getValueAsString()); @@ -114,7 +128,7 @@ public class RemoteTerminologyServiceResourceProviderR4Test { assertEquals(IValidationSupport.IssueSeverity.ERROR, outcome.getSeverity()); assertEquals(SAMPLE_MESSAGE, outcome.getMessage()); - assertFalse(((BooleanType) ourCodeSystemProvider.myNextReturnParams.getParameterValue("result")).booleanValue()); + assertFalse(((BooleanType) ourCodeSystemProvider.myNextReturnParams.getParameterValue("result")).booleanValue()); } @Test @@ -143,7 +157,7 @@ public class RemoteTerminologyServiceResourceProviderR4Test { assertNotNull(outcome); assertEquals(CODE, outcome.getCode()); assertEquals(DISPLAY, outcome.getDisplay()); - assertNull(outcome.getSeverity()); + assertNull(outcome.getSeverity()); assertNull(outcome.getMessage()); assertEquals(CODE, ourValueSetProvider.myLastCode.getCode()); @@ -152,6 +166,27 @@ public class RemoteTerminologyServiceResourceProviderR4Test { 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); @@ -186,6 +221,27 @@ public class RemoteTerminologyServiceResourceProviderR4Test { } + @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; 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 eaa13bd30ac..708773d61bb 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 @@ -59,6 +59,16 @@ 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 RemoteTerminologyServiceResourceProviderR4Test + */ public class RemoteTerminologyServiceValidationSupportR4Test extends BaseValidationTestWithInlineMocks { private static final String DISPLAY = "DISPLAY"; private static final String CODE_SYSTEM = "CODE_SYS"; diff --git a/hapi-fhir-validation/src/test/resources/r4/CodeSystem-lookup-output-with-subproperties.json b/hapi-fhir-validation/src/test/resources/r4/CodeSystem-lookup-output-with-subproperties.json new file mode 100644 index 00000000000..af666e9103f --- /dev/null +++ b/hapi-fhir-validation/src/test/resources/r4/CodeSystem-lookup-output-with-subproperties.json @@ -0,0 +1,113 @@ +{ + "resourceType": "Parameters", + "parameter": [ + { + "name": "name", + "valueString": "Client_Config_CodeSystem" + }, + { + "name": "display", + "valueString": "Value to display" + }, + { + "name": "abstract", + "valueBoolean": false + }, + { + "name": "property", + "part": [ + { + "name": "code", + "valueCode": "interfaces" + }, + { + "name": "subproperty", + "part": [ + { + "name": "code", + "valueCode": "dataType" + }, + { + "name": "value", + "valueString": "drugs" + } + ] + }, + { + "name": "subproperty", + "part": [ + { + "name": "code", + "valueCode": "apiVersion" + }, + { + "name": "value", + "valueString": "4.5.0" + } + ] + }, + { + "name": "subproperty", + "part": [ + { + "name": "code", + "valueCode": "onboardStatus" + }, + { + "name": "value", + "valueString": "unauthorized" + } + ] + } + ] + }, + { + "name": "property", + "part": [ + { + "name": "code", + "valueCode": "interfaces" + }, + { + "name": "subproperty", + "part": [ + { + "name": "code", + "valueCode": "dataType" + }, + { + "name": "value", + "valueString": "ps" + } + ] + }, + { + "name": "subproperty", + "part": [ + { + "name": "code", + "valueCode": "apiVersion" + }, + { + "name": "value", + "valueString": "4.0.0" + } + ] + }, + { + "name": "subproperty", + "part": [ + { + "name": "code", + "valueCode": "onboardStatus" + }, + { + "name": "value", + "valueString": "contributor-testing" + } + ] + } + ] + } + ] +} \ No newline at end of file