From 63722b033d7e8d74c2a0e732897cf4dad28e188a Mon Sep 17 00:00:00 2001 From: oliveregger Date: Mon, 19 Feb 2024 19:41:22 +0100 Subject: [PATCH 1/2] add API tests directly #193 --- .vscode/launch.json | 8 + docs/changelog.md | 6 + matchbox-server/pom.xml | 84 ++- .../jpa/validation/ValidationProvider.java | 38 +- .../java/ch/ahdis/matchbox/CliContext.java | 433 ++++++++------- .../ahdis/matchbox/MatchboxEngineSupport.java | 4 + .../matchbox/util/EngineSessionCache.java | 27 +- .../matchbox/test/GenericFhirClient.java | 163 ++++++ .../ahdis/matchbox/test/MatchboxApiTest.java | 265 +++++++++ .../ahdis/matchbox/test/ValidationClient.java | 138 +++++ .../src/test/resources/application-test.yaml | 31 ++ .../src/test/resources/ehs-419.json | 9 + .../src/test/resources/ehs-431.json | 509 ++++++++++++++++++ .../src/test/resources/logback-test.xml | 12 + .../matchbox.health.test.ig.r4-0.1.0.tgz | Bin 0 -> 12697 bytes matchbox-server/with-cda/application.yaml | 3 +- pom.xml | 4 + 17 files changed, 1533 insertions(+), 201 deletions(-) create mode 100644 matchbox-server/src/test/java/ch/ahdis/matchbox/test/GenericFhirClient.java create mode 100644 matchbox-server/src/test/java/ch/ahdis/matchbox/test/MatchboxApiTest.java create mode 100644 matchbox-server/src/test/java/ch/ahdis/matchbox/test/ValidationClient.java create mode 100644 matchbox-server/src/test/resources/application-test.yaml create mode 100644 matchbox-server/src/test/resources/ehs-419.json create mode 100644 matchbox-server/src/test/resources/ehs-431.json create mode 100644 matchbox-server/src/test/resources/logback-test.xml create mode 100644 matchbox-server/src/test/resources/matchbox.health.test.ig.r4-0.1.0.tgz diff --git a/.vscode/launch.json b/.vscode/launch.json index 42d82af1fc4..5aab0a5a5de 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -67,6 +67,14 @@ "cwd": "${workspaceFolder}/matchbox-server" }, { + "type": "java", + "name": "Launch Matchbox-Server (test)", + "request": "launch", + "mainClass": "ca.uhn.fhir.jpa.starter.Application", + "projectName": "matchbox-server", + "vmArgs": "-Dspring.config.additional-location=file:/Users/oegger/Documents/github/matchbox/matchbox-server/target/test-classes/application-test.yaml", + "cwd": "${workspaceFolder}/matchbox-server" + }, { "type": "java", "name": "Launch Matchbox-Server", "request": "launch", diff --git a/docs/changelog.md b/docs/changelog.md index f6a0b240382..4e378fea91d 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,3 +1,9 @@ +2024/02/xx Release 3.6.0 + +- TODO: `docker pull europe-west6-docker.pkg.dev/ahdis-ch/ahdis/matchbox:v3.6.0` +- Upgraded to HAPI FHIR 7.0.0 and org.hl7.fhir.core 6.1.2.2 [#191](https://github.com/ahdis/matchbox/issues/191) +- added matchbox validation API tests [#193](https://github.com/ahdis/matchbox/issues/193) + 2024/01/31 Release 3.5.4 - `docker pull europe-west6-docker.pkg.dev/ahdis-ch/ahdis/matchbox:v3.5.4` diff --git a/matchbox-server/pom.xml b/matchbox-server/pom.xml index e74e1311cfb..456ff4d1873 100644 --- a/matchbox-server/pom.xml +++ b/matchbox-server/pom.xml @@ -135,11 +135,46 @@ h2 + + + org.testcontainers + testcontainers + test + + + org.testcontainers + elasticsearch + test + + + org.testcontainers + junit-jupiter + test + + ca.uhn.hapi.fhir hapi-fhir-client + + org.hl7.fhir.testcases + fhir-test-cases + test + + + + ca.uhn.hapi.fhir + hapi-fhir-test-utilities + test + + + + org.awaitility + awaitility + test + + org.springframework.boot spring-boot-autoconfigure @@ -162,6 +197,27 @@ javax.xml.bind jaxb-api + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.springframework.boot + spring-boot-starter-test + test + @@ -169,7 +225,7 @@ matchbox - + org.apache.maven.plugins @@ -190,10 +246,33 @@ true + + + ca.uhn.hapi.fhir + hapi-fhir-testpage-overlay + + false + + + org.apache.maven.plugins + maven-failsafe-plugin + + true + + + + + integration-test + verify + + + + + org.basepom.maven duplicate-finder-maven-plugin @@ -261,6 +340,9 @@ src/main/resources + + src/test/resources + ${project.build.directory}/generated-sources/properties false diff --git a/matchbox-server/src/main/java/ch/ahdis/fhir/hapi/jpa/validation/ValidationProvider.java b/matchbox-server/src/main/java/ch/ahdis/fhir/hapi/jpa/validation/ValidationProvider.java index 17227b52d1d..1b889688c8f 100644 --- a/matchbox-server/src/main/java/ch/ahdis/fhir/hapi/jpa/validation/ValidationProvider.java +++ b/matchbox-server/src/main/java/ch/ahdis/fhir/hapi/jpa/validation/ValidationProvider.java @@ -41,8 +41,13 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.StructureDefinition; import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat; +import org.hl7.fhir.r5.model.DateTimeType; +import org.hl7.fhir.r5.model.DateType; +import org.hl7.fhir.r5.model.Duration; import org.hl7.fhir.r5.model.OperationOutcome; import org.hl7.fhir.r5.model.StringType; +import org.hl7.fhir.r5.model.TimeType; +import org.hl7.fhir.r5.model.UriType; import org.hl7.fhir.r5.utils.OperationOutcomeUtilities; import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.utilities.validation.ValidationMessage; @@ -73,9 +78,6 @@ public class ValidationProvider { @Autowired private FhirContext myContext; - @Autowired - private INpmPackageVersionDao myPackageVersionDao; - // @Operation(name = "$canonical", manualRequest = true, idempotent = true, returnParameters = { // @OperationParam(name = "return", type = IBase.class, min = 1, max = 1) }) // public IBaseResource canonical(HttpServletRequest theRequest) { @@ -183,9 +185,10 @@ public IBaseResource validate(final HttpServletRequest theRequest) { return this.getOoForError("Error during validation: %s".formatted(e.getMessage())); } - sw.endCurrentTask(); + long millis = sw.getMillis(); log.debug("Validation time: {}", sw); - return this.getOperationOutcome(sha3Hex, messages, profile, engine, sw.formatTaskDurations(), cliContext); + + return this.getOperationOutcome(sha3Hex, messages, profile, engine, millis, cliContext); } private String getContentString(final HttpServletRequest theRequest, @@ -219,7 +222,7 @@ private IBaseResource getOperationOutcome(final String id, final List messages, final String profile, final MatchboxEngine engine, - final String taskDuration, + final long ms, final CliContext cliContext) { final var oo = new OperationOutcome(); oo.setId(id); @@ -231,6 +234,9 @@ private IBaseResource getOperationOutcome(final String id, issue.setCode(OperationOutcome.IssueType.INFORMATIONAL); final StructureDefinition structDef = engine.getStructureDefinition(profile); + + final org.hl7.fhir.r5.model.StructureDefinition structDefR5 = (org.hl7.fhir.r5.model.StructureDefinition) VersionConvertorFactory_40_50.convertResource(structDef); + final var profileDate = (structDef.getDateElement() != null) ? " (%s)".formatted(structDef.getDateElement().asStringValue()) : " "; @@ -241,10 +247,28 @@ private IBaseResource getOperationOutcome(final String id, structDef.getVersion(), profileDate, String.join(", ", engine.getContext().getLoadedPackages()), - taskDuration, + "" + ms/1000.0+ "s", VersionUtil.getPoweredBy(), cliContext.toString() )); + + var ext = issue.addExtension().setUrl("http://matchbox.health/validiation"); + ext.addExtension("profile", new UriType(structDef.getUrl())); + ext.addExtension("profileVersion", new UriType(structDef.getVersion())); + ext.addExtension("profileDate", structDefR5.getDateElement()); + + ext.addExtension("total", new Duration().setUnit("ms").setValue(ms) ); + if (matchboxEngineSupport.getSessionId(engine) != null) { + ext.addExtension("validatorVersion", new StringType(VersionUtil.getPoweredBy())); + } + cliContext.addContextToExtension(ext); + if (matchboxEngineSupport.getSessionId(engine) != null) { + ext.addExtension("sessionId", new StringType(matchboxEngineSupport.getSessionId(engine))); + } + for(String pkg : engine.getContext().getLoadedPackages()) { + ext.addExtension("package", new StringType(pkg)); + } + } // Map the SingleValidationMessages to OperationOutcomeIssue diff --git a/matchbox-server/src/main/java/ch/ahdis/matchbox/CliContext.java b/matchbox-server/src/main/java/ch/ahdis/matchbox/CliContext.java index e5675f0e16c..7a62c234781 100644 --- a/matchbox-server/src/main/java/ch/ahdis/matchbox/CliContext.java +++ b/matchbox-server/src/main/java/ch/ahdis/matchbox/CliContext.java @@ -10,6 +10,11 @@ import java.util.stream.Collectors; import org.apache.commons.beanutils.BeanUtils; +import org.apache.jena.sparql.function.library.e; +import org.hl7.fhir.r5.model.BooleanType; +import org.hl7.fhir.r5.model.Extension; +import org.hl7.fhir.r5.model.StringType; +import org.hl7.fhir.r5.model.UriType; import org.hl7.fhir.r5.terminologies.JurisdictionUtilities; import org.hl7.fhir.r5.utils.validation.BundleValidationRule; import org.hl7.fhir.utilities.VersionUtilities; @@ -28,15 +33,16 @@ /** * A POJO for storing the flags/values for the CLI validator. - * Needed to copy the class because the setters with CliContext as return type are not accessible via reflection - * In addition we have parameters from the CliContext which do not make sense to expose for the Web APi + * Needed to copy the class because the setters with CliContext as return type + * are not accessible via reflection + * In addition we have parameters from the CliContext which do not make sense to + * expose for the Web APi */ @Component public class CliContext { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CliContext.class); - @JsonProperty("doNative") private boolean doNative = false; @JsonProperty("hintAboutNonMustSupport") @@ -51,8 +57,9 @@ public class CliContext { private boolean assumeValidRestReferences = false; @JsonProperty("canDoNative") private boolean canDoNative = false; -// @JsonProperty("noInternalCaching") -// private boolean noInternalCaching = false; // internal, for when debugging terminology validation + // @JsonProperty("noInternalCaching") + // private boolean noInternalCaching = false; // internal, for when debugging + // terminology validation @JsonProperty("noExtensibleBindingMessages") private boolean noExtensibleBindingMessages = false; @JsonProperty("noUnicodeBiDiControlChars") @@ -101,12 +108,12 @@ public class CliContext { // private List igs = new ArrayList(); @JsonProperty("ig") private String ig = null; - + @JsonProperty("questionnaire") private QuestionnaireMode questionnaireMode = QuestionnaireMode.CHECK; @JsonProperty("level") private ValidationLevel level = ValidationLevel.HINTS; - + // @JsonProperty("profiles") // private List profiles = new ArrayList(); // @JsonProperty("sources") @@ -117,19 +124,19 @@ public class CliContext { @JsonProperty("securityChecks") private boolean securityChecks = false; - + @JsonProperty("crumbTrails") private boolean crumbTrails = false; - + @JsonProperty("forPublication") private boolean forPublication = false; - + @JsonProperty("allowExampleUrls") private boolean allowExampleUrls = false; - + // @JsonProperty("showTimes") // private boolean showTimes = false; - + @JsonProperty("locale") private String locale = Locale.ENGLISH.getDisplayLanguage(); @@ -138,7 +145,7 @@ public class CliContext { // @JsonProperty("outputStyle") // private String outputStyle = null; - + // TODO: Mark what goes here? // private List bundleValidationRules = new ArrayList<>(); @@ -159,31 +166,32 @@ public boolean getOnlyOneEngine() { private boolean httpReadOnly = false; - public boolean isHttpReadOnly() { - return this.httpReadOnly; + public boolean isHttpReadOnly() { + return this.httpReadOnly; } @Autowired public CliContext(Environment environment) { - // get al list of all JsonProperty of cliContext with return values property name and property type - List cliContextProperties = getValidateEngineParameters(); + // get al list of all JsonProperty of cliContext with return values property + // name and property type + List cliContextProperties = getValidateEngineParameters(); - // check for each cliContextProperties if it is in the request parameter - for (Field field : cliContextProperties) { - String cliContextProperty = field.getName(); + // check for each cliContextProperties if it is in the request parameter + for (Field field : cliContextProperties) { + String cliContextProperty = field.getName(); String value = environment.getProperty("matchbox.fhir.context." + cliContextProperty); - if (value!=null && value.length()>0) { - try { - if (field.getType() == boolean.class) { - BeanUtils.setProperty(this, cliContextProperty, Boolean.parseBoolean(value)); - } else { - BeanUtils.setProperty(this, cliContextProperty, value); - } - } catch (IllegalAccessException | InvocationTargetException e) { - log.error("error setting property " + cliContextProperty + " to " + value); - } - } - } + if (value != null && value.length() > 0) { + try { + if (field.getType() == boolean.class) { + BeanUtils.setProperty(this, cliContextProperty, Boolean.parseBoolean(value)); + } else { + BeanUtils.setProperty(this, cliContextProperty, value); + } + } catch (IllegalAccessException | InvocationTargetException e) { + log.error("error setting property " + cliContextProperty + " to " + value); + } + } + } // get properties array from the environment? this.igsPreloaded = environment.getProperty("matchbox.fhir.context.igsPreloaded", String[].class); this.onlyOneEngine = environment.getProperty("matchbox.fhir.context.onlyOneEngine", Boolean.class, false); @@ -192,54 +200,53 @@ public CliContext(Environment environment) { public CliContext(CliContext other) { List cliContextProperties = getValidateEngineParameters(); - // check for each cliContextProperties if it is in the request parameter - for (Field field : cliContextProperties) { + // check for each cliContextProperties if it is in the request parameter + for (Field field : cliContextProperties) { try { String value = BeanUtils.getProperty(other, field.getName()); - if (value!=null) { - BeanUtils.setProperty(this, field.getName(), value); - } + if (value != null) { + BeanUtils.setProperty(this, field.getName(), value); + } + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + log.error("error setting property " + field.getName()); } - catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - log.error("error setting property " + field.getName() ); - } - } + } this.igsPreloaded = other.igsPreloaded; this.onlyOneEngine = other.onlyOneEngine; - this.httpReadOnly = other.httpReadOnly; + this.httpReadOnly = other.httpReadOnly; } @JsonProperty("ig") public String getIg() { - return ig; + return ig; } @JsonProperty("igs") public void setIg(String ig) { - this.ig = ig; + this.ig = ig; } // @JsonProperty("igs") // public void setIgs(List igs) { - // this.igs = igs; + // this.igs = igs; // } // @JsonProperty("igs") // public List getIgs() { - // return igs; + // return igs; // } // @JsonProperty("igs") // public void setIgs(List igs) { - // this.igs = igs; + // this.igs = igs; // } // public CliContext addIg(String ig) { - // if (this.igs == null) { - // this.igs = new ArrayList<>(); - // } - // this.igs.add(ig); - // return this; + // if (this.igs == null) { + // this.igs = new ArrayList<>(); + // } + // this.igs.add(ig); + // return this; // } @JsonProperty("questionnaire") @@ -377,7 +384,6 @@ public CliContext addLocation(String profile, String location) { return this; } - @JsonProperty("lang") public String getLang() { return lang; @@ -390,16 +396,26 @@ public void setLang(String lang) { @JsonProperty("snomedCT") public String getSnomedCT() { - if ("intl".equals(snomedCT)) return "900000000000207008"; - if ("us".equals(snomedCT)) return "731000124108"; - if ("uk".equals(snomedCT)) return "999000041000000102"; - if ("au".equals(snomedCT)) return "32506021000036107"; - if ("ca".equals(snomedCT)) return "20611000087101"; - if ("nl".equals(snomedCT)) return "11000146104"; - if ("se".equals(snomedCT)) return "45991000052106"; - if ("es".equals(snomedCT)) return "449081005"; - if ("dk".equals(snomedCT)) return "554471000005108"; - if ("ch".equals(snomedCT)) return "2011000195101"; + if ("intl".equals(snomedCT)) + return "900000000000207008"; + if ("us".equals(snomedCT)) + return "731000124108"; + if ("uk".equals(snomedCT)) + return "999000041000000102"; + if ("au".equals(snomedCT)) + return "32506021000036107"; + if ("ca".equals(snomedCT)) + return "20611000087101"; + if ("nl".equals(snomedCT)) + return "11000146104"; + if ("se".equals(snomedCT)) + return "45991000052106"; + if ("es".equals(snomedCT)) + return "449081005"; + if ("dk".equals(snomedCT)) + return "554471000005108"; + if ("ch".equals(snomedCT)) + return "2011000195101"; return snomedCT; } @@ -440,12 +456,12 @@ public void setAssumeValidRestReferences(boolean assumeValidRestReferences) { // @JsonProperty("noInternalCaching") // public boolean isNoInternalCaching() { - // return noInternalCaching; + // return noInternalCaching; // } // @JsonProperty("noInternalCaching") // public void setNoInternalCaching(boolean noInternalCaching) { - // this.noInternalCaching = noInternalCaching; + // this.noInternalCaching = noInternalCaching; // } @JsonProperty("noExtensibleBindingMessages") @@ -457,7 +473,7 @@ public boolean isNoExtensibleBindingMessages() { public void setNoExtensibleBindingMessages(boolean noExtensibleBindingMessages) { this.noExtensibleBindingMessages = noExtensibleBindingMessages; } - + @JsonProperty("noInvariants") public boolean isNoInvariants() { return noInvariants; @@ -488,12 +504,12 @@ public void setWantInvariantsInMessages(boolean wantInvariantsInMessages) { this.wantInvariantsInMessages = wantInvariantsInMessages; } - @JsonProperty("securityChecks") + @JsonProperty("securityChecks") public boolean isSecurityChecks() { return securityChecks; } - @JsonProperty("securityChecks") + @JsonProperty("securityChecks") public void setSecurityChecks(boolean securityChecks) { this.securityChecks = securityChecks; } @@ -543,125 +559,164 @@ public void setJurisdiction(String jurisdiction) { this.jurisdiction = jurisdiction; } - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (!(o instanceof final CliContext that)) return false; - return doNative == that.doNative - && hintAboutNonMustSupport == that.hintAboutNonMustSupport - && recursive == that.recursive - && showMessagesFromReferences == that.showMessagesFromReferences - && doDebug == that.doDebug - && assumeValidRestReferences == that.assumeValidRestReferences - && canDoNative == that.canDoNative - && noExtensibleBindingMessages == that.noExtensibleBindingMessages - && noUnicodeBiDiControlChars == that.noUnicodeBiDiControlChars - && noInvariants == that.noInvariants - && displayIssuesAreWarnings == that.displayIssuesAreWarnings - && wantInvariantsInMessages == that.wantInvariantsInMessages - && doImplicitFHIRPathStringConversion == that.doImplicitFHIRPathStringConversion - && securityChecks == that.securityChecks - && crumbTrails == that.crumbTrails - && forPublication == that.forPublication - && allowExampleUrls == that.allowExampleUrls - && onlyOneEngine == that.onlyOneEngine - && httpReadOnly == that.httpReadOnly - && htmlInMarkdownCheck == that.htmlInMarkdownCheck - && Objects.equals(txServer, that.txServer) - && Objects.equals(lang, that.lang) - && Objects.equals(snomedCT, that.snomedCT) - && Objects.equals(fhirVersion, that.fhirVersion) - && Objects.equals(ig, that.ig) - && questionnaireMode == that.questionnaireMode - && level == that.level - && mode == that.mode - && Objects.equals(locale, that.locale) - && Objects.equals(locations, that.locations) - && Objects.equals(jurisdiction, that.jurisdiction) - && Arrays.equals(igsPreloaded, that.igsPreloaded); - } - - @Override - public int hashCode() { - int result = Objects.hash(doNative, - hintAboutNonMustSupport, - recursive, - showMessagesFromReferences, - doDebug, - assumeValidRestReferences, - canDoNative, - noExtensibleBindingMessages, - noUnicodeBiDiControlChars, - noInvariants, - displayIssuesAreWarnings, - wantInvariantsInMessages, - doImplicitFHIRPathStringConversion, - htmlInMarkdownCheck, - txServer, - lang, - snomedCT, - fhirVersion, - ig, - questionnaireMode, - level, - mode, - securityChecks, - crumbTrails, - forPublication, - allowExampleUrls, - locale, - locations, - jurisdiction, - onlyOneEngine, - httpReadOnly); - result = 31 * result + Arrays.hashCode(igsPreloaded); - return result; - } - - @Override - public String toString() { - return "CliContext{" + - "doNative=" + doNative + - ", hintAboutNonMustSupport=" + hintAboutNonMustSupport + - ", recursive=" + recursive + - ", showMessagesFromReferences=" + showMessagesFromReferences + - ", doDebug=" + doDebug + - ", assumeValidRestReferences=" + assumeValidRestReferences + - ", canDoNative=" + canDoNative + - ", noExtensibleBindingMessages=" + noExtensibleBindingMessages + - ", noUnicodeBiDiControlChars=" + noUnicodeBiDiControlChars + - ", noInvariants=" + noInvariants + - ", displayIssuesAreWarnings=" + displayIssuesAreWarnings + - ", wantInvariantsInMessages=" + wantInvariantsInMessages + - ", doImplicitFHIRPathStringConversion=" + doImplicitFHIRPathStringConversion + - ", htmlInMarkdownCheck=" + htmlInMarkdownCheck + - ", txServer='" + txServer + '\'' + - ", lang='" + lang + '\'' + - ", snomedCT='" + snomedCT + '\'' + - ", fhirVersion='" + fhirVersion + '\'' + - ", ig='" + ig + '\'' + - ", questionnaireMode=" + questionnaireMode + - ", level=" + level + - ", mode=" + mode + - ", securityChecks=" + securityChecks + - ", crumbTrails=" + crumbTrails + - ", forPublication=" + forPublication + - ", allowExampleUrls=" + allowExampleUrls + - ", locale='" + locale + '\'' + - ", locations=" + locations + - ", jurisdiction='" + jurisdiction + '\'' + - ", igsPreloaded=" + Arrays.toString(igsPreloaded) + - ", onlyOneEngine=" + onlyOneEngine + - ", httpReadOnly=" + httpReadOnly + - '}'; - } - - public List getValidateEngineParameters() { - List cliContextProperties = Arrays.asList(this.getClass().getDeclaredFields()).stream() - .filter(f -> f.isAnnotationPresent(JsonProperty.class)) - .filter(f -> f.getName() != "profile") - .filter(f -> f.getType() == String.class || f.getType() == boolean.class) - .collect(Collectors.toList()); - return cliContextProperties; - } + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (!(o instanceof final CliContext that)) + return false; + return doNative == that.doNative + && hintAboutNonMustSupport == that.hintAboutNonMustSupport + && recursive == that.recursive + && showMessagesFromReferences == that.showMessagesFromReferences + && doDebug == that.doDebug + && assumeValidRestReferences == that.assumeValidRestReferences + && canDoNative == that.canDoNative + && noExtensibleBindingMessages == that.noExtensibleBindingMessages + && noUnicodeBiDiControlChars == that.noUnicodeBiDiControlChars + && noInvariants == that.noInvariants + && displayIssuesAreWarnings == that.displayIssuesAreWarnings + && wantInvariantsInMessages == that.wantInvariantsInMessages + && doImplicitFHIRPathStringConversion == that.doImplicitFHIRPathStringConversion + && securityChecks == that.securityChecks + && crumbTrails == that.crumbTrails + && forPublication == that.forPublication + && allowExampleUrls == that.allowExampleUrls + && onlyOneEngine == that.onlyOneEngine + && httpReadOnly == that.httpReadOnly + && htmlInMarkdownCheck == that.htmlInMarkdownCheck + && Objects.equals(txServer, that.txServer) + && Objects.equals(lang, that.lang) + && Objects.equals(snomedCT, that.snomedCT) + && Objects.equals(fhirVersion, that.fhirVersion) + && Objects.equals(ig, that.ig) + && questionnaireMode == that.questionnaireMode + && level == that.level + && mode == that.mode + && Objects.equals(locale, that.locale) + && Objects.equals(locations, that.locations) + && Objects.equals(jurisdiction, that.jurisdiction) + && Arrays.equals(igsPreloaded, that.igsPreloaded); + } + + @Override + public int hashCode() { + int result = Objects.hash(doNative, + hintAboutNonMustSupport, + recursive, + showMessagesFromReferences, + doDebug, + assumeValidRestReferences, + canDoNative, + noExtensibleBindingMessages, + noUnicodeBiDiControlChars, + noInvariants, + displayIssuesAreWarnings, + wantInvariantsInMessages, + doImplicitFHIRPathStringConversion, + securityChecks, + crumbTrails, + forPublication, + httpReadOnly, + allowExampleUrls, + htmlInMarkdownCheck, + txServer, + lang, + snomedCT, + fhirVersion, + ig, + questionnaireMode, + level, + mode, + locale, + locations, + jurisdiction); + result = 31 * result + Arrays.hashCode(igsPreloaded); + return result; + } + + @Override + public String toString() { + return "CliContext{" + + "doNative=" + doNative + + ", hintAboutNonMustSupport=" + hintAboutNonMustSupport + + ", recursive=" + recursive + + ", showMessagesFromReferences=" + showMessagesFromReferences + + ", doDebug=" + doDebug + + ", assumeValidRestReferences=" + assumeValidRestReferences + + ", canDoNative=" + canDoNative + + ", noExtensibleBindingMessages=" + noExtensibleBindingMessages + + ", noUnicodeBiDiControlChars=" + noUnicodeBiDiControlChars + + ", noInvariants=" + noInvariants + + ", displayIssuesAreWarnings=" + displayIssuesAreWarnings + + ", wantInvariantsInMessages=" + wantInvariantsInMessages + + ", doImplicitFHIRPathStringConversion=" + doImplicitFHIRPathStringConversion + + ", htmlInMarkdownCheck=" + htmlInMarkdownCheck + + ", txServer='" + txServer + '\'' + + ", lang='" + lang + '\'' + + ", snomedCT='" + snomedCT + '\'' + + ", fhirVersion='" + fhirVersion + '\'' + + ", ig='" + ig + '\'' + + ", questionnaireMode=" + questionnaireMode + + ", level=" + level + + ", mode=" + mode + + ", securityChecks=" + securityChecks + + ", crumbTrails=" + crumbTrails + + ", forPublication=" + forPublication + + ", allowExampleUrls=" + allowExampleUrls + + ", locale='" + locale + '\'' + + ", locations=" + locations + + ", jurisdiction='" + jurisdiction + '\'' + + ", igsPreloaded=" + Arrays.toString(igsPreloaded) + + ", onlyOneEngine=" + onlyOneEngine + + ", httpReadOnly=" + httpReadOnly + + '}'; + } + + public List getValidateEngineParameters() { + List cliContextProperties = Arrays.asList(this.getClass().getDeclaredFields()).stream() + .filter(f -> f.isAnnotationPresent(JsonProperty.class)) + .filter(f -> f.getName() != "profile") + .filter(f -> f.getType() == String.class || f.getType() == boolean.class) + .collect(Collectors.toList()); + return cliContextProperties; + } + + public void addContextToExtension(Extension ext) { + + ext.addExtension("ig", new StringType(ig)); + ext.addExtension("hintAboutNonMustSupport", new BooleanType(hintAboutNonMustSupport)); + ext.addExtension("recursive", new BooleanType(recursive)); + + ext.addExtension("showMessagesFromReferences", new BooleanType(showMessagesFromReferences)); + ext.addExtension("doDebug", new BooleanType(doDebug)); + ext.addExtension("assumeValidRestReferences", new BooleanType(assumeValidRestReferences)); + ext.addExtension("canDoNative", new BooleanType(canDoNative)); + ext.addExtension("noExtensibleBindingMessages", new BooleanType(noExtensibleBindingMessages)); + ext.addExtension("noUnicodeBiDiControlChars", new BooleanType(noUnicodeBiDiControlChars)); + ext.addExtension("noInvariants", new BooleanType(noInvariants)); + ext.addExtension("displayIssuesAreWarnings", new BooleanType(displayIssuesAreWarnings)); + ext.addExtension("wantInvariantsInMessages", new BooleanType(wantInvariantsInMessages)); + ext.addExtension("doImplicitFHIRPathStringConversion", new BooleanType(doImplicitFHIRPathStringConversion)); + // ext.addExtension("htmlInMarkdownCheck", new BooleanType(htmlInMarkdownCheck + // == HtmlInMarkdownCheck.ERROR)); + + ext.addExtension("securityChecks", new BooleanType(securityChecks)); + ext.addExtension("crumbTrails", new BooleanType(crumbTrails)); + ext.addExtension("forPublication", new BooleanType(forPublication)); + ext.addExtension("httpReadOnly", new BooleanType(httpReadOnly)); + ext.addExtension("allowExampleUrls", new BooleanType(allowExampleUrls)); + ext.addExtension("txServer", new UriType(txServer)); + ext.addExtension("lang", new StringType(lang)); + ext.addExtension("snomedCT", new BooleanType(snomedCT)); + ext.addExtension("fhirVersion", new StringType(fhirVersion)); + ext.addExtension("ig", new StringType(ig)); +// ext.addExtension("questionnaireMode", new BooleanType(questionnaireMode)); +// ext.addExtension("level", new BooleanType(level)); + // ext.addExtension("mode", new BooleanType(mode)); + ext.addExtension("locale", new StringType(locale)); +// ext.addExtension("locations", new StringType(locations)); + ext.addExtension("jurisdiction", new StringType(jurisdiction)); + + } } diff --git a/matchbox-server/src/main/java/ch/ahdis/matchbox/MatchboxEngineSupport.java b/matchbox-server/src/main/java/ch/ahdis/matchbox/MatchboxEngineSupport.java index d905e88ce65..e57d1a95335 100644 --- a/matchbox-server/src/main/java/ch/ahdis/matchbox/MatchboxEngineSupport.java +++ b/matchbox-server/src/main/java/ch/ahdis/matchbox/MatchboxEngineSupport.java @@ -401,6 +401,10 @@ public MatchboxEngine getMatchboxEngineNotSynchronized(final @Nullable String ca return null; } + public String getSessionId(final MatchboxEngine engine) { + return this.sessionCache.getSessionId(engine); + } + public boolean isInitialized() { return initialized; } diff --git a/matchbox-server/src/main/java/ch/ahdis/matchbox/util/EngineSessionCache.java b/matchbox-server/src/main/java/ch/ahdis/matchbox/util/EngineSessionCache.java index 763229aa23d..cd203f5021d 100644 --- a/matchbox-server/src/main/java/ch/ahdis/matchbox/util/EngineSessionCache.java +++ b/matchbox-server/src/main/java/ch/ahdis/matchbox/util/EngineSessionCache.java @@ -21,10 +21,10 @@ package ch.ahdis.matchbox.util; import java.util.Set; -import java.util.UUID; import java.util.HashSet; import java.util.Map; +import org.apache.commons.collections4.map.PassiveExpiringMap; import org.hl7.fhir.validation.ValidationEngine; import org.hl7.fhir.validation.cli.services.SessionCache; @@ -32,11 +32,17 @@ * @author Oliver Egger * * We want to have a validation engines also that are not timed out as - * in the parent classe. + * in the parent class. */ public class EngineSessionCache extends SessionCache { private final Map cachedSessionsNoTimeout = new java.util.HashMap(); + private final Map cachedSessionIdsNoTimeout = new java.util.HashMap(); + private final PassiveExpiringMap cachedSessionIds; + + public EngineSessionCache() { + cachedSessionIds = new PassiveExpiringMap<>(TIME_TO_LIVE, TIME_UNIT); + } /** * Returns the stored {@link ValidationEngine} associated with the passed in @@ -80,6 +86,13 @@ public Set getSessionIds() { */ public String cacheSessionForEver(String sessionId, ValidationEngine validationEngine) { cachedSessionsNoTimeout.put(sessionId, validationEngine); + cachedSessionIdsNoTimeout.put(validationEngine, sessionId); + return sessionId; + } + + public String cacheSession(String sessionId, ValidationEngine validationEngine) { + String id = super.cacheSession(sessionId, validationEngine); + cachedSessionIds.put(validationEngine, id); return sessionId; } @@ -88,4 +101,14 @@ public boolean sessionExists(String sessionId) { return super.sessionExists(sessionId) || cachedSessionsNoTimeout.containsKey(sessionId); } + public String getSessionId(ValidationEngine validationEngine) { + if (cachedSessionIdsNoTimeout.containsKey(validationEngine)) { + return cachedSessionIdsNoTimeout.get(validationEngine); + } + if (cachedSessionIds.containsKey(validationEngine)) { + return cachedSessionIds.get(validationEngine); + } + return null; + } + } diff --git a/matchbox-server/src/test/java/ch/ahdis/matchbox/test/GenericFhirClient.java b/matchbox-server/src/test/java/ch/ahdis/matchbox/test/GenericFhirClient.java new file mode 100644 index 00000000000..78d6871e34f --- /dev/null +++ b/matchbox-server/src/test/java/ch/ahdis/matchbox/test/GenericFhirClient.java @@ -0,0 +1,163 @@ +package ch.ahdis.matchbox.test; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.client.method.*; +import org.apache.commons.io.IOUtils; +import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import com.google.common.base.Charsets; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.client.impl.BaseHttpClientInvocation; +import ca.uhn.fhir.rest.client.impl.GenericClient; +import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; +import org.hl7.fhir.r4.model.CapabilityStatement; + + +/** + * ValidationClient extends the Generic Client + * @author oliveregger + * + */ +public class GenericFhirClient extends GenericClient{ + + static final public String testServer = "http://localhost:8080/matchboxv3/fhir"; + + public GenericFhirClient(FhirContext theContext, String theServerBase) { + super(theContext, null, theServerBase, null); + setDontValidateConformance(true); + theContext.getRestfulClientFactory().setSocketTimeout(600 * 1000); + } + + public GenericFhirClient(FhirContext theContext) { + this(theContext, testServer); + } + + private final class OutcomeResponseHandler implements IClientResponseHandler { + + private OutcomeResponseHandler() { + super(); + } + + @Override + public MethodOutcome invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map> theHeaders) throws BaseServerResponseException { + MethodOutcome response = MethodUtil.process2xxResponse(getFhirContext(), theResponseStatusCode, theResponseMimeType, theResponseInputStream, theHeaders); + response.setCreatedUsingStatusCode(theResponseStatusCode); + response.setResponseHeaders(theHeaders); + return response; + } + } + + public static BaseHttpClientInvocation createOperationInvocation(FhirContext theContext, String theOperationName, String theInput, Map> urlParams) { + StringBuilder b = new StringBuilder(); + if (b.length() > 0) { + b.append('/'); + } + if (!theOperationName.startsWith("$")) { + b.append("$"); + } + b.append(theOperationName); + BaseHttpClientInvocation.appendExtraParamsWithQuestionMark(urlParams, b, b.indexOf("?") == -1); + return new HttpPostClientInvocation(theContext, theInput, false, b.toString()); + } + + /** + * Performs the $validate operation with a direct POST (see http://hl7.org/fhir/resource-operation-validate.html#examples) + * and the profile specified as a parameter (not the Parameters syntact). + * @param theContents content to validate + * @param theProfile optional: profile to validate against + * @return + */ + public IBaseOperationOutcome validate(String theContents, String theProfile) { + setEncoding(EncodingEnum.detectEncoding(theContents)); + Map> theExtraParams = null; + if (theProfile!=null) { + theExtraParams = new HashMap>(); + List profiles = new ArrayList(); + profiles.add(theProfile); + theExtraParams.put("profile", profiles); + } + OutcomeResponseHandler binding = new OutcomeResponseHandler(); + BaseHttpClientInvocation clientInvoke = createOperationInvocation(getFhirContext(), "$validate", theContents, theExtraParams); + MethodOutcome resp = invokeClient(getFhirContext(), binding, clientInvoke, null, null, false, null, null, null, null, null); + return resp.getOperationOutcome(); + } + + + private String getStructureMapTransformOperation(Map> urlParams) { + StringBuilder b = new StringBuilder(); + b.append("StructureMap/$transform"); + BaseHttpClientInvocation.appendExtraParamsWithQuestionMark(urlParams, b, b.indexOf("?") == -1); + return b.toString(); + } + + public BaseHttpClientInvocation createStructureMapTransformInvocation(FhirContext theContext, String theInput, Map> urlParams) { + return new HttpPostClientInvocation(theContext, theInput, false, getStructureMapTransformOperation(urlParams)); + } + + public BaseHttpClientInvocation createStructureMapTransformInvocation(FhirContext theContext, IBaseResource resource, Map> urlParams) { + return new HttpPostClientInvocation(theContext, resource, getStructureMapTransformOperation(urlParams)); + } + + + /** + * Performs the $transform operation with a direct POST and returning a Resource + * @return + */ + public IBaseResource convert(String theContents, EncodingEnum contentEndoding, String sourceMapUrl, String acceptHeader) { + setEncoding(contentEndoding); + Map> theExtraParams = null; + if (sourceMapUrl!=null) { + theExtraParams = new HashMap>(); + List urls = new ArrayList(); + urls.add(sourceMapUrl); + theExtraParams.put("source", urls); + } + ResourceResponseHandler binding = new ResourceResponseHandler(); + BaseHttpClientInvocation clientInvoke = createStructureMapTransformInvocation(getFhirContext(), theContents, theExtraParams); + return invokeClient(getFhirContext(), binding, clientInvoke, null, null, false, null, null, null, acceptHeader, null); + } + + private final class StringResponseHandler implements IClientResponseHandler { + + @Override + public String invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map> theHeaders) + throws IOException, BaseServerResponseException { + return IOUtils.toString(theResponseInputStream, Charsets.UTF_8); + } + } + + /** + * Performs the $transform operation with a direct POST and returning a Resource + * @return + */ + public String convert(IBaseResource resource, EncodingEnum contentEndoding, String sourceMapUrl, String acceptHeader) { + setEncoding(contentEndoding); + Map> theExtraParams = null; + if (sourceMapUrl!=null) { + theExtraParams = new HashMap>(); + List urls = new ArrayList(); + urls.add(sourceMapUrl); + theExtraParams.put("source", urls); + } + BaseHttpClientInvocation clientInvoke = createStructureMapTransformInvocation(getFhirContext(), resource, theExtraParams); + return invokeClient(getFhirContext(), new StringResponseHandler(), clientInvoke, null, null, false, null, null, null, acceptHeader, null); + } + + public CapabilityStatement retrieveCapabilityStatement() { + IGenericClient client = getFhirContext().newRestfulGenericClient(testServer); + CapabilityStatement capabilityStatement = client.capabilities().ofType(CapabilityStatement.class).execute(); + return capabilityStatement; + } + +} diff --git a/matchbox-server/src/test/java/ch/ahdis/matchbox/test/MatchboxApiTest.java b/matchbox-server/src/test/java/ch/ahdis/matchbox/test/MatchboxApiTest.java new file mode 100644 index 00000000000..94c86457ec8 --- /dev/null +++ b/matchbox-server/src/test/java/ch/ahdis/matchbox/test/MatchboxApiTest.java @@ -0,0 +1,265 @@ +package ch.ahdis.matchbox.test; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.apache.commons.io.FileUtils; +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.instance.model.api.IBaseExtension; +import org.hl7.fhir.instance.model.api.IBaseHasExtensions; +import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; +import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.hl7.fhir.r4.model.OperationOutcome; +import org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity; +import org.hl7.fhir.r4.model.OperationOutcome.OperationOutcomeIssueComponent; +import org.hl7.fhir.r4.model.Parameters; +import org.junit.jupiter.api.BeforeAll; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Collectors; + +import ca.uhn.fhir.context.BaseRuntimeChildDefinition; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.jpa.starter.Application; + +import static org.junit.Assert.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT) +@ContextConfiguration(classes = { Application.class }) +@ActiveProfiles("test") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class MatchboxApiTest { + + static public int getValidationFailures(OperationOutcome outcome) { + int fails = 0; + if (outcome != null && outcome.getIssue() != null) { + for (OperationOutcomeIssueComponent issue : outcome.getIssue()) { + if (IssueSeverity.FATAL == issue.getSeverity()) { + ++fails; + } + if (IssueSeverity.ERROR == issue.getSeverity()) { + ++fails; + } + } + } + return fails; + } + + private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MatchboxApiTest.class); + + private String targetServer = "http://localhost:8081/matchboxv3/fhir"; + + @BeforeAll + void waitUntilStartup() throws Exception { + Path dir = Paths.get("database"); + if (Files.exists(dir)) { + for (Path file : Files.list(dir).collect(Collectors.toList())) { + if (Files.isRegularFile(file)) { + Files.delete(file); + } + } + } + Thread.sleep(10000); // give the server some time to start up + FhirContext contextR4 = FhirVersionEnum.R4.newContext(); + ValidationClient validationClient = new ValidationClient(contextR4, this.targetServer); + validationClient.capabilities(); + } + + private static IBaseExtension getMatchboxValidationExtension(FhirContext theCtx, + IBaseOperationOutcome theOutcome) { + if (theOutcome == null) { + return null; + } + RuntimeResourceDefinition ooDef = theCtx.getResourceDefinition(theOutcome); + BaseRuntimeChildDefinition issueChild = ooDef.getChildByName("issue"); + List issues = issueChild.getAccessor().getValues(theOutcome); + if (issues.isEmpty()) { + return null; + } + IBase issue = issues.get(0); + if (issue instanceof IBaseHasExtensions) { + List> extensions = ((IBaseHasExtensions) issue).getExtension(); + for (IBaseExtension nextSource : extensions) { + if (nextSource.getUrl().equals("http://matchbox.health/validiation")) { + return nextSource; + } + } + } + return null; + } + + public String getSessionId(FhirContext ctx, IBaseOperationOutcome outcome) { + IBaseExtension ext = getMatchboxValidationExtension(ctx, outcome); + List> extensions = (List>) ext.getExtension(); + for (IBaseExtension next : extensions) { + if (next.getUrl().equals("sessionId")) { + IPrimitiveType value = (IPrimitiveType) next.getValue(); + return value.getValueAsString(); + } + } + return null; + } + + public String getIg(FhirContext ctx, IBaseOperationOutcome outcome) { + IBaseExtension ext = getMatchboxValidationExtension(ctx, outcome); + List> extensions = (List>) ext.getExtension(); + for (IBaseExtension next : extensions) { + if (next.getUrl().equals("ig")) { + IPrimitiveType value = (IPrimitiveType) next.getValue(); + return value.getValueAsString(); + } + } + return null; + } + + public String getTxServer(FhirContext ctx, IBaseOperationOutcome outcome) { + IBaseExtension ext = getMatchboxValidationExtension(ctx, outcome); + List> extensions = (List>) ext.getExtension(); + for (IBaseExtension next : extensions) { + if (next.getUrl().equals("txServer")) { + IPrimitiveType value = (IPrimitiveType) next.getValue(); + return value.getValueAsString(); + } + } + return null; + } + + @Test + public void validatePatientRawR4() { + FhirContext contextR4 = FhirVersionEnum.R4.newContext(); + ValidationClient validationClient = new ValidationClient(contextR4, this.targetServer); + + String patient = "\n" + " \n" + + " \n" + " \n" + + "
42
\n" + "
\n" + + "
\n"; + + IBaseOperationOutcome operationOutcome = validationClient.validate(patient, + "http://hl7.org/fhir/StructureDefinition/Patient"); + assertEquals(0, getValidationFailures((OperationOutcome) operationOutcome)); + + String sessionIdFirst = getSessionId(contextR4, operationOutcome); + + operationOutcome = validationClient.validate(patient, + "http://hl7.org/fhir/StructureDefinition/Bundle"); + assertEquals(1, getValidationFailures((OperationOutcome) operationOutcome)); + + String sessionIdF2nd = getSessionId(contextR4, operationOutcome); + assertEquals(sessionIdFirst,sessionIdF2nd); + } + + @Test + public void verifyCachingImplementationGuides() { + FhirContext contextR4 = FhirVersionEnum.R4.newContext(); + ValidationClient validationClient = new ValidationClient(contextR4, this.targetServer); + + String resource = "\n" + // + " \n" + // + " \n" + // + " \n" + // + " \n" + // + ""; + + // tests against base core profile + String profileCore = "http://hl7.org/fhir/StructureDefinition/Practitioner"; + IBaseOperationOutcome operationOutcome = validationClient.validate(resource, profileCore); + String sessionIdCore = getSessionId(contextR4, operationOutcome); + assertEquals(0, getValidationFailures((OperationOutcome) operationOutcome)); + assertEquals("hl7.fhir.r4.core#4.0.1", getIg(contextR4, operationOutcome)); + assertEquals("http://localhost:8081/matchboxv3/fhir", this.getTxServer(contextR4, operationOutcome)); + + // tests against matchbox r4 test ig + String profileMatchbox = "http://matchbox.health/ig/test/r4/StructureDefinition/practitioner-identifier-required"; + operationOutcome = validationClient.validate(resource, profileMatchbox); + String sessionIdMatchbox = getSessionId(contextR4, operationOutcome); + assertEquals(0, getValidationFailures((OperationOutcome) operationOutcome)); + assertEquals("matchbox.health.test.ig.r4#0.1.0", getIg(contextR4, operationOutcome)); + + // verify that we have have different validation engine + assertNotEquals(sessionIdCore, sessionIdMatchbox); + + // check that the cached validation engine of core gets used + operationOutcome = validationClient.validate(resource, profileCore); + assertEquals(0, getValidationFailures((OperationOutcome) operationOutcome)); + String sessionId2Core = getSessionId(contextR4, operationOutcome); + assertEquals(sessionIdCore, sessionId2Core); + + // check that the cached validation engine of matchbox r4 test ig is used again + operationOutcome = validationClient.validate(resource, profileMatchbox); + assertEquals(0, getValidationFailures((OperationOutcome) operationOutcome)); + String sessionId2Matchbox = getSessionId(contextR4, operationOutcome); + assertEquals(sessionIdMatchbox, sessionId2Matchbox); + + // add new parameters should create a new validation engine for matchbox r4 test ig + Parameters parameters = new Parameters(); + parameters.addParameter("txServer", "n/a"); + operationOutcome = validationClient.validate(resource, profileMatchbox, parameters); + assertEquals(0, getValidationFailures((OperationOutcome) operationOutcome)); + String sessionId2MatchboxTxNa = getSessionId(contextR4, operationOutcome); + assertNotEquals(sessionIdMatchbox, sessionId2MatchboxTxNa); + assertEquals("matchbox.health.test.ig.r4#0.1.0", getIg(contextR4, operationOutcome)); + assertEquals("n/a", this.getTxServer(contextR4, operationOutcome)); + + // add new parameters should create a new validation engine for default validation + // operationOutcome = validationClient.validate(resource, profileCore); + // String sessionId3CoreTxNa = getSessionId(contextR4, operationOutcome); + // assertEquals(0, getValidationFailures((OperationOutcome) operationOutcome)); + // assertNotEquals(sessionIdCore, sessionId3CoreTxNa); + } + + @Test + // https://gazelle.ihe.net/jira/browse/EHS-431 + public void validateEhs431() throws IOException { + // + FhirContext contextR4 = FhirVersionEnum.R4.newContext(); + ValidationClient validationClient = new ValidationClient(contextR4, this.targetServer); + + validationClient.capabilities(); + + // IBaseOperationOutcome operationOutcome = + // validationClient.validate(getContent("ehs-431.json"), + // "http://fhir.ch/ig/ch-emed/StructureDefinition/ch-emed-document-medicationcard"); + IBaseOperationOutcome operationOutcome = validationClient.validate(getContent("ehs-431.json"), + "http://hl7.org/fhir/StructureDefinition/Bundle"); + log.debug(contextR4.newJsonParser().encodeResourceToString(operationOutcome)); + assertEquals(1, getValidationFailures((OperationOutcome) operationOutcome)); + } + + @Test + // https://gazelle.ihe.net/jira/browse/EHS-419 + public void validateEhs419() throws IOException { + // + FhirContext contextR4 = FhirVersionEnum.R4.newContext(); + ValidationClient validationClient = new ValidationClient(contextR4, this.targetServer); + + validationClient.capabilities(); + + IBaseOperationOutcome operationOutcome = validationClient.validate(getContent("ehs-419.json"), + "http://hl7.org/fhir/StructureDefinition/Patient"); + log.debug(contextR4.newJsonParser().encodeResourceToString(operationOutcome)); + assertEquals(0, getValidationFailures((OperationOutcome) operationOutcome)); + } + + private String getContent(String resourceName) throws IOException { + Resource resource = new ClassPathResource(resourceName); + File file = resource.getFile(); + return FileUtils.readFileToString(file, StandardCharsets.UTF_8); + } + +} diff --git a/matchbox-server/src/test/java/ch/ahdis/matchbox/test/ValidationClient.java b/matchbox-server/src/test/java/ch/ahdis/matchbox/test/ValidationClient.java new file mode 100644 index 00000000000..83790b40a26 --- /dev/null +++ b/matchbox-server/src/test/java/ch/ahdis/matchbox/test/ValidationClient.java @@ -0,0 +1,138 @@ +package ch.ahdis.matchbox.test; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; +import org.hl7.fhir.instance.model.api.IBaseParameters; +import org.hl7.fhir.instance.model.api.IPrimitiveType; + +import ca.uhn.fhir.context.BaseRuntimeChildDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.client.impl.BaseHttpClientInvocation; +import ca.uhn.fhir.rest.client.impl.GenericClient; +import ca.uhn.fhir.rest.client.method.HttpPostClientInvocation; +import ca.uhn.fhir.rest.client.method.IClientResponseHandler; +import ca.uhn.fhir.rest.client.method.MethodUtil; +import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; +import ca.uhn.fhir.util.ParametersUtil; + + +/** + * ValidationClient extends the GenericClient + * @author oliveregger + * + */ +public class ValidationClient extends GenericClient{ + + + public ValidationClient(FhirContext theContext, String theServerBase) { + super(theContext, null, theServerBase, null); + setDontValidateConformance(true); + theContext.getRestfulClientFactory().setSocketTimeout(600 * 1000); + } + + private final class OutcomeResponseHandler implements IClientResponseHandler { + + private OutcomeResponseHandler() { + super(); + } + + @Override + public MethodOutcome invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map> theHeaders) throws BaseServerResponseException { + MethodOutcome response = MethodUtil.process2xxResponse(getFhirContext(), theResponseStatusCode, theResponseMimeType, theResponseInputStream, theHeaders); + response.setCreatedUsingStatusCode(theResponseStatusCode); + response.setResponseHeaders(theHeaders); + return response; + } + } + + public static BaseHttpClientInvocation createValidationInvocation(FhirContext theContext, String theOperationName, String theInput, Map> urlParams) { + StringBuilder b = new StringBuilder(); + if (b.length() > 0) { + b.append('/'); + } + if (!theOperationName.startsWith("$")) { + b.append("$"); + } + b.append(theOperationName); + BaseHttpClientInvocation.appendExtraParamsWithQuestionMark(urlParams, b, b.indexOf("?") == -1); + return new HttpPostClientInvocation(theContext, theInput, false, b.toString()); + } + + private static Optional> getNameValue( + IBase nextParameter, BaseRuntimeElementCompositeDefinition theNextParameterDef) { + BaseRuntimeChildDefinition nameChild = theNextParameterDef.getChildByName("name"); + List nameValues = nameChild.getAccessor().getValues(nextParameter); + return nameValues.stream() + .filter(t -> t instanceof IPrimitiveType) + .map(t -> ((IPrimitiveType) t)) + .findFirst(); + } + + + private List getParameterNamesFirstLevel(FhirContext theCtx, IBaseParameters theParameters) { + RuntimeResourceDefinition resDef = theCtx.getResourceDefinition(theParameters.getClass()); + BaseRuntimeChildDefinition parameterChild = resDef.getChildByName("parameter"); + List parameterReps = parameterChild.getAccessor().getValues(theParameters); + if (parameterReps!=null) + return parameterReps.stream() + .map(t -> getNameValue(t, (BaseRuntimeElementCompositeDefinition) theCtx.getElementDefinition(t.getClass())).get().getValueAsString()).toList(); + return null; + } + + /** + * Performs the $validate operation with a direct POST (see http://hl7.org/fhir/resource-operation-validate.html#examples) + * and the profile specified as a parameter, additional parameters can be provided + * @param theContents content to validate + * @param theProfile optional: profile to validate against + * @return + */ + public IBaseOperationOutcome validate(String theContents, String theProfile) { + return validate(theContents, theProfile, null); + } + + + /** + * Performs the $validate operation with a direct POST (see http://hl7.org/fhir/resource-operation-validate.html#examples) + * and the profile specified as a parameter, additional parameters can be provided + * @param theContents content to validate + * @param theProfile optional: profile to validate against + * @param parameters optional: additional validation parameters + * @return + */ + public IBaseOperationOutcome validate(String theContents, String theProfile, IBaseParameters parameters) { + setEncoding(EncodingEnum.detectEncoding(theContents)); + Map> theExtraParams = null; + if (theProfile!=null || parameters!=null) { + theExtraParams = new HashMap>(); + if (theProfile != null) { + List profiles = new ArrayList(); + profiles.add(theProfile); + theExtraParams.put("profile", profiles); + } + if (parameters!=null) { + List parametersLevel1 = getParameterNamesFirstLevel(getFhirContext(), parameters); + if (parametersLevel1 != null) { + for (String parameter : parametersLevel1) { + theExtraParams.put(parameter, ParametersUtil.getNamedParameterValuesAsString(getFhirContext(), parameters, parameter)); + } + } + } + } + OutcomeResponseHandler binding = new OutcomeResponseHandler(); + BaseHttpClientInvocation clientInvoke = createValidationInvocation(getFhirContext(), "$validate", theContents, theExtraParams); + MethodOutcome resp = invokeClient(getFhirContext(), binding, clientInvoke, null, null, false, null, null, null, null, null); + return resp.getOperationOutcome(); + } + +} diff --git a/matchbox-server/src/test/resources/application-test.yaml b/matchbox-server/src/test/resources/application-test.yaml new file mode 100644 index 00000000000..2d0a45478e9 --- /dev/null +++ b/matchbox-server/src/test/resources/application-test.yaml @@ -0,0 +1,31 @@ +server: + port: 8081 + servlet: + context-path: /matchboxv3 +hapi: + fhir: + implementationguides: + fhir_r4_core: + name: hl7.fhir.r4.core + version: 4.0.1 + url: classpath:/hl7.fhir.r4.core.tgz + fhir_terminology: + name: hl7.terminology + version: 5.4.0 + url: classpath:/hl7.terminology#5.4.0.tgz + fhir_extensions: + name: hl7.fhir.uv.extensions.r4 + version: 1.0.0 + url: classpath:/hl7.fhir.uv.extensions.r4#1.0.0.tgz + matchbox_health_test_ig_r4: + name: matchbox.health.test.ig.r4 + version: 0.1.0 + url: classpath:/matchbox.health.test.ig.r4-0.1.0.tgz +matchbox: + fhir: + context: + txServer: http://localhost:${server.port}/matchboxv3/fhir + suppressWarnInfo: + hl7.fhir.r4.core#4.0.1: + - "Constraint failed: dom-6:" + - "regex:Entry '(.+)' isn't reachable by traversing forwards from the Composition" \ No newline at end of file diff --git a/matchbox-server/src/test/resources/ehs-419.json b/matchbox-server/src/test/resources/ehs-419.json new file mode 100644 index 00000000000..688392c5108 --- /dev/null +++ b/matchbox-server/src/test/resources/ehs-419.json @@ -0,0 +1,9 @@ +{ + "resourceType": "Patient", + "id": "example", + "language": "de", + "text": { + "status": "generated", + "div": "
42\n
" + } +} \ No newline at end of file diff --git a/matchbox-server/src/test/resources/ehs-431.json b/matchbox-server/src/test/resources/ehs-431.json new file mode 100644 index 00000000000..cec8419f8f7 --- /dev/null +++ b/matchbox-server/src/test/resources/ehs-431.json @@ -0,0 +1,509 @@ +[{ + + "resourceType": "Bundle", + + "id": "171", + + "meta": { + + "versionId": "1", + + "lastUpdated": "2020-09-24T10:24:34.481+02:00", + + "profile": [ + + "http://fhir.ch/ig/ch-emed/StructureDefinition/ch-emed-document-pharmaceuticaladvice" + + ] + + }, + + "identifier": { + + "system": "urn:ietf:rfc:3986", + + "value": "urn:uuid:552647_TST" + + }, + + "type": "document", + + "timestamp": "2020-09-24T10:12:34.000+02:00", + + "entry": [ + + { + + "fullUrl": "http://cistec.com/r4/\tComposition/35740112-9f9b-4258-8943-f682abac5fef_PA_AGL4703276", + + "resource": { + + "resourceType": "Composition", + + "id": "35740112-9f9b-4258-8943-f682abac5fef_PA_AGL4703276", + + "language": "de-CH", + + "text": { + + "status": "generated", + + "div": "
$composition = org.hl7.fhir.r4.model.Composition@46eab393\r\n

Generated Narrative

\r\n

id: $composition.getId()

\r\n

language: $composition.getLanguage()

\r\n

identifier: id: $composition.getIdentifier().getId()

\r\n

status: $composition.getStatus().toCode()

\r\n

type: Medication summary

\r\n

date:

\r\n

author:

\r\n
    \r\n
\r\n

title: Medikationsplan

\r\n

confidentiality: $composition.getConfidentiality().getDisplay()

\r\n

custodian: See $composition.getCustodian().getId()

" + + }, + + "extension": [ + + { + + "url": "http://fhir.ch/ig/ch-core/StructureDefinition/ch-ext-epr-setid", + + "valueIdentifier": { + + "system": "urn:ietf:rfc:3986", + + "value": "urn:uuid:35740112-9f9b-4258-8943-f682abac5fef_PA_AGL4703276" + + } + + }, + + { + + "url": "http://fhir.ch/ig/ch-core/StructureDefinition/ch-ext-epr-versionnumber", + + "valueUnsignedInt": 2 + + }, + + { + + "url": "http://fhir.ch/ig/ch-core/StructureDefinition/ch-ext-epr-informationrecipient", + + "valueReference": { + + "reference": "Patient/TU681" + + } + + } + + ], + + "identifier": { + + "system": "urn:ietf:rfc:3986", + + "value": "urn:uuid:35740112-9f9b-4258-8943-f682abac5fef_PA_AGL4703276" + + }, + + "status": "final", + + "type": { + + "coding": [ + + { + + "system": "http://snomed.info/sct", + + "code": "419891008", + + "display": "Record artifact (record artifact)" + + }, + + { + + "system": "http://loinc.org", + + "code": "61356-2", + + "display": "Medication pharmaceutical advice.extended" + + } + + ] + + }, + + "subject": { + + "reference": "Patient/TU681" + + }, + + "date": "2020-09-24T10:12:34+02:00", + + "author": [ + + { + + "extension": [ + + { + + "url": "http://fhir.ch/ig/ch-core/StructureDefinition/ch-ext-epr-time", + + "valueDateTime": "2020-09-24T10:12:34+02:00" + + } + + ], + + "reference": "Practitioner/CISEESCH" + + }, + + { + + "reference": "Organization/COM" + + } + + ], + + "title": "Kommentar zur Medikation", + + "confidentiality": "N", + + "_confidentiality": { + + "extension": [ + + { + + "url": "http://fhir.ch/ig/ch-core/StructureDefinition/ch-ext-epr-confidentialitycode", + + "valueCodeableConcept": { + + "coding": [ + + { + + "system": "http://snomed.info/sct", + + "code": "17621005", + + "display": "Normally accessible" + + } + + ] + + } + + } + + ] + + }, + + "custodian": { + + "reference": "Organization/COM" + + }, + + "section": [ + + { + + "extension": [ + + { + + "url": "http://fhir.ch/ig/ch-core/StructureDefinition/ch-ext-epr-sectionid", + + "valueIdentifier": { + + "system": "urn:ietf:rfc:3986", + + "value": "urn:uuid:8ed02d0a-2971-11e6-b67b-9e71128cae77" + + } + + } + + ], + + "title": "Hinweise zur Medikation", + + "code": { + + "coding": [ + + { + + "system": "http://loinc.org", + + "code": "61357-0", + + "display": "Medication pharmaceutical advice.brief" + + } + + ] + + }, + + "text": { + + "status": "generated", + + "div": "
TODO add text
" + + }, + + "entry": [ + + { + + "reference": "Observation/AGL4703276" + + } + + ] + + } + + ] + + } + + }, + + { + + "fullUrl": "http://cistec.com/r4/\tPatient/TU681", + + "resource": { + + "resourceType": "Patient", + + "id": "TU681", + + "text": { + + "status": "generated", + + "div": "
Gebhard August ANTONYOVA
IdentifierTU681
Date of birth06 September 1969
" + + }, + + "identifier": [ + + { + + "type": { + + "coding": [ + + { + + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + + "code": "MR" + + } + + ] + + }, + + "system": "http://cistec.com/DEV_COM/Patient", + + "value": "TU681" + + } + + ], + + "name": [ + + { + + "family": "Antonyova", + + "given": [ + + "Gebhard August" + + ] + + } + + ], + + "gender": "male", + + "birthDate": "1969-09-06" + + } + + }, + + { + + "fullUrl": "http://cistec.com/r4/\tPractitioner/CISEESCH", + + "resource": { + + "resourceType": "Practitioner", + + "id": "CISEESCH", + + "identifier": [ + + { + + "system": "http://cistec.com/DEV_COM/Practitioner", + + "value": "CISEESCH" + + }, + + { + + "system": "urn:oid:2.51.1.3", + + "value": "123456789" + + } + + ], + + "name": [ + + { + + "family": "Eschmann", + + "given": [ + + "Emmanuel" + + ] + + } + + ], + + "gender": "unknown" + + } + + }, + + { + + "fullUrl": "http://cistec.com/r4/\tOrganization/COM", + + "resource": { + + "resourceType": "Organization", + + "id": "COM", + + "identifier": [ + + { + + "system": "http://cistec.com/DEV_COM/Organization", + + "value": "2.16.756.5.30.1.163.1.1" + + } + + ], + + "type": [ + + { + + "coding": [ + + { + + "system": "http://snomed.info/sct", + + "code": "22232009", + + "display": "Hospital" + + } + + ] + + } + + ], + + "name": "Cistec COM Spital" + + } + + }, + + { + + "fullUrl": "http://cistec.com/r4/\tObservation/AGL4703276", + + "resource": { + + "resourceType": "Observation", + + "id": "AGL4703276", + + "text": { + + "status": "generated", + + "div": "
TODO add text
" + + }, + + "status": "final", + + "code": { + + "coding": [ + + { + + "system": "urn:oid:1.3.6.1.4.1.19376.1.9.2.1", + + "code": "CANCEL", + + "display": "CANCEL" + + } + + ] + + }, + + "subject": { + + "reference": "Patient/TU681" + + }, + + "note": [ + + { + + "text": "Tabl p.o." + + } + + ] + + } + + } + + ] + +}] diff --git a/matchbox-server/src/test/resources/logback-test.xml b/matchbox-server/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..71985e14b1e --- /dev/null +++ b/matchbox-server/src/test/resources/logback-test.xml @@ -0,0 +1,12 @@ + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] %msg%n + + + + + + + \ No newline at end of file diff --git a/matchbox-server/src/test/resources/matchbox.health.test.ig.r4-0.1.0.tgz b/matchbox-server/src/test/resources/matchbox.health.test.ig.r4-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..9b1fb897c3f7713aea3109d4a74148f80759c1c7 GIT binary patch literal 12697 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 literal 0 HcmV?d00001 diff --git a/matchbox-server/with-cda/application.yaml b/matchbox-server/with-cda/application.yaml index 54765b76a6a..fa25ec48549 100644 --- a/matchbox-server/with-cda/application.yaml +++ b/matchbox-server/with-cda/application.yaml @@ -26,5 +26,4 @@ matchbox: fhir: context: fhirVersion: 4.0.1 - txServer: http://tx.fhir.org - onlyOneEngine: true + txServer: http://tx.fhir.org \ No newline at end of file diff --git a/pom.xml b/pom.xml index a2245d6b643..73f00c73173 100644 --- a/pom.xml +++ b/pom.xml @@ -520,6 +520,10 @@ src/main/resources false
+ + src/test/resources + false + ${project.build.directory}/generated-sources/properties false From 2899ba642315c06b816996fe1aa6669bef924a17 Mon Sep 17 00:00:00 2001 From: oliveregger Date: Mon, 19 Feb 2024 22:30:38 +0100 Subject: [PATCH 2/2] adding test for multiple core engine --- .../ahdis/matchbox/test/MatchboxApiTest.java | 9 ++++---- matchbox-server/validation-fhir-tests.http | 22 ++++++++++++++++--- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/matchbox-server/src/test/java/ch/ahdis/matchbox/test/MatchboxApiTest.java b/matchbox-server/src/test/java/ch/ahdis/matchbox/test/MatchboxApiTest.java index 94c86457ec8..1ac68648f17 100644 --- a/matchbox-server/src/test/java/ch/ahdis/matchbox/test/MatchboxApiTest.java +++ b/matchbox-server/src/test/java/ch/ahdis/matchbox/test/MatchboxApiTest.java @@ -217,10 +217,11 @@ public void verifyCachingImplementationGuides() { assertEquals("n/a", this.getTxServer(contextR4, operationOutcome)); // add new parameters should create a new validation engine for default validation - // operationOutcome = validationClient.validate(resource, profileCore); - // String sessionId3CoreTxNa = getSessionId(contextR4, operationOutcome); - // assertEquals(0, getValidationFailures((OperationOutcome) operationOutcome)); - // assertNotEquals(sessionIdCore, sessionId3CoreTxNa); + operationOutcome = validationClient.validate(resource, profileCore, parameters); + String sessionId3CoreTxNa = getSessionId(contextR4, operationOutcome); + assertEquals(0, getValidationFailures((OperationOutcome) operationOutcome)); + assertNotEquals(sessionIdCore, sessionId3CoreTxNa); + assertEquals("n/a", this.getTxServer(contextR4, operationOutcome)); } @Test diff --git a/matchbox-server/validation-fhir-tests.http b/matchbox-server/validation-fhir-tests.http index 77b51f7e628..f0a6382f3a7 100644 --- a/matchbox-server/validation-fhir-tests.http +++ b/matchbox-server/validation-fhir-tests.http @@ -1,8 +1,8 @@ -@host = https://test.ahdis.ch/matchboxv3/fhir -### @host = http://localhost:8080/matchboxv3/fhir +### @host = https://test.ahdis.ch/matchboxv3/fhir +@host = http://localhost:8081/matchboxv3/fhir ### @host = http://hapi.fhir.org/baseR4 -## No issues detected during validation +### No issues detected during validation POST {{host}}/$validate?profile=http://hl7.org/fhir/StructureDefinition/Basic HTTP/1.1 Accept: application/fhir+json Content-Type: application/fhir+xml @@ -18,6 +18,22 @@ Content-Type: application/fhir+xml +### No issues detected during validation +POST {{host}}/$validate?profile=http://hl7.org/fhir/StructureDefinition/Basic&txServer=n/a HTTP/1.1 +Accept: application/fhir+json +Content-Type: application/fhir+xml + + + + + + + + + + + + ### No issues detected during validation POST {{host}}/$validate?profile=http://fhir.ch/ig/ch-elm/StructureDefinition/ch-elm-document-strict HTTP/1.1 Accept: application/fhir+json