Add terminology service tests

This commit is contained in:
Grahame Grieve 2023-04-02 23:07:34 +10:00
parent 0bbff8d14d
commit 42a9e47379
8 changed files with 401 additions and 35 deletions

View File

@ -820,7 +820,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
return res;
}
}
p.setParameter("includeDefinition", false);
p.setParameter("excludeNested", !hierarchical);
if (incompleteOk) {
p.setParameter("incomplete-ok", true);
@ -872,7 +872,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
}
res = new ValueSetExpansionOutcome(result).setTxLink(txLog.getLastId());
} catch (Exception e) {
res = new ValueSetExpansionOutcome(e.getMessage() == null ? e.getClass().getName() : e.getMessage(), TerminologyServiceErrorClass.UNKNOWN, allErrors).setTxLink(txLog == null ? null : txLog.getLastId());
res = new ValueSetExpansionOutcome((e.getMessage() == null ? e.getClass().getName() : e.getMessage()), TerminologyServiceErrorClass.UNKNOWN, allErrors).setTxLink(txLog == null ? null : txLog.getLastId());
}
txCache.cacheExpansion(cacheToken, res, TerminologyCache.PERMANENT);
return res;

View File

@ -0,0 +1,55 @@
package org.hl7.fhir.r5.elementmodel;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.utilities.i18n.LanguageFileProducer;
import org.hl7.fhir.utilities.i18n.LanguageFileProducer.LanguageProducerLanguageSession;
import org.hl7.fhir.utilities.i18n.LanguageFileProducer.TextUnit;
/**
* in here:
* * generateTranslations
* * importFromTranslations
* * stripTranslations
* * switchLanguage
*
* in the validator
*
* @author grahamegrieve
* generateTranslations = -langTransform export -src {src} -tgt {tgt} -dest {dest}
* importFromTranslations = -langTransform import -src {src} -tgt {tgt} -dest {dest}
*/
public class LangaugeUtils {
IWorkerContext context;
public LangaugeUtils(IWorkerContext context) {
super();
this.context = context;
}
public void generateTranslations(Element resource, LanguageProducerLanguageSession session) {
translate(resource, session);
}
private void translate(Element element, LanguageProducerLanguageSession langSession) {
if (element.isPrimitive() && isTranslatable(element)) {
String base = element.primitiveValue();
if (base != null) {
String translation = element.getTranslation(langSession.getTargetLang());
langSession.entry(new TextUnit(element.getPath(), base, translation));
}
}
for (Element c: element.getChildren()) {
translate(c, langSession);
}
}
private boolean isTranslatable(Element element) {
return element.getProperty().isTranslatable();
}
public static boolean matches(String dstLang, String srcLang) {
return dstLang == null ? false : dstLang.equals(srcLang);
}
}

View File

@ -1810,5 +1810,15 @@ public boolean getParameterBool(String name) {
}
// end addition
public String getParameterString(String name) {
for (ParametersParameterComponent p : getParameter()) {
if (p.getName().equals(name)) {
if (p.getValue() instanceof PrimitiveType)
return ((PrimitiveType) p.getValue()).primitiveValue();
}
}
return null;
}
}

View File

@ -102,6 +102,9 @@ public interface ValueSetExpander {
if (!allErrors.contains(error)) {
allErrors.add(error);
}
if (!errList.contains(error)) {
errList.add(error);
}
}
public ValueSet getValueset() {

View File

@ -81,6 +81,8 @@ import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.exceptions.NoTerminologyServiceException;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.LangaugeUtils;
import org.hl7.fhir.r5.extensions.Extensions;
import org.hl7.fhir.r5.model.BooleanType;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode;
@ -98,6 +100,7 @@ import org.hl7.fhir.r5.model.PackageInformation;
import org.hl7.fhir.r5.model.Parameters;
import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent;
import org.hl7.fhir.r5.model.PrimitiveType;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.UriType;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent;
@ -117,6 +120,12 @@ import com.google.errorprone.annotations.NoAllocation;
public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetExpander {
public interface IConceptFilter {
boolean includeConcept(CodeSystem cs, ConceptDefinitionComponent def);
}
public class PropertyFilter implements IConceptFilter {
private ConceptSetFilterComponent filter;
@ -172,12 +181,20 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
}
}
public interface IConceptFilter {
public class RegexFilter implements IConceptFilter {
boolean includeConcept(CodeSystem cs, ConceptDefinitionComponent def);
private String regex;
protected RegexFilter(String regex) {
super();
this.regex = regex;
}
@Override
public boolean includeConcept(CodeSystem cs, ConceptDefinitionComponent def) {
return def.getCode().matches(regex);
}
}
private List<ValueSetExpansionContainsComponent> codes = new ArrayList<ValueSet.ValueSetExpansionContainsComponent>();
private List<ValueSetExpansionContainsComponent> roots = new ArrayList<ValueSet.ValueSetExpansionContainsComponent>();
private Map<String, ValueSetExpansionContainsComponent> map = new HashMap<String, ValueSet.ValueSetExpansionContainsComponent>();
@ -208,8 +225,9 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
maxExpansionSize = theMaxExpansionSize;
}
private ValueSetExpansionContainsComponent addCode(String system, String code, String display, ValueSetExpansionContainsComponent parent, List<ConceptDefinitionDesignationComponent> designations, Parameters expParams,
boolean isAbstract, boolean inactive, List<ValueSet> filters, boolean noInactive, boolean deprecated, List<ValueSetExpansionPropertyComponent> vsProp) {
private ValueSetExpansionContainsComponent addCode(String system, String code, String display, String dispLang, ValueSetExpansionContainsComponent parent, List<ConceptDefinitionDesignationComponent> designations, Parameters expParams,
boolean isAbstract, boolean inactive, String definition, List<ValueSet> filters, boolean noInactive, boolean deprecated, List<ValueSetExpansionPropertyComponent> vsProp,
List<ConceptPropertyComponent> csProps, List<org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent> expProps) {
if (filters != null && !filters.isEmpty() && !filterContainsCode(filters, system, code))
return null;
@ -227,10 +245,30 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
if (deprecated) {
ValueSetUtilities.setDeprecated(vsProp, n);
}
if (expParams.getParameterBool("includeDefinition") && definition != null) {
n.addExtension(Extensions.makeVSConceptDefinition(definition));
}
// display and designations
String srcLang = dispLang;
String dstLang = focus.getLanguage();
boolean usedDisplay = false;
ConceptDefinitionDesignationComponent tu = expParams.hasParameter("displayLanguage") ? getMatchingLang(designations, expParams.getParameterString("displayLanguage")) : null;
if (tu != null) {
n.setDisplay(tu.getValue());
} else if (display != null && (srcLang == null || dstLang == null || LangaugeUtils.matches(dstLang, srcLang))) {
n.setDisplay(display);
usedDisplay = true;
} else {
// we don't have a usable display
}
if (expParams.getParameterBool("includeDesignations") && designations != null) {
if (!usedDisplay && display != null) {
n.addDesignation().setLanguage(srcLang).setValue(display);
}
for (ConceptDefinitionDesignationComponent t : designations) {
if (t.getLanguage() != null || t.getValue() != null) {
if (t != tu && (t.hasLanguage() || t.hasUse()) && t.getValue() != null) {
ConceptReferenceDesignationComponent d = n.addDesignation();
if (t.getLanguage() != null) {
d.setLanguage(t.getLanguage().trim());
@ -238,14 +276,32 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
if (t.getValue() != null) {
d.setValue(t.getValue().trim());
}
if (t.getUse() != null) {
d.setUse(t.getUse());
}
}
}
}
ConceptDefinitionDesignationComponent t = expParams.hasLanguage() ? getMatchingLang(designations, expParams.getLanguage()) : null;
if (t == null)
n.setDisplay(display);
else
n.setDisplay(t.getValue());
for (ParametersParameterComponent p : expParams.getParameter()) {
if ("property".equals(p.getName())) {
if (csProps != null && p.hasValue()) {
for (ConceptPropertyComponent cp : csProps) {
if (p.getValue().primitiveValue().equals(cp.getCode())) {
n.addProperty().setCode(cp.getCode()).setValue(cp.getValue());
}
}
}
if (expProps != null && p.hasValue()) {
for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent cp : expProps) {
if (p.getValue().primitiveValue().equals(cp.getCode())) {
n.addProperty(cp);
}
}
}
}
}
String s = key(n);
if (map.containsKey(s) || excludeKeys.contains(s)) {
@ -290,12 +346,12 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
return null;
}
private void addCodeAndDescendents(ValueSetExpansionContainsComponent focus, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps) throws FHIRException {
private void addCodeAndDescendents(ValueSetExpansionContainsComponent focus, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, ValueSet vsSrc) throws FHIRException {
focus.checkNoModifiers("Expansion.contains", "expanding");
ValueSetExpansionContainsComponent np = addCode(focus.getSystem(), focus.getCode(), focus.getDisplay(), parent,
convert(focus.getDesignation()), expParams, focus.getAbstract(), focus.getInactive(), filters, noInactive, false, vsProps);
ValueSetExpansionContainsComponent np = addCode(focus.getSystem(), focus.getCode(), focus.getDisplay(), vsSrc.getLanguage(), parent,
convert(focus.getDesignation()), expParams, focus.getAbstract(), focus.getInactive(), focus.getExtensionString(ToolingExtensions.EXT_DEFINITION), filters, noInactive, false, vsProps, null, focus.getProperty());
for (ValueSetExpansionContainsComponent c : focus.getContains())
addCodeAndDescendents(focus, np, expParams, filters, noInactive, vsProps);
addCodeAndDescendents(focus, np, expParams, filters, noInactive, vsProps, vsSrc);
}
private List<ConceptDefinitionDesignationComponent> convert(List<ConceptReferenceDesignationComponent> designations) {
@ -310,7 +366,8 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
return list;
}
private void addCodeAndDescendents(CodeSystem cs, String system, ConceptDefinitionComponent def, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters, ConceptDefinitionComponent exclusion, IConceptFilter filterFunc, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps) throws FHIRException {
private void addCodeAndDescendents(CodeSystem cs, String system, ConceptDefinitionComponent def, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters,
ConceptDefinitionComponent exclusion, IConceptFilter filterFunc, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps) throws FHIRException {
def.checkNoModifiers("Code in Code System", "expanding");
if (exclusion != null) {
if (exclusion.getCode().equals(def.getCode()))
@ -321,7 +378,7 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
boolean inc = CodeSystemUtilities.isInactive(cs, def);
boolean dep = CodeSystemUtilities.isDeprecated(cs, def, false);
if ((includeAbstract || !abs) && filterFunc.includeConcept(cs, def)) {
np = addCode(system, def.getCode(), def.getDisplay(), parent, def.getDesignation(), expParams, abs, inc, filters, noInactive, dep, vsProps);
np = addCode(system, def.getCode(), def.getDisplay(), cs.getLanguage(), parent, def.getDesignation(), expParams, abs, inc, def.getDefinition(), filters, noInactive, dep, vsProps, def.getProperty(), null);
}
for (ConceptDefinitionComponent c : def.getConcept()) {
addCodeAndDescendents(cs, system, c, np, expParams, filters, exclusion, filterFunc, noInactive, vsProps);
@ -334,7 +391,7 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
}
private void addCodes(ValueSetExpansionComponent expand, List<ValueSetExpansionParameterComponent> params, Parameters expParams, List<ValueSet> filters, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps) throws ETooCostly, FHIRException {
private void addCodes(ValueSetExpansionComponent expand, List<ValueSetExpansionParameterComponent> params, Parameters expParams, List<ValueSet> filters, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, ValueSet vsSrc) throws ETooCostly, FHIRException {
if (expand != null) {
if (expand.getContains().size() > maxExpansionSize)
throw failCostly("Too many codes to display (>" + Integer.toString(expand.getContains().size()) + ")");
@ -343,7 +400,7 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
params.add(p);
}
copyImportContains(expand.getContains(), null, expParams, filters, noInactive, vsProps);
copyImportContains(expand.getContains(), null, expParams, filters, noInactive, vsProps, vsSrc);
}
}
@ -432,8 +489,12 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
focus.getExpansion().setTimestampElement(DateTimeType.now());
focus.getExpansion().setIdentifier(Factory.createUUID());
for (ParametersParameterComponent p : expParams.getParameter()) {
if (Utilities.existsInList(p.getName(), "includeDesignations", "excludeNested", "activeOnly"))
if (Utilities.existsInList(p.getName(), "includeDesignations", "excludeNested", "activeOnly")) {
focus.getExpansion().addParameter().setName(p.getName()).setValue(p.getValue());
}
}
if (expParams.hasLanguage()) {
focus.setLanguage(expParams.getLanguage());
}
if (source.hasCompose()) {
@ -591,11 +652,12 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
}
}
private void copyImportContains(List<ValueSetExpansionContainsComponent> list, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filter, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps) throws FHIRException {
private void copyImportContains(List<ValueSetExpansionContainsComponent> list, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filter, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, ValueSet vsSrc) throws FHIRException {
for (ValueSetExpansionContainsComponent c : list) {
c.checkNoModifiers("Imported Expansion in Code System", "expanding");
ValueSetExpansionContainsComponent np = addCode(c.getSystem(), c.getCode(), c.getDisplay(), parent, null, expParams, c.getAbstract(), c.getInactive(), filter, noInactive, false, vsProps);
copyImportContains(c.getContains(), np, expParams, filter, noInactive, vsProps);
ValueSetExpansionContainsComponent np = addCode(c.getSystem(), c.getCode(), c.getDisplay(), vsSrc.getLanguage(), parent, null, expParams, c.getAbstract(), c.getInactive(), c.getExtensionString(ToolingExtensions.EXT_DEFINITION),
filter, noInactive, false, vsProps, null, c.getProperty());
copyImportContains(c.getContains(), np, expParams, filter, noInactive, vsProps, vsSrc);
}
}
@ -612,13 +674,13 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
ValueSet base = imports.get(0);
imports.remove(0);
base.checkNoModifiers("Imported ValueSet", "expanding");
copyImportContains(base.getExpansion().getContains(), null, expParams, imports, noInactive, base.getExpansion().getProperty());
copyImportContains(base.getExpansion().getContains(), null, expParams, imports, noInactive, base.getExpansion().getProperty(), base);
} else {
CodeSystem cs = context.fetchCodeSystem(inc.getSystem());
if (isServerSide(inc.getSystem()) || (cs == null || (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT))) {
doServerIncludeCodes(inc, heirarchical, exp, imports, expParams, extensions, noInactive, valueSet.getExpansion().getProperty());
} else {
doInternalIncludeCodes(inc, exp, expParams, imports, cs, noInactive);
doInternalIncludeCodes(inc, exp, expParams, imports, cs, noInactive, valueSet);
}
}
}
@ -653,7 +715,7 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
}
}
for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) {
addCodeAndDescendents(cc, null, expParams, imports, noInactive, vsProps);
addCodeAndDescendents(cc, null, expParams, imports, noInactive, vsProps, vs);
}
}
@ -666,7 +728,7 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
return false;
}
public void doInternalIncludeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, List<ValueSet> imports, CodeSystem cs, boolean noInactive) throws NoTerminologyServiceException, TerminologyServiceException, FHIRException {
public void doInternalIncludeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, List<ValueSet> imports, CodeSystem cs, boolean noInactive, Resource vsSrc) throws NoTerminologyServiceException, TerminologyServiceException, FHIRException {
if (cs == null) {
if (context.isNoTerminologyServer())
throw failTSE("Unable to find code system " + inc.getSystem().toString());
@ -712,7 +774,8 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
} else {
inactive = CodeSystemUtilities.isInactive(cs, def);
}
addCode(inc.getSystem(), c.getCode(), !Utilities.noString(c.getDisplay()) ? c.getDisplay() : def == null ? null : def.getDisplay(), null, convertDesignations(c.getDesignation()), expParams, false, inactive, imports, noInactive, false, exp.getProperty());
addCode(inc.getSystem(), c.getCode(), !Utilities.noString(c.getDisplay()) ? c.getDisplay() : def == null ? null : def.getDisplay(), c.hasDisplay() ? vsSrc.getLanguage() : cs.getLanguage(), null, mergeDesignations(def, convertDesignations(c.getDesignation())),
expParams, false, inactive, def == null ? null : def.getDefinition(), imports, noInactive, false, exp.getProperty(), def != null ? def.getProperty() : null, null);
}
}
if (inc.getFilter().size() > 1) {
@ -758,8 +821,8 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
if (def != null) {
if (isNotBlank(def.getDisplay()) && isNotBlank(fc.getValue())) {
if (def.getDisplay().contains(fc.getValue())) {
addCode(inc.getSystem(), def.getCode(), def.getDisplay(), null, def.getDesignation(), expParams, CodeSystemUtilities.isNotSelectable(cs, def), CodeSystemUtilities.isInactive(cs, def),
imports, noInactive, false, exp.getProperty());
addCode(inc.getSystem(), def.getCode(), def.getDisplay(), cs.getLanguage(), null, def.getDesignation(), expParams, CodeSystemUtilities.isNotSelectable(cs, def), CodeSystemUtilities.isInactive(cs, def),
def.getDefinition(), imports, noInactive, false, exp.getProperty(), def.getProperty(), null);
}
}
}
@ -767,12 +830,24 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
for (ConceptDefinitionComponent def : cs.getConcept()) {
addCodeAndDescendents(cs, inc.getSystem(), def, null, expParams, imports, null, new PropertyFilter(fc, getPropertyDefinition(cs, fc.getProperty())), noInactive, exp.getProperty());
}
} else if ("regex".equals(fc.getProperty()) && fc.getOp() == FilterOperator.EQUAL) {
for (ConceptDefinitionComponent def : cs.getConcept()) {
addCodeAndDescendents(cs, inc.getSystem(), def, null, expParams, imports, null, new RegexFilter(fc.getValue()), noInactive, exp.getProperty());
}
} else {
throw fail("Search by property[" + fc.getProperty() + "] and op[" + fc.getOp() + "] is not supported yet");
}
}
}
private List<ConceptDefinitionDesignationComponent> mergeDesignations(ConceptDefinitionComponent def,
List<ConceptDefinitionDesignationComponent> list) {
List<ConceptDefinitionDesignationComponent> res = new ArrayList<>();
res.addAll(def.getDesignation());
res.addAll(list);
return res;
}
private PropertyComponent getPropertyDefinition(CodeSystem cs, String property) {
for (PropertyComponent cp : cs.getProperty()) {
if (cp.getCode().equals(property)) {

View File

@ -2,6 +2,7 @@ package org.hl7.fhir.r5.test.utils;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.utilities.CSFile;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.ToolGlobalSettings;
@ -181,7 +182,7 @@ public class CompareUtilities extends BaseTestingUtilities {
String diff = null;
if (System.getProperty("os.name").contains("Linux"))
diff = Utilities.path("/", "usr", "bin", "meld");
else {
else if (System.getenv("ProgramFiles(X86)") != null) {
if (Utilities.checkFile("WinMerge", Utilities.path(System.getenv("ProgramFiles(X86)"), "WinMerge"), "\\WinMergeU.exe", null))
diff = Utilities.path(System.getenv("ProgramFiles(X86)"), "WinMerge", "WinMergeU.exe");
else if (Utilities.checkFile("WinMerge", Utilities.path(System.getenv("ProgramFiles(X86)"), "Meld"), "\\Meld.exe", null))
@ -271,7 +272,7 @@ public class CompareUtilities extends BaseTestingUtilities {
String actualJsonString = actualJsonPrimitive.getAsString();
String expectedJsonString = expectedJsonPrimitive.getAsString();
if (!(actualJsonString.contains("<div") && expectedJsonString.contains("<div")))
if (!actualJsonString.equals(expectedJsonString))
if (!matches(actualJsonString, expectedJsonString))
if (!sameBytes(unBase64(actualJsonString), unBase64(expectedJsonString)))
return createNotEqualMessage("string property values differ at " + path, expectedJsonString, actualJsonString);
} else if (actualJsonPrimitive.isNumber() && expectedJsonPrimitive.isNumber()) {
@ -301,6 +302,20 @@ public class CompareUtilities extends BaseTestingUtilities {
return null;
}
private static boolean matches(String actualJsonString, String expectedJsonString) {
if (expectedJsonString.startsWith("$") && expectedJsonString.endsWith("$")) {
switch (expectedJsonString) {
case "$$" : return true;
case "$instant$": return actualJsonString.matches("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]{1,9})?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))");
case "$uuid$": return actualJsonString.matches("urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}");
default:
throw new Error("Unhandled template: "+expectedJsonString);
}
} else {
return actualJsonString.equals(expectedJsonString);
}
}
public static String checkTextIsSame(String expected, String actual) throws JsonSyntaxException, FileNotFoundException, IOException {
return checkTextIsSame(expected, actual, true);
}
@ -349,4 +364,5 @@ public class CompareUtilities extends BaseTestingUtilities {
return createNotEqualMessage("Strings differ in length but match to the end of the shortest.", Integer.toString(expectedString.length()), Integer.toString(actualString.length()));
return null;
}
}

View File

@ -17,5 +17,5 @@ public class TestModules {
VALIDATION_MODULE
};
public static final List<String> JUNIT4_CLASSNAMES = Arrays.asList("org.hl7.fhir.validation.tests.ValidationTests");
public static final List<String> JUNIT4_CLASSNAMES = Arrays.asList("org.hl7.fhir.validation.tests.ValidationTests", "org.hl7.fhir.terminologies.tests.TerminologyServiceTests");
}

View File

@ -0,0 +1,207 @@
package org.hl7.fhir.terminology.tests;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50;
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_14_50;
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_50;
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.formats.XmlParser;
import org.hl7.fhir.r5.model.Constants;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionParameterComponent;
import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.r5.test.utils.CompareUtilities;
import org.hl7.fhir.r5.test.utils.TestingUtilities;
import org.hl7.fhir.terminology.tests.TerminologyServiceTests.JsonObjectPair;
import org.hl7.fhir.utilities.FhirPublication;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.validation.ValidationEngine;
import org.hl7.fhir.validation.tests.ValidationEngineTests;
import org.hl7.fhir.validation.tests.utilities.TestUtilities;
import org.junit.AfterClass;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import com.google.common.base.Charsets;
@RunWith(Parameterized.class)
public class TerminologyServiceTests {
public static class JsonObjectPair {
public JsonObjectPair(JsonObject suite, JsonObject test) {
this.suite = suite;
this.test = test;
}
private JsonObject suite;
private JsonObject test;
}
@Parameters(name = "{index}: id {0}")
public static Iterable<Object[]> data() throws IOException {
String contents = TestingUtilities.loadTestResource("tx", "test-cases.json");
Map<String, JsonObjectPair> examples = new HashMap<String, JsonObjectPair>();
manifest = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(contents);
for (org.hl7.fhir.utilities.json.model.JsonObject suite : manifest.getJsonObjects("suites")) {
String sn = suite.asString("name");
for (org.hl7.fhir.utilities.json.model.JsonObject test : suite.getJsonObjects("tests")) {
String tn = test.asString("name");
examples.put(sn+"."+tn, new JsonObjectPair(suite, test));
}
}
List<String> names = new ArrayList<String>(examples.size());
names.addAll(examples.keySet());
Collections.sort(names);
List<Object[]> objects = new ArrayList<Object[]>(examples.size());
for (String id : names) {
objects.add(new Object[]{id, examples.get(id)});
}
return objects;
}
private static org.hl7.fhir.utilities.json.model.JsonObject manifest;
private JsonObjectPair setup;
private String version;
private String name;
private static ValidationEngine baseEngine;
public TerminologyServiceTests(String name, JsonObjectPair setup) {
this.name = name;
this.setup = setup;
version = "5.0.0";
}
@SuppressWarnings("deprecation")
@Test
public void test() throws Exception {
if (baseEngine == null) {
baseEngine = TestUtilities.getValidationEngine("hl7.fhir.r5.core#5.0.0", ValidationEngineTests.DEF_TX, null, FhirPublication.R5, true, "5.0.0");
}
ValidationEngine engine = new ValidationEngine(this.baseEngine);
for (String s : setup.suite.forceArray("setup").asStrings()) {
Resource res = loadResource(s);
engine.seeResource(res);
}
Resource req = loadResource(setup.test.asString("request"));
String resp = TestingUtilities.loadTestResource("tx", setup.test.asString("response"));
if (setup.test.asString("operation").equals("expand")) {
expand(engine, req, resp, setup.test.asString("response"));
} else if (setup.test.asString("operation").equals("validate-code")) {
validate(engine, req, resp);
} else {
Assertions.fail("Unknown Operation "+setup.test.asString("operation"));
}
}
private void expand(ValidationEngine engine, Resource req, String resp, String fn) throws IOException {
String fp = Utilities.path("[tmp]", "tx", fn);
File fo = new File(fp);
if (fo.exists()) {
fo.delete();
}
org.hl7.fhir.r5.model.Parameters p = ( org.hl7.fhir.r5.model.Parameters) req;
ValueSet vs = engine.getContext().fetchResource(ValueSet.class, p.getParameterValue("url").primitiveValue());
boolean hierarchical = p.hasParameter("excludeNested") ? p.getParameterBool("excludeNested") == false : true;
Assertions.assertNotNull(vs);
ValueSetExpansionOutcome vse = engine.getContext().expandVS(vs, false, hierarchical, false, p);
if (resp.contains("\"ValueSet\"")) {
if (vse.getValueset() == null) {
Assertions.fail(vse.getError());
} else {
if (!p.hasParameter("excludeNested")) {
removeParameter(vse.getValueset(), "excludeNested");
}
String vsj = new JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(vse.getValueset());
String diff = CompareUtilities.checkJsonSrcIsSame(resp, vsj);
if (diff != null) {
Utilities.createDirectory(Utilities.getDirectoryForFile(fp));
TextFile.stringToFile(vsj, fp);
}
Assertions.assertTrue(diff == null, diff);
}
} else {
Assertions.fail("expand error not done yet");
}
}
private void removeParameter(ValueSet valueset, String name) {
for (ValueSetExpansionParameterComponent exp : valueset.getExpansion().getParameter()) {
if (exp.getName().equals(name)) {
valueset.getExpansion().getParameter().remove(exp);
return;
}
}
}
private void validate(ValidationEngine engine2, Resource req, String resp) {
Assertions.fail("validate not done yet");
}
public Resource loadResource(String filename) throws IOException, FHIRFormatError, FileNotFoundException, FHIRException, DefinitionException {
String contents = TestingUtilities.loadTestResource("tx", filename);
try (InputStream inputStream = IOUtils.toInputStream(contents, Charsets.UTF_8)) {
if (filename.contains(".json")) {
if (Constants.VERSION.equals(version) || "5.0".equals(version))
return new JsonParser().parse(inputStream);
else if (org.hl7.fhir.dstu3.model.Constants.VERSION.equals(version) || "3.0".equals(version))
return VersionConvertorFactory_30_50.convertResource(new org.hl7.fhir.dstu3.formats.JsonParser().parse(inputStream));
else if (org.hl7.fhir.dstu2016may.model.Constants.VERSION.equals(version) || "1.4".equals(version))
return VersionConvertorFactory_14_50.convertResource(new org.hl7.fhir.dstu2016may.formats.JsonParser().parse(inputStream));
else if (org.hl7.fhir.dstu2.model.Constants.VERSION.equals(version) || "1.0".equals(version))
return VersionConvertorFactory_10_50.convertResource(new org.hl7.fhir.dstu2.formats.JsonParser().parse(inputStream));
else if (org.hl7.fhir.r4.model.Constants.VERSION.equals(version) || "4.0".equals(version))
return VersionConvertorFactory_40_50.convertResource(new org.hl7.fhir.r4.formats.JsonParser().parse(inputStream));
else
throw new FHIRException("unknown version " + version);
} else {
if (Constants.VERSION.equals(version) || "5.0".equals(version))
return new XmlParser().parse(inputStream);
else if (org.hl7.fhir.dstu3.model.Constants.VERSION.equals(version) || "3.0".equals(version))
return VersionConvertorFactory_30_50.convertResource(new org.hl7.fhir.dstu3.formats.XmlParser().parse(inputStream));
else if (org.hl7.fhir.dstu2016may.model.Constants.VERSION.equals(version) || "1.4".equals(version))
return VersionConvertorFactory_14_50.convertResource(new org.hl7.fhir.dstu2016may.formats.XmlParser().parse(inputStream));
else if (org.hl7.fhir.dstu2.model.Constants.VERSION.equals(version) || "1.0".equals(version))
return VersionConvertorFactory_10_50.convertResource(new org.hl7.fhir.dstu2.formats.XmlParser().parse(inputStream));
else if (org.hl7.fhir.r4.model.Constants.VERSION.equals(version) || "4.0".equals(version))
return VersionConvertorFactory_40_50.convertResource(new org.hl7.fhir.r4.formats.XmlParser().parse(inputStream));
else
throw new FHIRException("unknown version " + version);
}
}
}
@AfterClass
public static void saveWhenDone() throws IOException {
}
}