More work on tx tests, including testing HTTP status code

This commit is contained in:
Grahame Grieve 2024-10-21 22:48:43 +10:30
parent 6f924e4773
commit e521b9f750
17 changed files with 209 additions and 109 deletions

View File

@ -0,0 +1,7 @@
package org.hl7.fhir.r4.profilemodel.gen;
public @interface Definition {
String value();
}

View File

@ -56,14 +56,16 @@ import org.hl7.fhir.r4.model.OperationOutcome;
*/
public class EFhirClientException extends RuntimeException {
private static final long serialVersionUID = 1L;
private int code;
private List<OperationOutcome> errors = new ArrayList<OperationOutcome>();
public EFhirClientException(String message) {
super(message);
}
public EFhirClientException(String message, List<OperationOutcome> serverErrors) {
public EFhirClientException(int code, String message, List<OperationOutcome> serverErrors) {
super(message);
this.code = code;
if (serverErrors != null && serverErrors.size() > 0) {
errors.addAll(serverErrors);
}
@ -73,8 +75,9 @@ public class EFhirClientException extends RuntimeException {
super(cause);
}
public EFhirClientException(String message, Exception cause) {
public EFhirClientException(int code, String message, Exception cause) {
super(message, cause);
this.code = code;
}
/**
@ -85,8 +88,9 @@ public class EFhirClientException extends RuntimeException {
* @param message
* @param serverError
*/
public EFhirClientException(String message, OperationOutcome serverError) {
public EFhirClientException(int code, String message, OperationOutcome serverError) {
super(message);
this.code = code;
if (serverError != null) {
errors.add(serverError);
}
@ -102,9 +106,10 @@ public class EFhirClientException extends RuntimeException {
*
* @param serverError
*/
public EFhirClientException(OperationOutcome serverError) {
public EFhirClientException(int code, OperationOutcome serverError) {
super("Error on the server: " + serverError.getText().getDiv().allText()
+ ". Refer to e.getServerErrors() for additional details.");
this.code = code;
if (serverError != null) {
errors.add(serverError);
}
@ -130,4 +135,8 @@ public class EFhirClientException extends RuntimeException {
return errors.size() > 0;
}
public int getCode() {
return code;
}
}

View File

@ -170,7 +170,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "Read " + resourceClass + "/" + id,
timeoutNormal);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
} catch (Exception e) {
@ -187,7 +187,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "Read " + resourceClass.getName() + "/" + id,
timeoutNormal);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
} catch (Exception e) {
@ -205,7 +205,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(),
"VRead " + resourceClass.getName() + "/" + id + "/?_history/" + version, timeoutNormal);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
} catch (Exception e) {
@ -223,11 +223,11 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "Read " + resourceClass.getName() + "?url=" + canonicalURL,
timeoutNormal);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
} catch (Exception e) {
handleException("An error has occurred while trying to read this version of the resource", e);
handleException(0, "An error has occurred while trying to read this version of the resource", e);
}
Bundle bnd = (Bundle) result.getPayload();
if (bnd.getEntry().size() == 0)
@ -247,11 +247,11 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "Update " + resource.fhirType() + "/" + resource.getId(),
timeoutOperation);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
} catch (Exception e) {
throw new EFhirClientException("An error has occurred while trying to update this resource", e);
throw new EFhirClientException(0, "An error has occurred while trying to update this resource", e);
}
// TODO oe 26.1.2015 could be made nicer if only OperationOutcome locationheader
// is returned with an operationOutcome would be returned (and not the resource
@ -277,11 +277,11 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "Update " + resource.fhirType() + "/" + id,
timeoutOperation);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
} catch (Exception e) {
throw new EFhirClientException("An error has occurred while trying to update this resource", e);
throw new EFhirClientException(0, "An error has occurred while trying to update this resource", e);
}
// TODO oe 26.1.2015 could be made nicer if only OperationOutcome locationheader
// is returned with an operationOutcome would be returned (and not the resource
@ -319,7 +319,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
"GET " + resourceClass.getName() + "/$" + name, timeoutLong);
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
if (result.getPayload() instanceof Parameters) {
@ -339,7 +339,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
ByteUtils.resourceToByteArray(batch, false, isJson(getPreferredResourceFormat()), false),
withVer(getPreferredResourceFormat(), "4.0"), "transaction", timeoutOperation + (timeoutEntry * batch.getEntry().size()));
} catch (Exception e) {
handleException("An error occurred trying to process this transaction request", e);
handleException(0, "An error occurred trying to process this transaction request", e);
}
return transactionResult;
}
@ -354,11 +354,11 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(),
"POST " + resourceClass.getName() + (id != null ? "/" + id : "") + "/$validate", timeoutLong);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
} catch (Exception e) {
handleException("An error has occurred while trying to validate this resource", e);
handleException(0, "An error has occurred while trying to validate this resource", e);
}
return (OperationOutcome) result.getPayload();
}
@ -369,11 +369,11 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
* @param e
* @throws EFhirClientException
*/
protected void handleException(String message, Exception e) throws EFhirClientException {
protected void handleException(int code, String message, Exception e) throws EFhirClientException {
if (e instanceof EFhirClientException) {
throw (EFhirClientException) e;
} else {
throw new EFhirClientException(message, e);
throw new EFhirClientException(code, message, e);
}
}
@ -398,7 +398,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
try {
feed = client.issueGetFeedRequest(new URI(url), withVer(getPreferredResourceFormat(), "4.0"), timeoutLong);
} catch (Exception e) {
handleException("An error has occurred while trying to retrieve history since last update", e);
handleException(0, "An error has occurred while trying to retrieve history since last update", e);
}
return feed;
}
@ -413,7 +413,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
throw new FHIRException(e);
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
return (Parameters) result.getPayload();
@ -430,7 +430,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
throw new FHIRException(e);
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
return (Parameters) result.getPayload();
@ -447,7 +447,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
throw new FHIRException(e);
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
return (Parameters) result.getPayload();
@ -466,17 +466,17 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
generateHeaders(), source == null ? "ValueSet/$expand" : "ValueSet/$expand?url=" + source.getUrl(),
timeoutExpand);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
} catch (EFhirClientException e) {
if (e.getServerErrors().size() > 0) {
throw new EFhirClientException(e.getMessage(), e.getServerErrors().get(0));
throw new EFhirClientException(e.getCode(), e.getMessage(), e.getServerErrors().get(0));
} else {
throw new EFhirClientException(e.getMessage(), e);
throw new EFhirClientException(e.getCode(), e.getMessage(), e);
}
} catch (Exception e) {
throw new EFhirClientException(e.getMessage(), e);
throw new EFhirClientException(0, e.getMessage(), e);
}
return result == null ? null : (ValueSet) result.getPayload();
}
@ -496,7 +496,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()), true),
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "Closure?name=" + name, timeoutNormal);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
} catch (IOException e) {
@ -517,7 +517,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()), true),
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "UpdateClosure?name=" + name, timeoutOperation);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
} catch (IOException e) {
@ -632,7 +632,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
throw new FHIRException(e);
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
return (T) result.getPayload();

View File

@ -260,7 +260,7 @@ public class ResourceAddress {
public static URI buildAbsoluteURI(String absoluteURI) {
if (StringUtils.isBlank(absoluteURI)) {
throw new EFhirClientException("Invalid URI", new URISyntaxException(absoluteURI, "URI/URL cannot be blank"));
throw new EFhirClientException(0, "Invalid URI", new URISyntaxException(absoluteURI, "URI/URL cannot be blank"));
}
String endpoint = appendForwardSlashToPath(absoluteURI);
@ -289,7 +289,7 @@ public class ResourceAddress {
throw new EFhirClientException("host cannot be blank: " + uri);
}
} catch (URISyntaxException e) {
throw new EFhirClientException("Invalid URI", e);
throw new EFhirClientException(0, "Invalid URI", e);
}
return uri;
}
@ -301,7 +301,7 @@ public class ResourceAddress {
uriBuilder.setQuery(parameterName + "=" + parameterValue);
modifiedUri = uriBuilder.build();
} catch (Exception e) {
throw new EFhirClientException(
throw new EFhirClientException(0,
"Unable to append query parameter '" + parameterName + "=" + parameterValue + " to URI " + uri, e);
}
return modifiedUri;
@ -439,7 +439,7 @@ public class ResourceAddress {
return new URI(basePath.getScheme(), basePath.getUserInfo(), basePath.getHost(), basePath.getPort(),
basePath.getPath(), query, basePath.getFragment());
} catch (Exception e) {
throw new EFhirClientException("Error appending http parameter", e);
throw new EFhirClientException(0, "Error appending http parameter", e);
}
}

View File

@ -37,9 +37,9 @@ public class ByteUtils {
try {
baos.close();
} catch (Exception ex) {
throw new EFhirClientException("Error closing output stream", ex);
throw new EFhirClientException(0, "Error closing output stream", ex);
}
throw new EFhirClientException("Error converting output stream to byte array", e);
throw new EFhirClientException(0, "Error converting output stream to byte array", e);
}
return byteArray;
}

View File

@ -314,14 +314,14 @@ public class FhirRequestBuilder {
}
}
} catch (IOException ioe) {
throw new EFhirClientException("Error reading Http Response from "+source+":"+ioe.getMessage(), ioe);
throw new EFhirClientException(code, "Error reading Http Response from "+source+":"+ioe.getMessage(), ioe);
} catch (Exception e) {
throw new EFhirClientException("Error parsing response message from "+source+": "+e.getMessage(), e);
throw new EFhirClientException(code, "Error parsing response message from "+source+": "+e.getMessage(), e);
}
if (resource instanceof OperationOutcome && (!"OperationOutcome".equals(resourceType) || !ok)) {
OperationOutcome error = (OperationOutcome) resource;
if (hasError((OperationOutcome) resource)) {
throw new EFhirClientException("Error from "+source+": " + ResourceUtilities.getErrorDescription(error), error);
throw new EFhirClientException(code, "Error from "+source+": " + ResourceUtilities.getErrorDescription(error), error);
} else {
// umm, weird...
System.out.println("Got OperationOutcome with no error from "+source+" with status "+code);

View File

@ -24,7 +24,6 @@ public class RendererFactory {
case "CompartmentDefinition": return new CompartmentDefinitionRenderer(context);
case "ConceptMap": return new ConceptMapRenderer(context);
case "DiagnosticReport": return new DiagnosticReportRenderer(context);
case "Encounter": return new EncounterRenderer(context);
case "ExampleScenario": return new ExampleScenarioRenderer(context);
case "ImplementationGuide": return new ImplementationGuideRenderer(context);
case "Library": return new LibraryRenderer(context);
@ -87,7 +86,6 @@ public class RendererFactory {
case "CodeSystem": return new CodeSystemRenderer(context);
case "CompartmentDefinition": return new CompartmentDefinitionRenderer(context);
case "ConceptMap": return new ConceptMapRenderer(context);
case "Encounter": return new EncounterRenderer(context);
case "ExampleScenario": return new ExampleScenarioRenderer(context);
case "ImplementationGuide": return new ImplementationGuideRenderer(context);
case "NamingSystem": return new NamingSystemRenderer(context);

View File

@ -303,13 +303,32 @@ public class CompareUtilities extends BaseTestingUtilities {
for (JsonProperty en : expectedJsonObject.getProperties()) {
String n = en.getName();
if (!n.equals("fhir_comments") && !n.equals("$optional$") && !optionals.contains(n)) {
if (!actualJsonObject.has(n))
if (!actualJsonObject.has(n) && !allOptional(en.getValue()))
return "properties differ at " + path + ": missing property " + n;
}
}
return null;
}
private boolean allOptional(JsonElement value) {
if (value.isJsonArray()) {
JsonArray a = value.asJsonArray();
for (JsonElement e : a) {
if (e.isJsonObject()) {
JsonObject o = e.asJsonObject();
if (!o.has("$optional$")) {
return false;
}
} else {
// nothing
}
}
return true;
} else {
return false;
}
}
private List<String> listOptionals(JsonObject expectedJsonObject) {
List<String> res = new ArrayList<>();
if (expectedJsonObject.has("$optional-properties$")) {

View File

@ -58,17 +58,20 @@ import org.hl7.fhir.r5.model.OperationOutcome;
public class EFhirClientException extends RuntimeException {
private static final long serialVersionUID = 1L;
private OperationOutcome error = null;
private int code;
public EFhirClientException(String message) {
public EFhirClientException(int code, String message) {
super(message);
this.code = code;
}
public EFhirClientException(Exception cause) {
super(cause);
}
public EFhirClientException(String message, Exception cause) {
public EFhirClientException(int code, String message, Exception cause) {
super(message, cause);
this.code = code;
}
/**
@ -78,8 +81,9 @@ public class EFhirClientException extends RuntimeException {
* @param message
* @param serverError
*/
public EFhirClientException(String message, OperationOutcome serverError) {
public EFhirClientException(int code, String message, OperationOutcome serverError) {
super(message);
this.code = code;
error = serverError;
}
@ -91,8 +95,9 @@ public class EFhirClientException extends RuntimeException {
* @param message
* @param serverError
*/
public EFhirClientException(String message, OperationOutcome serverError, Exception cause) {
public EFhirClientException(int code, String message, OperationOutcome serverError, Exception cause) {
super(message, cause);
this.code = code;
error = serverError;
}
@ -105,8 +110,9 @@ public class EFhirClientException extends RuntimeException {
*
* @param serverError
*/
public EFhirClientException(OperationOutcome serverError) {
public EFhirClientException(int code, OperationOutcome serverError) {
super("Error on the server: "+serverError.getText().getDiv().allText()+". Refer to e.getServerErrors() for additional details.");
this.code = code;
error = serverError;
}
@ -123,4 +129,8 @@ public class EFhirClientException extends RuntimeException {
return error != null;
}
public int getCode() {
return code;
}
}

View File

@ -217,7 +217,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(false), "Read " + resourceClass + "/" + id,
timeoutNormal);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
} catch (Exception e) {
@ -237,7 +237,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
"Read " + resourceClass.getName() + "/" + id,
timeoutNormal);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
} catch (Exception e) {
throw new FHIRException(e);
@ -255,7 +255,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
"VRead " + resourceClass.getName() + "/" + id + "/?_history/" + version,
timeoutNormal);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
} catch (Exception e) {
throw new FHIRException("Error trying to read this version of the resource", e);
@ -273,16 +273,16 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
"Read " + resourceClass.getName() + "?url=" + canonicalURL,
timeoutNormal);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
} catch (Exception e) {
handleException("An error has occurred while trying to read this version of the resource", e);
handleException(0, "An error has occurred while trying to read this version of the resource", e);
}
Bundle bnd = (Bundle) result.getPayload();
if (bnd.getEntry().size() == 0)
throw new EFhirClientException("No matching resource found for canonical URL '" + canonicalURL + "'");
throw new EFhirClientException(0, "No matching resource found for canonical URL '" + canonicalURL + "'");
if (bnd.getEntry().size() > 1)
throw new EFhirClientException("Multiple matching resources found for canonical URL '" + canonicalURL + "'");
throw new EFhirClientException(0, "Multiple matching resources found for canonical URL '" + canonicalURL + "'");
return (T) bnd.getEntry().get(0).getResource();
}
@ -297,10 +297,10 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
"Update " + resource.fhirType() + "/" + resource.getId(),
timeoutOperation);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
} catch (Exception e) {
throw new EFhirClientException("An error has occurred while trying to update this resource", e);
throw new EFhirClientException(0, "An error has occurred while trying to update this resource", e);
}
// TODO oe 26.1.2015 could be made nicer if only OperationOutcome locationheader is returned with an operationOutcome would be returned (and not the resource also) we make another read
try {
@ -325,10 +325,10 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
"Update " + resource.fhirType() + "/" + id,
timeoutOperation);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
} catch (Exception e) {
throw new EFhirClientException("An error has occurred while trying to update this resource", e);
throw new EFhirClientException(0, "An error has occurred while trying to update this resource", e);
}
// TODO oe 26.1.2015 could be made nicer if only OperationOutcome locationheader is returned with an operationOutcome would be returned (and not the resource also) we make another read
try {
@ -363,7 +363,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
result = client.issueGetResourceRequest(url, withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(false), "GET " + resourceClass.getName() + "/$" + name, timeoutLong);
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
if (result.getPayload() instanceof Parameters) {
return (Parameters) result.getPayload();
@ -373,7 +373,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
return p_out;
}
} catch (Exception e) {
handleException("Error performing tx5 operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e);
handleException(0, "Error performing tx5 operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e);
}
return null;
}
@ -386,7 +386,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
generateHeaders(true),
"transaction", timeoutOperation + (timeoutEntry * batch.getEntry().size()));
} catch (Exception e) {
handleException("An error occurred trying to process this transaction request", e);
handleException(0, "An error occurred trying to process this transaction request", e);
}
return transactionResult;
}
@ -401,10 +401,10 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(true),
"POST " + resourceClass.getName() + (id != null ? "/" + id : "") + "/$validate", timeoutLong);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
} catch (Exception e) {
handleException("An error has occurred while trying to validate this resource", e);
handleException(0, "An error has occurred while trying to validate this resource", e);
}
return (OperationOutcome) result.getPayload();
}
@ -415,11 +415,11 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
* @param e
* @throws EFhirClientException
*/
protected void handleException(String message, Exception e) throws EFhirClientException {
protected void handleException(int code, String message, Exception e) throws EFhirClientException {
if (e instanceof EFhirClientException) {
throw (EFhirClientException) e;
} else {
throw new EFhirClientException(message, e);
throw new EFhirClientException(code, message, e);
}
}
@ -444,7 +444,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
try {
feed = client.issueGetFeedRequest(new URI(url), getPreferredResourceFormat());
} catch (Exception e) {
handleException("An error has occurred while trying to retrieve history since last update", e);
handleException(0, "An error has occurred while trying to retrieve history since last update", e);
}
return feed;
}
@ -465,7 +465,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
throw new FHIRException(e);
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
return result == null ? null : (ValueSet) result.getPayload();
}
@ -483,7 +483,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
e.printStackTrace();
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
return (Parameters) result.getPayload();
}
@ -502,7 +502,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
e.printStackTrace();
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
return (Parameters) result.getPayload();
}
@ -521,7 +521,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
e.printStackTrace();
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
return (Parameters) result.getPayload();
}
@ -543,7 +543,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
"Closure?name=" + name,
timeoutNormal);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
} catch (IOException e) {
e.printStackTrace();
@ -565,7 +565,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
"UpdateClosure?name=" + name,
timeoutOperation);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
} catch (IOException e) {
e.printStackTrace();
@ -694,7 +694,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
throw new FHIRException(e);
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
return (T) result.getPayload();

View File

@ -255,7 +255,7 @@ public class ResourceAddress {
public static URI buildAbsoluteURI(String absoluteURI) {
if(StringUtils.isBlank(absoluteURI)) {
throw new EFhirClientException("Invalid URI", new URISyntaxException(absoluteURI, "URI/URL cannot be blank"));
throw new EFhirClientException(0, "Invalid URI", new URISyntaxException(absoluteURI, "URI/URL cannot be blank"));
}
String endpoint = appendForwardSlashToPath(absoluteURI);
@ -278,13 +278,13 @@ public class ResourceAddress {
String scheme = uri.getScheme();
String host = uri.getHost();
if(!scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https")) {
throw new EFhirClientException("Scheme must be 'http' or 'https': " + uri);
throw new EFhirClientException(0, "Scheme must be 'http' or 'https': " + uri);
}
if(StringUtils.isBlank(host)) {
throw new EFhirClientException("host cannot be blank: " + uri);
throw new EFhirClientException(0, "host cannot be blank: " + uri);
}
} catch(URISyntaxException e) {
throw new EFhirClientException("Invalid URI", e);
throw new EFhirClientException(0, "Invalid URI", e);
}
return uri;
}
@ -296,7 +296,7 @@ public class ResourceAddress {
uriBuilder.setQuery(parameterName + "=" + parameterValue);
modifiedUri = uriBuilder.build();
} catch(Exception e) {
throw new EFhirClientException("Unable to append query parameter '" + parameterName + "=" + parameterValue + " to URI " + uri, e);
throw new EFhirClientException(0, "Unable to append query parameter '" + parameterName + "=" + parameterValue + " to URI " + uri, e);
}
return modifiedUri;
}
@ -431,7 +431,7 @@ public class ResourceAddress {
return new URI(basePath.getScheme(), basePath.getUserInfo(), basePath.getHost(),basePath.getPort(), basePath.getPath(), query, basePath.getFragment());
} catch(Exception e) {
throw new EFhirClientException("Error appending http parameter", e);
throw new EFhirClientException(0, "Error appending http parameter", e);
}
}

View File

@ -38,9 +38,9 @@ public class ByteUtils {
try {
baos.close();
} catch (Exception ex) {
throw new EFhirClientException("Error closing output stream", ex);
throw new EFhirClientException(0, "Error closing output stream", ex);
}
throw new EFhirClientException("Error converting output stream to byte array", e);
throw new EFhirClientException(0, "Error converting output stream to byte array", e);
}
return byteArray;
}

View File

@ -99,7 +99,7 @@ public class Client {
Headers headers,
String message,
long timeout) throws IOException {
if (payload == null) throw new EFhirClientException("PUT requests require a non-null payload");
if (payload == null) throw new EFhirClientException(0, "PUT requests require a non-null payload");
this.payload = payload;
RequestBody body = RequestBody.create(payload);
Request.Builder request = new Request.Builder()
@ -123,7 +123,7 @@ public class Client {
Headers headers,
String message,
long timeout) throws IOException {
if (payload == null) throw new EFhirClientException("POST requests require a non-null payload");
if (payload == null) throw new EFhirClientException(0, "POST requests require a non-null payload");
this.payload = payload;
RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
Request.Builder request = new Request.Builder()
@ -168,7 +168,7 @@ public class Client {
Headers headers,
String message,
int timeout) throws IOException {
if (payload == null) throw new EFhirClientException("POST requests require a non-null payload");
if (payload == null) throw new EFhirClientException(0, "POST requests require a non-null payload");
RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
Request.Builder request = new Request.Builder()
.url(resourceUri.toURL())

View File

@ -255,7 +255,7 @@ public class FhirRequestBuilder {
boolean ok = code >= 200 && code < 300;
if (response.body() == null) {
if (!ok) {
throw new EFhirClientException(response.message());
throw new EFhirClientException(code, response.message());
} else {
return null;
}
@ -307,14 +307,14 @@ public class FhirRequestBuilder {
}
}
} catch (IOException ioe) {
throw new EFhirClientException("Error reading Http Response from "+source+":"+ioe.getMessage(), ioe);
throw new EFhirClientException(0, "Error reading Http Response from "+source+":"+ioe.getMessage(), ioe);
} catch (Exception e) {
throw new EFhirClientException("Error parsing response message from "+source+": "+e.getMessage(), e);
throw new EFhirClientException(0, "Error parsing response message from "+source+": "+e.getMessage(), e);
}
if (resource instanceof OperationOutcome && (!"OperationOutcome".equals(resourceType) || !ok)) {
OperationOutcome error = (OperationOutcome) resource;
if (hasError((OperationOutcome) resource)) {
throw new EFhirClientException("Error from "+source+": " + ResourceUtilities.getErrorDescription(error), error);
throw new EFhirClientException(0, "Error from "+source+": " + ResourceUtilities.getErrorDescription(error), error);
} else {
// umm, weird...
System.out.println("Got OperationOutcome with no error from "+source+" with status "+code);
@ -328,7 +328,7 @@ public class FhirRequestBuilder {
return null; // shouldn't get here?
}
if (resourceType != null && !resource.fhirType().equals(resourceType)) {
throw new EFhirClientException("Error parsing response message from "+source+": Found an "+resource.fhirType()+" looking for a "+resourceType);
throw new EFhirClientException(0, "Error parsing response message from "+source+": Found an "+resource.fhirType()+" looking for a "+resourceType);
}
return (T) resource;
}
@ -359,7 +359,7 @@ public class FhirRequestBuilder {
} else if (mt.getBase().equalsIgnoreCase(ResourceFormat.RESOURCE_XML.getHeader())) {
return new XmlParser();
} else {
throw new EFhirClientException("Invalid format: " + format);
throw new EFhirClientException(0, "Invalid format: " + format);
}
}
}

View File

@ -214,15 +214,15 @@ public class TxTester {
String lang = test.asString("Content-Language");
String msg = null;
if (test.asString("operation").equals("expand")) {
msg = expand(test.str("name"), tx, setup, req, resp, fp, lang, profile, ext);
msg = expand(test.str("name"), tx, setup, req, resp, fp, lang, profile, ext, getResponseCode(test));
} else if (test.asString("operation").equals("validate-code")) {
msg = validate(test.str("name"),tx, setup, req, resp, fp, lang, profile, ext);
msg = validate(test.str("name"),tx, setup, req, resp, fp, lang, profile, ext, getResponseCode(test));
} else if (test.asString("operation").equals("cs-validate-code")) {
msg = validateCS(test.str("name"),tx, setup, req, resp, fp, lang, profile, ext);
msg = validateCS(test.str("name"),tx, setup, req, resp, fp, lang, profile, ext, getResponseCode(test));
} else if (test.asString("operation").equals("lookup")) {
msg = lookup(test.str("name"),tx, setup, req, resp, fp, lang, profile, ext);
msg = lookup(test.str("name"),tx, setup, req, resp, fp, lang, profile, ext, getResponseCode(test));
} else if (test.asString("operation").equals("translate")) {
msg = translate(test.str("name"),tx, setup, req, resp, fp, lang, profile, ext);
msg = translate(test.str("name"),tx, setup, req, resp, fp, lang, profile, ext, getResponseCode(test));
} else {
throw new Exception("Unknown Operation "+test.asString("operation"));
}
@ -250,6 +250,14 @@ public class TxTester {
}
}
private String getResponseCode(JsonObject test) {
if (test.has("http-code")) {
return test.asString("http-code");
} else {
return "2xx";
}
}
private String chooseParam(JsonObject test, String name, List<String> modes) {
for (String mode : modes) {
if (test.has(name+":"+mode)) {
@ -271,23 +279,29 @@ public class TxTester {
return new URI(server).getHost();
}
private String lookup(String id, ITerminologyClient tx, List<Resource> setup, Parameters p, String resp, String fp, String lang, Parameters profile, JsonObject ext) throws IOException {
private String lookup(String id, ITerminologyClient tx, List<Resource> setup, Parameters p, String resp, String fp, String lang, Parameters profile, JsonObject ext, String tcode) throws IOException {
for (Resource r : setup) {
p.addParameter().setName("tx-resource").setResource(r);
}
tx.setContentLanguage(lang);
p.getParameter().addAll(profile.getParameter());
int code = 0;
String pj;
try {
Parameters po = tx.lookupCode(p);
TxTesterScrubbers.scrubParams(po);
TxTesterSorters.sortParameters(po);
pj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(po);
code = 200;
} catch (EFhirClientException e) {
code = e.getCode();
OperationOutcome oo = e.getServerError();
TxTesterScrubbers.scrubOO(oo, tight);
pj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(oo);
}
if (tcode != null && !httpCodeOk(tcode, code)) {
return "Response Code fail: should be '"+tcode+"' but is '"+code+"'";
}
String diff = CompareUtilities.checkJsonSrcIsSame(id, resp, pj, false, ext);
if (diff != null) {
Utilities.createDirectory(Utilities.getDirectoryForFile(fp));
@ -296,23 +310,29 @@ public class TxTester {
return diff;
}
private String translate(String id, ITerminologyClient tx, List<Resource> setup, Parameters p, String resp, String fp, String lang, Parameters profile, JsonObject ext) throws IOException {
private String translate(String id, ITerminologyClient tx, List<Resource> setup, Parameters p, String resp, String fp, String lang, Parameters profile, JsonObject ext, String tcode) throws IOException {
for (Resource r : setup) {
p.addParameter().setName("tx-resource").setResource(r);
}
tx.setContentLanguage(lang);
p.getParameter().addAll(profile.getParameter());
int code = 0;
String pj;
try {
Parameters po = tx.translate(p);
TxTesterScrubbers.scrubParams(po);
TxTesterSorters.sortParameters(po);
pj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(po);
code = 200;
} catch (EFhirClientException e) {
code = e.getCode();
OperationOutcome oo = e.getServerError();
TxTesterScrubbers.scrubOO(oo, tight);
pj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(oo);
}
if (tcode != null && !httpCodeOk(tcode, code)) {
return "Response Code fail: should be '"+tcode+"' but is '"+code+"'";
}
String diff = CompareUtilities.checkJsonSrcIsSame(id, resp, pj, false, ext);
if (diff != null) {
Utilities.createDirectory(Utilities.getDirectoryForFile(fp));
@ -321,23 +341,29 @@ public class TxTester {
return diff;
}
private String expand(String id, ITerminologyClient tx, List<Resource> setup, Parameters p, String resp, String fp, String lang, Parameters profile, JsonObject ext) throws IOException {
private String expand(String id, ITerminologyClient tx, List<Resource> setup, Parameters p, String resp, String fp, String lang, Parameters profile, JsonObject ext, String tcode) throws IOException {
for (Resource r : setup) {
p.addParameter().setName("tx-resource").setResource(r);
}
tx.setContentLanguage(lang);
p.getParameter().addAll(profile.getParameter());
int code = 0;
String vsj;
try {
ValueSet vs = tx.expandValueset(null, p);
TxTesterScrubbers.scrubVS(vs, tight);
TxTesterSorters.sortValueSet(vs);
vsj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(vs);
code = 200;
} catch (EFhirClientException e) {
code = e.getCode();
OperationOutcome oo = e.getServerError();
TxTesterScrubbers.scrubOO(oo, tight);
vsj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(oo);
}
if (tcode != null && !httpCodeOk(tcode, code)) {
return "Response Code fail: should be '"+tcode+"' but is '"+code+"'";
}
String diff = CompareUtilities.checkJsonSrcIsSame(id, resp, vsj, false, ext);
if (diff != null) {
Utilities.createDirectory(Utilities.getDirectoryForFile(fp));
@ -346,23 +372,40 @@ public class TxTester {
return diff;
}
private String validate(String id, ITerminologyClient tx, List<Resource> setup, Parameters p, String resp, String fp, String lang, Parameters profile, JsonObject ext) throws IOException {
private boolean httpCodeOk(String tcode, int code) {
switch (tcode) {
case "2xx" : return code >= 200 && code < 300;
case "3xx" : return code >= 300 && code < 400;
case "4xx" : return code >= 400 && code < 500;
case "5xx" : return code >= 500 && code < 600;
default:
throw new Error("unknown code string "+tcode);
}
}
private String validate(String id, ITerminologyClient tx, List<Resource> setup, Parameters p, String resp, String fp, String lang, Parameters profile, JsonObject ext, String tcode) throws IOException {
for (Resource r : setup) {
p.addParameter().setName("tx-resource").setResource(r);
}
p.getParameter().addAll(profile.getParameter());
tx.setContentLanguage(lang);
int code = 0;
String pj;
try {
Parameters po = tx.validateVS(p);
TxTesterScrubbers.scrubParams(po);
TxTesterSorters.sortParameters(po);
pj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(po);
code = 200;
} catch (EFhirClientException e) {
code = e.getCode();
OperationOutcome oo = e.getServerError();
oo.setText(null);
pj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(oo);
}
if (tcode != null && !httpCodeOk(tcode, code)) {
return "Response Code fail: should be '"+tcode+"' but is '"+code+"'";
}
String diff = CompareUtilities.checkJsonSrcIsSame(id, resp, pj, false, ext);
if (diff != null) {
Utilities.createDirectory(Utilities.getDirectoryForFile(fp));
@ -371,23 +414,29 @@ public class TxTester {
return diff;
}
private String validateCS(String id, ITerminologyClient tx, List<Resource> setup, Parameters p, String resp, String fp, String lang, Parameters profile, JsonObject ext) throws IOException {
private String validateCS(String id, ITerminologyClient tx, List<Resource> setup, Parameters p, String resp, String fp, String lang, Parameters profile, JsonObject ext, String tcode) throws IOException {
for (Resource r : setup) {
p.addParameter().setName("tx-resource").setResource(r);
}
p.getParameter().addAll(profile.getParameter());
tx.setContentLanguage(lang);
int code = 0;
String pj;
try {
Parameters po = tx.validateCS(p);
TxTesterScrubbers.scrubParams(po);
TxTesterSorters.sortParameters(po);
pj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(po);
code = 200;
} catch (EFhirClientException e) {
code = e.getCode();
OperationOutcome oo = e.getServerError();
oo.setText(null);
pj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(oo);
}
if (tcode != null && !httpCodeOk(tcode, code)) {
return "Response Code fail: should be '"+tcode+"' but is '"+code+"'";
}
String diff = CompareUtilities.checkJsonSrcIsSame(id, resp, pj, false, ext);
if (diff != null) {
Utilities.createDirectory(Utilities.getDirectoryForFile(fp));

View File

@ -212,8 +212,8 @@ public class TxTesterSorters {
if (code1 != null && code2 != null && !code1.equals(code2)) {
return code1.compareTo(code2);
}
String v1 = o1.getPart("value") != null && o1.getPart("value").hasPrimitiveValue() ? o1.getPart("value").getValue().primitiveValue().toLowerCase() : null;
String v2 = o2.getPart("value") != null && o2.getPart("value").hasPrimitiveValue() ? o2.getPart("value").getValue().primitiveValue().toLowerCase() : null;
String v1 = o1.getPart("value") != null && o1.getPart("value").getValue().hasPrimitiveValue() ? o1.getPart("value").getValue().primitiveValue().toLowerCase() : null;
String v2 = o2.getPart("value") != null && o2.getPart("value").getValue().hasPrimitiveValue() ? o2.getPart("value").getValue().primitiveValue().toLowerCase() : null;
if (v1 != null && v2 != null && !v1.equals(v2)) {
return v1.compareTo(v2);
}

View File

@ -60,15 +60,20 @@ public class OntoserverTests implements ITxTesterLoader {
public static Iterable<Object[]> data() throws IOException {
String contents = TestingUtilities.loadTestResource("tx", "test-cases.json");
externals = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(TestingUtilities.loadTestResource("tx", "messages-tx.fhir.org.json"));
externals = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(TestingUtilities.loadTestResource("tx", "messages-ontoserver.csiro.au.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));
if (!suite.has("mode") && !suite.asBoolean("disabled")) {
String sn = suite.asString("name");
for (org.hl7.fhir.utilities.json.model.JsonObject test : suite.getJsonObjects("tests")) {
if (!test.has("mode") && !test.asBoolean("disabled")) {
String tn = test.asString("name");
examples.put(sn+"."+tn, new JsonObjectPair(suite, test));
}
}
}
}
@ -107,10 +112,13 @@ public class OntoserverTests implements ITxTesterLoader {
return;
}
if (tester == null) {
tester = new TxTester(this, SERVER, true, externals);
tester = new TxTester(this, SERVER, false, externals);
}
String err = tester.executeTest(setup.suite, setup.test, modes);
Assertions.assertTrue(true); // we don't care what the result is, only that we didn't crash
if (err != null) {
System.out.println(err);
}
Assertions.assertTrue(err == null, err); // we don't care what the result is, only that we didn't crash
}