Update the implementation of the `toString` fhirpath function to: (pending FHIR-48737) * not return the text 'null' when no primitive value exists (when extension only) * throw an error if more than 1 item is to be returned (doesn't support collections) * return an empty set when no primitive values exist (when extension only)
This commit is contained in:
parent
bcf2ee886a
commit
01af08030f
|
@ -4817,7 +4817,15 @@ public class FHIRPathEngine {
|
|||
|
||||
private List<Base> funcToString(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
|
||||
List<Base> result = new ArrayList<Base>();
|
||||
result.add(new StringType(convertToString(focus)).noExtensions());
|
||||
for (Base item : focus) {
|
||||
String value = convertToString(item);
|
||||
if (value != null)
|
||||
result.add(new StringType(value).noExtensions());
|
||||
}
|
||||
|
||||
if (result.size() > 1) {
|
||||
throw makeException(exp, I18nConstants.FHIRPATH_NO_COLLECTION, "toString", result.size());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -4820,7 +4820,15 @@ public class FHIRPathEngine {
|
|||
|
||||
private List<Base> funcToString(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
|
||||
List<Base> result = new ArrayList<Base>();
|
||||
result.add(new StringType(convertToString(focus)).noExtensions());
|
||||
for (Base item : focus) {
|
||||
String value = convertToString(item);
|
||||
if (value != null)
|
||||
result.add(new StringType(value).noExtensions());
|
||||
}
|
||||
|
||||
if (result.size() > 1) {
|
||||
throw makeException(exp, I18nConstants.FHIRPATH_NO_COLLECTION, "toString", result.size());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -336,4 +336,23 @@ public class FHIRPathTests {
|
|||
assertEquals(1, results.size());
|
||||
assertEquals("123", results.get(0).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEvaluate_ToStringOnDateValue() {
|
||||
Patient input = new Patient();
|
||||
var dtv = new DateType("2024");
|
||||
input.setBirthDateElement(dtv);
|
||||
List<Base> results = fp.evaluate(input, "Patient.birthDate.toString()");
|
||||
assertEquals(1, results.size());
|
||||
assertEquals("2024", results.get(0).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEvaluate_ToStringOnExtensionOnlyValue() {
|
||||
Patient input = new Patient();
|
||||
var dtv = new DateType();
|
||||
input.setBirthDateElement(dtv);
|
||||
List<Base> results = fp.evaluate(input, "Patient.birthDate.toString()");
|
||||
assertEquals(0, results.size());
|
||||
}
|
||||
}
|
|
@ -4929,7 +4929,15 @@ public class FHIRPathEngine {
|
|||
|
||||
private List<Base> funcToString(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
|
||||
List<Base> result = new ArrayList<Base>();
|
||||
result.add(new StringType(convertToString(focus)).noExtensions());
|
||||
for (Base item : focus) {
|
||||
String value = convertToString(item);
|
||||
if (value != null)
|
||||
result.add(new StringType(value).noExtensions());
|
||||
}
|
||||
|
||||
if (result.size() > 1) {
|
||||
throw makeException(exp, I18nConstants.FHIRPATH_NO_COLLECTION, "toString", result.size());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,340 +1,359 @@
|
|||
package org.hl7.fhir.r5.test;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
import org.fhir.ucum.UcumException;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.exceptions.PathEngineException;
|
||||
import org.hl7.fhir.r5.context.SimpleWorkerContext;
|
||||
import org.hl7.fhir.r5.elementmodel.Manager;
|
||||
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
|
||||
import org.hl7.fhir.r5.fhirpath.ExpressionNode;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
|
||||
import org.hl7.fhir.r5.fhirpath.TypeDetails;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine.IEvaluationContext;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.FunctionDetails;
|
||||
import org.hl7.fhir.r5.elementmodel.ValidatedFragment;
|
||||
import org.hl7.fhir.r5.formats.JsonParser;
|
||||
import org.hl7.fhir.r5.formats.XmlParser;
|
||||
import org.hl7.fhir.r5.model.*;
|
||||
import org.hl7.fhir.r5.test.utils.TestingUtilities;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
|
||||
import org.hl7.fhir.utilities.npm.NpmPackage;
|
||||
import org.hl7.fhir.utilities.xml.XMLUtil;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
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 org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class FHIRPathTests {
|
||||
|
||||
public enum TestResultType {OK, SYNTAX, SEMANTICS, EXECUTION}
|
||||
|
||||
public class FHIRPathTestEvaluationServices implements IEvaluationContext {
|
||||
|
||||
@Override
|
||||
public List<Base> resolveConstant(FHIRPathEngine engine, Object appContext, String name, boolean beforeContext, boolean explicitConstant) throws PathEngineException {
|
||||
throw new NotImplementedException("Not done yet (FHIRPathTestEvaluationServices.resolveConstant), when item is element");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeDetails resolveConstantType(FHIRPathEngine engine, Object appContext, String name, boolean explicitConstant) throws PathEngineException {
|
||||
throw new NotImplementedException("Not done yet (FHIRPathTestEvaluationServices.resolveConstantType), when item is element");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean log(String argument, List<Base> focus) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionDetails resolveFunction(FHIRPathEngine engine, String functionName) {
|
||||
throw new NotImplementedException("Not done yet (FHIRPathTestEvaluationServices.resolveFunction), when item is element (for " + functionName + ")");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeDetails checkFunction(FHIRPathEngine engine, Object appContext, String functionName, TypeDetails focus, List<TypeDetails> parameters) throws PathEngineException {
|
||||
throw new NotImplementedException("Not done yet (FHIRPathTestEvaluationServices.checkFunction), when item is element");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Base> executeFunction(FHIRPathEngine engine, Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) {
|
||||
throw new NotImplementedException("Not done yet (FHIRPathTestEvaluationServices.executeFunction), when item is element");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Base resolveReference(FHIRPathEngine engine, Object appContext, String url, Base refContext) throws FHIRException {
|
||||
throw new NotImplementedException("Not done yet (FHIRPathTestEvaluationServices.resolveReference), when item is element");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean conformsToProfile(FHIRPathEngine engine, Object appContext, Base item, String url) throws FHIRException {
|
||||
if (url.equals("http://hl7.org/fhir/StructureDefinition/Patient"))
|
||||
return true;
|
||||
if (url.equals("http://hl7.org/fhir/StructureDefinition/Person"))
|
||||
return false;
|
||||
throw new FHIRException("unknown profile " + url);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueSet resolveValueSet(FHIRPathEngine engine, Object appContext, String url) {
|
||||
return context.fetchResource(ValueSet.class, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean paramIsType(String name, int index) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static FHIRPathEngine fp;
|
||||
private final Map<String, Base> resources = new HashMap<String, Base>();
|
||||
private static SimpleWorkerContext context;
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() throws FileNotFoundException, FHIRException, IOException {
|
||||
context = new SimpleWorkerContext((SimpleWorkerContext) TestingUtilities.getSharedWorkerContext());
|
||||
if (!context.hasPackage("hl7.cda.us.ccda", null)) {
|
||||
FilesystemPackageCacheManager pcm = new FilesystemPackageCacheManager.Builder().build();
|
||||
NpmPackage npm = pcm.loadPackage("hl7.cda.uv.core", "2.0.0");
|
||||
context.loadFromPackage(npm, null);
|
||||
npm = pcm.loadPackage("hl7.cda.us.ccda", "current");
|
||||
context.loadFromPackage(npm, null);
|
||||
}
|
||||
if (fp == null) {
|
||||
fp = new FHIRPathEngine(context);
|
||||
}
|
||||
}
|
||||
|
||||
public static Stream<Arguments> data() throws ParserConfigurationException, SAXException, IOException {
|
||||
Document dom = XMLUtil.parseToDom(TestingUtilities.loadTestResource("r5", "fhirpath", "tests-fhir-r5.xml"));
|
||||
|
||||
List<Element> list = new ArrayList<Element>();
|
||||
List<Element> groups = new ArrayList<Element>();
|
||||
XMLUtil.getNamedChildren(dom.getDocumentElement(), "group", groups);
|
||||
for (Element g : groups) {
|
||||
XMLUtil.getNamedChildren(g, "test", list);
|
||||
XMLUtil.getNamedChildren(g, "modeTest", list);
|
||||
}
|
||||
|
||||
List<Arguments> objects = new ArrayList<>();
|
||||
for (Element e : list) {
|
||||
objects.add(Arguments.of(getName(e), e));
|
||||
}
|
||||
|
||||
return objects.stream();
|
||||
}
|
||||
|
||||
private static Object getName(Element e) {
|
||||
String s = e.getAttribute("name");
|
||||
Element p = (Element) e.getParentNode();
|
||||
int ndx = 0;
|
||||
for (int i = 0; i < p.getChildNodes().getLength(); i++) {
|
||||
Node c = p.getChildNodes().item(i);
|
||||
if (c == e) {
|
||||
break;
|
||||
} else if (c instanceof Element) {
|
||||
ndx++;
|
||||
}
|
||||
}
|
||||
if (Utilities.noString(s)) {
|
||||
s = "?? - G " + p.getAttribute("name") + "[" + Integer.toString(ndx + 1) + "]";
|
||||
} else {
|
||||
s = s + " - G " + p.getAttribute("name") + "[" + Integer.toString(ndx + 1) + "]";
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@ParameterizedTest(name = "{index}: file {0}")
|
||||
@MethodSource("data")
|
||||
public void test(String name, Element test) throws FileNotFoundException, IOException, FHIRException, org.hl7.fhir.exceptions.FHIRException, UcumException {
|
||||
// Setting timezone for this test. Grahame is in UTC+11, Travis is in GMT, and I'm here in Toronto, Canada with
|
||||
// all my time based tests failing locally...
|
||||
TimeZone.setDefault(TimeZone.getTimeZone("UTC+1100"));
|
||||
|
||||
fp.setHostServices(new FHIRPathTestEvaluationServices());
|
||||
String input = test.getAttribute("inputfile");
|
||||
String expression = XMLUtil.getNamedChild(test, "expression").getTextContent();
|
||||
TestResultType fail = TestResultType.OK;
|
||||
if ("syntax".equals(XMLUtil.getNamedChild(test, "expression").getAttribute("invalid"))) {
|
||||
fail = TestResultType.SYNTAX;
|
||||
} else if ("semantic".equals(XMLUtil.getNamedChild(test, "expression").getAttribute("invalid"))) {
|
||||
fail = TestResultType.SEMANTICS;
|
||||
} else if ("execution".equals(XMLUtil.getNamedChild(test, "expression").getAttribute("invalid"))) {
|
||||
fail = TestResultType.EXECUTION;
|
||||
};
|
||||
fp.setAllowPolymorphicNames("lenient/polymorphics".equals(test.getAttribute("mode")));
|
||||
boolean skipStaticCheck = false;
|
||||
if ("true".equals(test.getAttribute("skipStaticCheck")))
|
||||
skipStaticCheck = true;
|
||||
Base res = null;
|
||||
|
||||
List<Base> outcome = new ArrayList<Base>();
|
||||
|
||||
System.out.println(name);
|
||||
|
||||
ExpressionNode node = null;
|
||||
try {
|
||||
node = fp.parse(expression);
|
||||
Assertions.assertTrue(fail != TestResultType.SYNTAX, String.format("Expected exception didn't occur parsing %s", expression));
|
||||
} catch (Exception e) {
|
||||
System.out.println("Parsing Error: "+e.getMessage());
|
||||
Assertions.assertTrue(fail == TestResultType.SYNTAX, String.format("Unexpected exception parsing %s: " + e.getMessage(), expression));
|
||||
}
|
||||
|
||||
if (node != null) {
|
||||
if (!Utilities.noString(input)) {
|
||||
res = resources.get(input);
|
||||
if (res == null) {
|
||||
if ("cda".equals(test.getAttribute("mode"))) {
|
||||
res = Manager.makeParser(fp.getWorker(), FhirFormat.XML).parseSingle(TestingUtilities.loadTestResourceStream("r5", input), null);
|
||||
} else if (input.endsWith(".json")) {
|
||||
res = new JsonParser().parse(TestingUtilities.loadTestResourceStream("r5", input));
|
||||
} else {
|
||||
res = new XmlParser().parse(TestingUtilities.loadTestResourceStream("r5", input));
|
||||
}
|
||||
resources.put(input, res);
|
||||
}
|
||||
}
|
||||
|
||||
if (!skipStaticCheck) {
|
||||
try {
|
||||
if (Utilities.noString(input)) {
|
||||
fp.check(null, null, node);
|
||||
} else {
|
||||
fp.check(res, res.fhirType(), res.fhirType(), node);
|
||||
}
|
||||
Assertions.assertTrue(fail != TestResultType.SEMANTICS, String.format("Expected exception didn't occur checking %s", expression));
|
||||
} catch (Exception e) {
|
||||
System.out.println("Checking Error: "+e.getMessage());
|
||||
Assertions.assertTrue(fail == TestResultType.SEMANTICS, String.format("Unexpected exception checking %s: " + e.getMessage(), expression));
|
||||
node = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (node != null) {
|
||||
try {
|
||||
if ("element".equals(test.getAttribute("mode"))) {
|
||||
List<ValidatedFragment> e = Manager.parse(fp.getWorker(), TestingUtilities.loadTestResourceStream("r5", input), input.endsWith(".json") ? FhirFormat.JSON : FhirFormat.XML);
|
||||
outcome = fp.evaluate(e.get(0).getElement(), node);
|
||||
} else {
|
||||
outcome = fp.evaluate(res, node);
|
||||
}
|
||||
Assertions.assertTrue(fail == TestResultType.OK, String.format("Expected exception didn't occur executing %s", expression));
|
||||
} catch (Exception e) {
|
||||
System.out.println("Execution Error: "+e.getMessage());
|
||||
Assertions.assertTrue(fail == TestResultType.EXECUTION, String.format("Unexpected exception executing %s: " + e.getMessage(), expression));
|
||||
node = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (fp.hasLog()) {
|
||||
System.out.println(name);
|
||||
System.out.println(fp.takeLog());
|
||||
}
|
||||
|
||||
if (node != null) {
|
||||
if ("true".equals(test.getAttribute("predicate"))) {
|
||||
boolean ok = fp.convertToBoolean(outcome);
|
||||
outcome.clear();
|
||||
outcome.add(new BooleanType(ok));
|
||||
}
|
||||
|
||||
List<Element> expected = new ArrayList<Element>();
|
||||
XMLUtil.getNamedChildren(test, "output", expected);
|
||||
assertEquals(outcome.size(), expected.size(), String.format("Expected %d objects but found %d for expression %s", expected.size(), outcome.size(), expression));
|
||||
if ("false".equals(test.getAttribute("ordered"))) {
|
||||
for (int i = 0; i < Math.min(outcome.size(), expected.size()); i++) {
|
||||
String tn = outcome.get(i).fhirType();
|
||||
String s;
|
||||
if (outcome.get(i) instanceof Quantity) {
|
||||
s = fp.convertToString(outcome.get(i));
|
||||
} else {
|
||||
s = ((PrimitiveType) outcome.get(i)).asStringValue();
|
||||
}
|
||||
boolean found = false;
|
||||
for (Element e : expected) {
|
||||
if ((Utilities.noString(e.getAttribute("type")) || e.getAttribute("type").equals(tn)) &&
|
||||
(Utilities.noString(e.getTextContent()) || e.getTextContent().equals(s))) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
Assertions.assertTrue(found, String.format("Outcome %d: Value %s of type %s not expected for %s", i, s, tn, expression));
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < Math.min(outcome.size(), expected.size()); i++) {
|
||||
String tn = expected.get(i).getAttribute("type");
|
||||
if (!Utilities.noString(tn)) {
|
||||
assertEquals(tn, outcome.get(i).fhirType(), String.format("Outcome %d: Type should be %s but was %s", i, tn, outcome.get(i).fhirType()));
|
||||
}
|
||||
String v = expected.get(i).getTextContent();
|
||||
if (!Utilities.noString(v)) {
|
||||
if (outcome.get(i) instanceof Quantity) {
|
||||
Quantity q = fp.parseQuantityString(v);
|
||||
Assertions.assertTrue(outcome.get(i).equalsDeep(q), String.format("Outcome %d: Value should be %s but was %s", i, v, outcome.get(i).toString()));
|
||||
} else {
|
||||
Assertions.assertTrue(outcome.get(i) instanceof PrimitiveType, String.format("Outcome %d: Value should be a primitive type but was %s", i, outcome.get(i).fhirType()));
|
||||
if (!(v.equals(((PrimitiveType) outcome.get(i)).fpValue()))) {
|
||||
System.out.println(name);
|
||||
System.out.println(String.format("Outcome %d: Value should be %s but was %s for expression %s", i, v, ((PrimitiveType) outcome.get(i)).fpValue(), expression));
|
||||
}
|
||||
assertEquals(v, ((PrimitiveType) outcome.get(i)).fpValue(), String.format("Outcome %d: Value should be %s but was %s for expression %s", i, v, ((PrimitiveType) outcome.get(i)).fpValue(), expression));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("resolveConstant returns a list of Base")
|
||||
public void resolveConstantReturnsList() {
|
||||
final String DUMMY_CONSTANT_1 = "dummyConstant1";
|
||||
final String DUMMY_CONSTANT_2 = "dummyConstant2";
|
||||
fp.setHostServices(new FHIRPathTestEvaluationServices() {
|
||||
@Override
|
||||
public List<Base> resolveConstant(FHIRPathEngine engine, Object appContext, String name, boolean beforeContext, boolean explicitConstant) throws PathEngineException {
|
||||
|
||||
return Arrays.asList(
|
||||
new StringType(DUMMY_CONSTANT_1).noExtensions(),
|
||||
new StringType(DUMMY_CONSTANT_2).noExtensions());
|
||||
}
|
||||
});
|
||||
|
||||
ExpressionNode expressionNode = fp.parse("%dummyConstant");
|
||||
|
||||
List<Base> result = fp.evaluate(null, expressionNode);
|
||||
assertEquals(2, result.size());
|
||||
assertEquals(DUMMY_CONSTANT_1, result.get(0).primitiveValue());
|
||||
assertEquals(DUMMY_CONSTANT_2, result.get(1).primitiveValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEvaluate_Id() {
|
||||
Patient input = new Patient();
|
||||
input.setId(new IdType("http://base/Patient/123/_history/222"));
|
||||
List<Base> results = fp.evaluate(input, "Patient.id");
|
||||
assertEquals(1, results.size());
|
||||
assertEquals("123", results.get(0).toString());
|
||||
}
|
||||
package org.hl7.fhir.r5.test;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
import org.fhir.ucum.UcumException;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.exceptions.PathEngineException;
|
||||
import org.hl7.fhir.r5.context.SimpleWorkerContext;
|
||||
import org.hl7.fhir.r5.elementmodel.Manager;
|
||||
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
|
||||
import org.hl7.fhir.r5.fhirpath.ExpressionNode;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
|
||||
import org.hl7.fhir.r5.fhirpath.TypeDetails;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine.IEvaluationContext;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.FunctionDetails;
|
||||
import org.hl7.fhir.r5.elementmodel.ValidatedFragment;
|
||||
import org.hl7.fhir.r5.formats.JsonParser;
|
||||
import org.hl7.fhir.r5.formats.XmlParser;
|
||||
import org.hl7.fhir.r5.model.*;
|
||||
import org.hl7.fhir.r5.test.utils.TestingUtilities;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
|
||||
import org.hl7.fhir.utilities.npm.NpmPackage;
|
||||
import org.hl7.fhir.utilities.xml.XMLUtil;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
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 org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class FHIRPathTests {
|
||||
|
||||
public enum TestResultType {OK, SYNTAX, SEMANTICS, EXECUTION}
|
||||
|
||||
public class FHIRPathTestEvaluationServices implements IEvaluationContext {
|
||||
|
||||
@Override
|
||||
public List<Base> resolveConstant(FHIRPathEngine engine, Object appContext, String name, boolean beforeContext, boolean explicitConstant) throws PathEngineException {
|
||||
throw new NotImplementedException("Not done yet (FHIRPathTestEvaluationServices.resolveConstant), when item is element");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeDetails resolveConstantType(FHIRPathEngine engine, Object appContext, String name, boolean explicitConstant) throws PathEngineException {
|
||||
throw new NotImplementedException("Not done yet (FHIRPathTestEvaluationServices.resolveConstantType), when item is element");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean log(String argument, List<Base> focus) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionDetails resolveFunction(FHIRPathEngine engine, String functionName) {
|
||||
throw new NotImplementedException("Not done yet (FHIRPathTestEvaluationServices.resolveFunction), when item is element (for " + functionName + ")");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeDetails checkFunction(FHIRPathEngine engine, Object appContext, String functionName, TypeDetails focus, List<TypeDetails> parameters) throws PathEngineException {
|
||||
throw new NotImplementedException("Not done yet (FHIRPathTestEvaluationServices.checkFunction), when item is element");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Base> executeFunction(FHIRPathEngine engine, Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) {
|
||||
throw new NotImplementedException("Not done yet (FHIRPathTestEvaluationServices.executeFunction), when item is element");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Base resolveReference(FHIRPathEngine engine, Object appContext, String url, Base refContext) throws FHIRException {
|
||||
throw new NotImplementedException("Not done yet (FHIRPathTestEvaluationServices.resolveReference), when item is element");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean conformsToProfile(FHIRPathEngine engine, Object appContext, Base item, String url) throws FHIRException {
|
||||
if (url.equals("http://hl7.org/fhir/StructureDefinition/Patient"))
|
||||
return true;
|
||||
if (url.equals("http://hl7.org/fhir/StructureDefinition/Person"))
|
||||
return false;
|
||||
throw new FHIRException("unknown profile " + url);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueSet resolveValueSet(FHIRPathEngine engine, Object appContext, String url) {
|
||||
return context.fetchResource(ValueSet.class, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean paramIsType(String name, int index) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static FHIRPathEngine fp;
|
||||
private final Map<String, Base> resources = new HashMap<String, Base>();
|
||||
private static SimpleWorkerContext context;
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() throws FileNotFoundException, FHIRException, IOException {
|
||||
context = new SimpleWorkerContext((SimpleWorkerContext) TestingUtilities.getSharedWorkerContext());
|
||||
if (!context.hasPackage("hl7.cda.us.ccda", null)) {
|
||||
FilesystemPackageCacheManager pcm = new FilesystemPackageCacheManager.Builder().build();
|
||||
NpmPackage npm = pcm.loadPackage("hl7.cda.uv.core", "2.0.0");
|
||||
context.loadFromPackage(npm, null);
|
||||
npm = pcm.loadPackage("hl7.cda.us.ccda", "current");
|
||||
context.loadFromPackage(npm, null);
|
||||
}
|
||||
if (fp == null) {
|
||||
fp = new FHIRPathEngine(context);
|
||||
}
|
||||
}
|
||||
|
||||
public static Stream<Arguments> data() throws ParserConfigurationException, SAXException, IOException {
|
||||
Document dom = XMLUtil.parseToDom(TestingUtilities.loadTestResource("r5", "fhirpath", "tests-fhir-r5.xml"));
|
||||
|
||||
List<Element> list = new ArrayList<Element>();
|
||||
List<Element> groups = new ArrayList<Element>();
|
||||
XMLUtil.getNamedChildren(dom.getDocumentElement(), "group", groups);
|
||||
for (Element g : groups) {
|
||||
XMLUtil.getNamedChildren(g, "test", list);
|
||||
XMLUtil.getNamedChildren(g, "modeTest", list);
|
||||
}
|
||||
|
||||
List<Arguments> objects = new ArrayList<>();
|
||||
for (Element e : list) {
|
||||
objects.add(Arguments.of(getName(e), e));
|
||||
}
|
||||
|
||||
return objects.stream();
|
||||
}
|
||||
|
||||
private static Object getName(Element e) {
|
||||
String s = e.getAttribute("name");
|
||||
Element p = (Element) e.getParentNode();
|
||||
int ndx = 0;
|
||||
for (int i = 0; i < p.getChildNodes().getLength(); i++) {
|
||||
Node c = p.getChildNodes().item(i);
|
||||
if (c == e) {
|
||||
break;
|
||||
} else if (c instanceof Element) {
|
||||
ndx++;
|
||||
}
|
||||
}
|
||||
if (Utilities.noString(s)) {
|
||||
s = "?? - G " + p.getAttribute("name") + "[" + Integer.toString(ndx + 1) + "]";
|
||||
} else {
|
||||
s = s + " - G " + p.getAttribute("name") + "[" + Integer.toString(ndx + 1) + "]";
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@ParameterizedTest(name = "{index}: file {0}")
|
||||
@MethodSource("data")
|
||||
public void test(String name, Element test) throws FileNotFoundException, IOException, FHIRException, org.hl7.fhir.exceptions.FHIRException, UcumException {
|
||||
// Setting timezone for this test. Grahame is in UTC+11, Travis is in GMT, and I'm here in Toronto, Canada with
|
||||
// all my time based tests failing locally...
|
||||
TimeZone.setDefault(TimeZone.getTimeZone("UTC+1100"));
|
||||
|
||||
fp.setHostServices(new FHIRPathTestEvaluationServices());
|
||||
String input = test.getAttribute("inputfile");
|
||||
String expression = XMLUtil.getNamedChild(test, "expression").getTextContent();
|
||||
TestResultType fail = TestResultType.OK;
|
||||
if ("syntax".equals(XMLUtil.getNamedChild(test, "expression").getAttribute("invalid"))) {
|
||||
fail = TestResultType.SYNTAX;
|
||||
} else if ("semantic".equals(XMLUtil.getNamedChild(test, "expression").getAttribute("invalid"))) {
|
||||
fail = TestResultType.SEMANTICS;
|
||||
} else if ("execution".equals(XMLUtil.getNamedChild(test, "expression").getAttribute("invalid"))) {
|
||||
fail = TestResultType.EXECUTION;
|
||||
};
|
||||
fp.setAllowPolymorphicNames("lenient/polymorphics".equals(test.getAttribute("mode")));
|
||||
boolean skipStaticCheck = false;
|
||||
if ("true".equals(test.getAttribute("skipStaticCheck")))
|
||||
skipStaticCheck = true;
|
||||
Base res = null;
|
||||
|
||||
List<Base> outcome = new ArrayList<Base>();
|
||||
|
||||
System.out.println(name);
|
||||
|
||||
ExpressionNode node = null;
|
||||
try {
|
||||
node = fp.parse(expression);
|
||||
Assertions.assertTrue(fail != TestResultType.SYNTAX, String.format("Expected exception didn't occur parsing %s", expression));
|
||||
} catch (Exception e) {
|
||||
System.out.println("Parsing Error: "+e.getMessage());
|
||||
Assertions.assertTrue(fail == TestResultType.SYNTAX, String.format("Unexpected exception parsing %s: " + e.getMessage(), expression));
|
||||
}
|
||||
|
||||
if (node != null) {
|
||||
if (!Utilities.noString(input)) {
|
||||
res = resources.get(input);
|
||||
if (res == null) {
|
||||
if ("cda".equals(test.getAttribute("mode"))) {
|
||||
res = Manager.makeParser(fp.getWorker(), FhirFormat.XML).parseSingle(TestingUtilities.loadTestResourceStream("r5", input), null);
|
||||
} else if (input.endsWith(".json")) {
|
||||
res = new JsonParser().parse(TestingUtilities.loadTestResourceStream("r5", input));
|
||||
} else {
|
||||
res = new XmlParser().parse(TestingUtilities.loadTestResourceStream("r5", input));
|
||||
}
|
||||
resources.put(input, res);
|
||||
}
|
||||
}
|
||||
|
||||
if (!skipStaticCheck) {
|
||||
try {
|
||||
if (Utilities.noString(input)) {
|
||||
fp.check(null, null, node);
|
||||
} else {
|
||||
fp.check(res, res.fhirType(), res.fhirType(), node);
|
||||
}
|
||||
Assertions.assertTrue(fail != TestResultType.SEMANTICS, String.format("Expected exception didn't occur checking %s", expression));
|
||||
} catch (Exception e) {
|
||||
System.out.println("Checking Error: "+e.getMessage());
|
||||
Assertions.assertTrue(fail == TestResultType.SEMANTICS, String.format("Unexpected exception checking %s: " + e.getMessage(), expression));
|
||||
node = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (node != null) {
|
||||
try {
|
||||
if ("element".equals(test.getAttribute("mode"))) {
|
||||
List<ValidatedFragment> e = Manager.parse(fp.getWorker(), TestingUtilities.loadTestResourceStream("r5", input), input.endsWith(".json") ? FhirFormat.JSON : FhirFormat.XML);
|
||||
outcome = fp.evaluate(e.get(0).getElement(), node);
|
||||
} else {
|
||||
outcome = fp.evaluate(res, node);
|
||||
}
|
||||
Assertions.assertTrue(fail == TestResultType.OK, String.format("Expected exception didn't occur executing %s", expression));
|
||||
} catch (Exception e) {
|
||||
System.out.println("Execution Error: "+e.getMessage());
|
||||
Assertions.assertTrue(fail == TestResultType.EXECUTION, String.format("Unexpected exception executing %s: " + e.getMessage(), expression));
|
||||
node = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (fp.hasLog()) {
|
||||
System.out.println(name);
|
||||
System.out.println(fp.takeLog());
|
||||
}
|
||||
|
||||
if (node != null) {
|
||||
if ("true".equals(test.getAttribute("predicate"))) {
|
||||
boolean ok = fp.convertToBoolean(outcome);
|
||||
outcome.clear();
|
||||
outcome.add(new BooleanType(ok));
|
||||
}
|
||||
|
||||
List<Element> expected = new ArrayList<Element>();
|
||||
XMLUtil.getNamedChildren(test, "output", expected);
|
||||
assertEquals(outcome.size(), expected.size(), String.format("Expected %d objects but found %d for expression %s", expected.size(), outcome.size(), expression));
|
||||
if ("false".equals(test.getAttribute("ordered"))) {
|
||||
for (int i = 0; i < Math.min(outcome.size(), expected.size()); i++) {
|
||||
String tn = outcome.get(i).fhirType();
|
||||
String s;
|
||||
if (outcome.get(i) instanceof Quantity) {
|
||||
s = fp.convertToString(outcome.get(i));
|
||||
} else {
|
||||
s = ((PrimitiveType) outcome.get(i)).asStringValue();
|
||||
}
|
||||
boolean found = false;
|
||||
for (Element e : expected) {
|
||||
if ((Utilities.noString(e.getAttribute("type")) || e.getAttribute("type").equals(tn)) &&
|
||||
(Utilities.noString(e.getTextContent()) || e.getTextContent().equals(s))) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
Assertions.assertTrue(found, String.format("Outcome %d: Value %s of type %s not expected for %s", i, s, tn, expression));
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < Math.min(outcome.size(), expected.size()); i++) {
|
||||
String tn = expected.get(i).getAttribute("type");
|
||||
if (!Utilities.noString(tn)) {
|
||||
assertEquals(tn, outcome.get(i).fhirType(), String.format("Outcome %d: Type should be %s but was %s", i, tn, outcome.get(i).fhirType()));
|
||||
}
|
||||
String v = expected.get(i).getTextContent();
|
||||
if (!Utilities.noString(v)) {
|
||||
if (outcome.get(i) instanceof Quantity) {
|
||||
Quantity q = fp.parseQuantityString(v);
|
||||
Assertions.assertTrue(outcome.get(i).equalsDeep(q), String.format("Outcome %d: Value should be %s but was %s", i, v, outcome.get(i).toString()));
|
||||
} else {
|
||||
Assertions.assertTrue(outcome.get(i) instanceof PrimitiveType, String.format("Outcome %d: Value should be a primitive type but was %s", i, outcome.get(i).fhirType()));
|
||||
if (!(v.equals(((PrimitiveType) outcome.get(i)).fpValue()))) {
|
||||
System.out.println(name);
|
||||
System.out.println(String.format("Outcome %d: Value should be %s but was %s for expression %s", i, v, ((PrimitiveType) outcome.get(i)).fpValue(), expression));
|
||||
}
|
||||
assertEquals(v, ((PrimitiveType) outcome.get(i)).fpValue(), String.format("Outcome %d: Value should be %s but was %s for expression %s", i, v, ((PrimitiveType) outcome.get(i)).fpValue(), expression));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("resolveConstant returns a list of Base")
|
||||
public void resolveConstantReturnsList() {
|
||||
final String DUMMY_CONSTANT_1 = "dummyConstant1";
|
||||
final String DUMMY_CONSTANT_2 = "dummyConstant2";
|
||||
fp.setHostServices(new FHIRPathTestEvaluationServices() {
|
||||
@Override
|
||||
public List<Base> resolveConstant(FHIRPathEngine engine, Object appContext, String name, boolean beforeContext, boolean explicitConstant) throws PathEngineException {
|
||||
|
||||
return Arrays.asList(
|
||||
new StringType(DUMMY_CONSTANT_1).noExtensions(),
|
||||
new StringType(DUMMY_CONSTANT_2).noExtensions());
|
||||
}
|
||||
});
|
||||
|
||||
ExpressionNode expressionNode = fp.parse("%dummyConstant");
|
||||
|
||||
List<Base> result = fp.evaluate(null, expressionNode);
|
||||
assertEquals(2, result.size());
|
||||
assertEquals(DUMMY_CONSTANT_1, result.get(0).primitiveValue());
|
||||
assertEquals(DUMMY_CONSTANT_2, result.get(1).primitiveValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEvaluate_Id() {
|
||||
Patient input = new Patient();
|
||||
input.setId(new IdType("http://base/Patient/123/_history/222"));
|
||||
List<Base> results = fp.evaluate(input, "Patient.id");
|
||||
assertEquals(1, results.size());
|
||||
assertEquals("123", results.get(0).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEvaluate_ToStringOnDateValue() {
|
||||
Patient input = new Patient();
|
||||
var dtv = new DateType("2024");
|
||||
input.setBirthDateElement(dtv);
|
||||
List<Base> results = fp.evaluate(input, "Patient.birthDate.toString()");
|
||||
assertEquals(1, results.size());
|
||||
assertEquals("2024", results.get(0).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEvaluate_ToStringOnExtensionOnlyValue() {
|
||||
Patient input = new Patient();
|
||||
var dtv = new DateType();
|
||||
input.setBirthDateElement(dtv);
|
||||
List<Base> results = fp.evaluate(input, "Patient.birthDate.toString()");
|
||||
assertEquals(0, results.size());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue