Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 75 - add tests for invoking custom operation framework with query parms #96

Merged
merged 6 commits into from
Sep 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified build/fhir-docker-cleanup.sh
100644 → 100755
Empty file.
Empty file modified build/gen-code.sh
100644 → 100755
Empty file.
Empty file modified build/post-integration-test-docker.sh
100644 → 100755
Empty file.
Empty file modified build/post-integration-test.sh
100644 → 100755
Empty file.
1 change: 1 addition & 0 deletions build/pre-integration-test-docker.sh
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ fi
# Set up installers and config files where docker processing can see them
cd ${WORKSPACE}/fhir-install/docker
./copy-dependencies.sh
./copy-test-operations.sh

# resolve environment variables in the Dockerfile(s).
cat ./Dockerfile-fhirtest.template | envsubst > ./Dockerfile-fhirtest
Expand Down
5 changes: 4 additions & 1 deletion build/pre-integration-test.sh
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ ${SIT}/fhir-server-dist/install.sh ${SIT}

echo "Copying configuration to install location..."
rm -fr ${SIT}/wlp/usr/servers/fhir-server/config/*
cp -pr ${WORKSPACE}/fhir-install/docker/volumes/dist/config/* ${SIT}/wlp/usr/servers/fhir-server/config
cp -pr ${WORKSPACE}/fhir-install/docker/volumes/dist/config/* ${SIT}/wlp/usr/servers/fhir-server/config/

echo "Copying test artifacts to install location..."
cp -pr ${WORKSPACE}/fhir-operation/target/fhir-operation-*-tests.jar ${SIT}/wlp/usr/servers/fhir-server/userlib/

# Start up the fhir server
echo "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ public static void main(String[] args) {
private static final String copyright = "\nFHIR Client Command Line Interface (fhir-cli) (c) Copyright IBM Corporation, 2018, 2019.\n";
private static final String header = "\nProvides access to the FHIR Client API via the command line.\n\nOptions:\n";
private static final String syntax = "fhir-cli [options]";
private static final String CONTENT_LENGTH_HEADER = "Content-Length";
private static final String DEFAULT_MIMETYPE = FHIRMediaType.APPLICATION_FHIR_JSON;

private static PrintStream console = System.err;
Expand Down Expand Up @@ -294,22 +293,7 @@ private void processResponse(InvocationContext ic) throws Exception {
println("");
}

// If the response contains a resource, then display it per user-specified options.
// To check for the presence of a resource in the response, we can't fully trust
// the Content-Length response header or the JAX-RS Response.hasEntity() method.
// The following scenarios are possible:
// 1) Response.hasEntity() could return true even if Content-Length = 0
// 2) Content-Length might be missing
// Result: we'll try to read the resource if Response.hasEntity() returns true,
// AND the Content-Length header is not specified as "0".
// In other words, if Content-Length is missing or set to something other than 0 and
// Response.hasEntity() is true, then we'll try to read the response entity.
String contentLengthStr = jaxrsResponse.getHeaderString(CONTENT_LENGTH_HEADER);
int contentLength = -1;
if (contentLengthStr != null && !contentLengthStr.isEmpty()) {
contentLength = Integer.valueOf(contentLengthStr).intValue();
}
if (jaxrsResponse.hasEntity() && contentLength != 0) {
if (!response.isEmpty()) {
Resource responseObj = response.getResource(Resource.class);
if (responseObj != null) {
Writer writer = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,10 @@ public interface FHIRResponse {
* @throws Exception
*/
String[] parseLocation(String locationString) throws Exception;

/**
* Returns whether the response contains a FHIR Resource entity.
* @return true if the response body is empty, otherwise false
*/
boolean isEmpty();
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@
import java.time.Instant;

import javax.json.JsonObject;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.xml.datatype.XMLGregorianCalendar;

import com.ibm.watson.health.fhir.client.FHIRResponse;
import com.ibm.watson.health.fhir.core.FHIRUtilities;
import com.ibm.watson.health.fhir.model.resource.Resource;

/**
Expand Down Expand Up @@ -136,4 +135,24 @@ public String[] parseLocation(String location) throws Exception {
}
return result;
}

/* (non-Javadoc)
* @see com.ibm.watson.health.fhir.client.FHIRResponse#isEmpty()
*/
@Override
public boolean isEmpty() {
// To check for the presence of a resource in the response, we can't fully trust
// the Content-Length response header or the JAX-RS Response.hasEntity() method.
// The following scenarios are possible:
// 1) Response.hasEntity() could return true even if Content-Length = 0
// 2) Content-Length might be missing
// Return true if either Response.hasEntity() returns false,
// OR the Content-Length header is specified as "0".
String contentLengthStr = response.getHeaderString(HttpHeaders.CONTENT_LENGTH);
int contentLength = -1;
if (contentLengthStr != null && !contentLengthStr.isEmpty()) {
contentLength = Integer.valueOf(contentLengthStr).intValue();
}
return (!response.hasEntity() || contentLength == 0);
}
}
Empty file modified fhir-install/docker/copy-dependencies.sh
100644 → 100755
Empty file.
11 changes: 11 additions & 0 deletions fhir-install/docker/copy-test-operations.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
###############################################################################
# (C) Copyright IBM Corp. 2016, 2019
#
# SPDX-License-Identifier: Apache-2.0
###############################################################################

DIST="volumes/dist"

echo "Copying test artifacts to install location..."
cp -pr ../../fhir-operation/target/fhir-operation-*-tests.jar $DIST/userlib/
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

package com.ibm.watson.health.fhir.operation;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

Expand Down Expand Up @@ -41,6 +40,10 @@ public String getName() {
else return definition.getCode().getValue();
}

/**
* Validate the input parameters, invoke the operation, validate the output parameters, and return the result.
* @throws FHIROperationException if input or output parameters fail validation or an exception occurs
*/
@Override
public final Parameters invoke(FHIROperationContext operationContext, Class<? extends Resource> resourceType, String logicalId, String versionId,
Parameters parameters, FHIRResourceHelpers resourceHelper) throws FHIROperationException {
Expand All @@ -57,6 +60,11 @@ protected int countParameters(Parameters parameters, String name) {
return getParameters(parameters, name).size();
}

/**
* This is the method that concrete subclasses must implement to perform the operation logic.
* @return The Parameters object to return or null if there is no response Parameters object to return
* @throws FHIROperationException
*/
protected abstract Parameters doInvoke(FHIROperationContext operationContext, Class<? extends Resource> resourceType, String logicalId, String versionId,
Parameters parameters, FHIRResourceHelpers resourceHelper) throws FHIROperationException;

Expand All @@ -82,6 +90,10 @@ protected List<OperationDefinition.Parameter> getParameterDefinitions(OperationP

protected List<Parameters.Parameter> getParameters(Parameters parameters, String name) {
List<Parameters.Parameter> result = new ArrayList<Parameters.Parameter>();
if (parameters == null) {
return result;
}

for (Parameters.Parameter parameter : parameters.getParameter()) {
if (parameter.getName() != null && name.equals(parameter.getName().getValue())) {
result.add(parameter);
Expand Down Expand Up @@ -141,7 +153,7 @@ protected void validateOutputParameters(Parameters result) throws FHIROperationE
validateParameters(result, OperationParameterUse.OUT);
}

protected void validateParameters(Parameters parameters, OperationParameterUse use) throws FHIROperationException {
protected void validateParameters(Parameters parameters, OperationParameterUse use) throws FHIROperationException {
String direction = OperationParameterUse.IN.equals(use) ? "input" : "output";

// Retrieve the set of parameters from the OperationDefinition matching the specified use (in/out).
Expand All @@ -158,7 +170,7 @@ protected void validateParameters(Parameters parameters, OperationParameterUse u
throw buildExceptionWithIssue(msg, IssueType.ValueSet.REQUIRED);
}
if (!"*".equals(max)) {
int maxValue = Integer.parseInt(max);
int maxValue = Integer.parseInt(max);
if (count > maxValue) {
String msg = "Number of occurrences of " + direction + " parameter: '" + name + "' greater than allowed maximum: " + maxValue;
throw buildExceptionWithIssue(msg, IssueType.ValueSet.INVALID);
Expand Down Expand Up @@ -199,9 +211,13 @@ protected void validateParameters(Parameters parameters, OperationParameterUse u
}
}

// Next, verify that each parameter contained in the Parameters object is defined
// in the OperationDefinition. This will root out any extaneous parameters included

// Next, if parameters is not null, verify that each parameter contained in the Parameters object is defined
// in the OperationDefinition. This will root out any extaneous parameters included
// in the Parameters object.
if (parameters == null) {
return;
}
for (Parameters.Parameter p : parameters.getParameter()) {
String name = p.getName().getValue();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ public static Parameters getInputParameters(OperationDefinition definition,
Parameters.Builder parametersBuilder = Parameters.builder();
parametersBuilder.id(Id.of("InputParameters"));
if (definition != null) {

for (OperationDefinition.Parameter parameter : definition.getParameter()) {
if (OperationParameterUse.IN.getValue().equals(parameter.getUse().getValue())) {
String name = parameter.getName().getValue();
Expand Down Expand Up @@ -82,6 +81,8 @@ public static Parameters getInputParameters(OperationDefinition definition,
parameterBuilder.value(Code.of(value));
} else if ("oid".equals(typeName)) {
parameterBuilder.value(Oid.of(value));
} else if ("id".equals(typeName)) {
parameterBuilder.value(Id.of(value));
} else if ("unsignedInt".equals(typeName)) {
parameterBuilder.value(UnsignedInt.of(value));
} else if ("positiveInt".equals(typeName)) {
Expand Down Expand Up @@ -144,6 +145,9 @@ public static Parameters getOutputParameters(Resource resource) throws Exception
}

public static boolean hasSingleResourceOutputParameter(Parameters parameters) {
if (parameters == null) {
return false;
}
return parameters.getParameter().size() == 1 &&
"return".equals(parameters.getParameter().get(0).getName().getValue()) &&
parameters.getParameter().get(0).getResource() != null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
/**
* (C) Copyright IBM Corp. 2016,2017,2018,2019
* (C) Copyright IBM Corp. 2016,2019
*
* SPDX-License-Identifier: Apache-2.0
*/

package com.ibm.watson.health.fhir.operation.test;

import static com.ibm.watson.health.fhir.model.type.String.string;

import java.util.Arrays;
import java.util.List;

import com.ibm.watson.health.fhir.exception.FHIROperationException;
import com.ibm.watson.health.fhir.model.resource.OperationDefinition;
import com.ibm.watson.health.fhir.model.resource.Parameters;
import com.ibm.watson.health.fhir.model.resource.Resource;
import com.ibm.watson.health.fhir.model.resource.Parameters.Parameter;
import com.ibm.watson.health.fhir.model.resource.Resource;
import com.ibm.watson.health.fhir.model.type.Boolean;
import com.ibm.watson.health.fhir.model.type.Code;
import com.ibm.watson.health.fhir.model.type.FHIRAllTypes;
Expand All @@ -27,30 +32,67 @@
public class MyOperation extends AbstractOperation {
@Override
protected OperationDefinition buildOperationDefinition() {
OperationDefinition.Builder OperationDefinitionBuilder = OperationDefinition.builder().name(String.of("My Operation")).status(
PublicationStatus.of(PublicationStatus.ValueSet.DRAFT)).kind(OperationKind.of(OperationKind.ValueSet.OPERATION)).code(Code.of("hello")).affectsState(Boolean.of(true)).experimental( Boolean.of(true)).instance(Boolean.of(false));

OperationDefinition.Parameter.Builder inputParameterBuilder = OperationDefinition.Parameter.builder().name(Code.of("input")).use(
OperationParameterUse.OUT).min(Integer.of(1)).id("1");

OperationDefinitionBuilder.parameter(inputParameterBuilder.type(FHIRAllTypes.STRING).build());

OperationDefinition.Parameter.Builder outputParameterBuilder = OperationDefinition.Parameter.builder().name(Code.of("output")).use(
OperationParameterUse.OUT).min(Integer.of(1)).id("1");

OperationDefinitionBuilder.parameter(outputParameterBuilder.type(FHIRAllTypes.STRING).build());
OperationDefinition.Builder operationDefinitionBuilder = OperationDefinition.builder()
.name(String.of("My Operation"))
.status(PublicationStatus.of(PublicationStatus.ValueSet.DRAFT))
.kind(OperationKind.of(OperationKind.ValueSet.OPERATION))
.code(Code.of("hello"))
.affectsState(Boolean.of(true))
.system(Boolean.of(true))
.type(Boolean.of(false))
.experimental(Boolean.of(true))
.instance(Boolean.of(false));

// All primitives except "xhtml" and "base64Binary"
List<FHIRAllTypes> primitives = Arrays.asList(
FHIRAllTypes.BOOLEAN, FHIRAllTypes.CANONICAL, FHIRAllTypes.CODE, FHIRAllTypes.DATE,
FHIRAllTypes.DATE_TIME, FHIRAllTypes.ID, FHIRAllTypes.INSTANT, FHIRAllTypes.INTEGER,
FHIRAllTypes.OID, FHIRAllTypes.POSITIVE_INT, FHIRAllTypes.STRING, FHIRAllTypes.TIME,
FHIRAllTypes.UNSIGNED_INT, FHIRAllTypes.URI, FHIRAllTypes.URL, FHIRAllTypes.UUID);

return OperationDefinitionBuilder.build();
for (FHIRAllTypes primitive : primitives) {
OperationDefinition.Parameter inputParameter = OperationDefinition.Parameter.builder()
.name(Code.of("input-" + primitive.getValue()))
.use(OperationParameterUse.IN)
.min(Integer.of(0))
.max(string("1"))
.type(primitive)
.id("1")
.build();
operationDefinitionBuilder.parameter(inputParameter);

OperationDefinition.Parameter outputParameter = OperationDefinition.Parameter.builder()
.name(Code.of("output-" + primitive.getValue()))
.use(OperationParameterUse.OUT)
.min(Integer.of(0))
.max(string("1"))
.type(primitive)
.id("1")
.build();
operationDefinitionBuilder.parameter(outputParameter);
}

return operationDefinitionBuilder.build();
}

@Override
protected Parameters doInvoke(FHIROperationContext context, Class<? extends Resource> resourceType, java.lang.String logicalId, java.lang.String versionId, Parameters parameters, FHIRResourceHelpers resourceHelper)
throws FHIROperationException {
try {
Parameter inputParameter = parameters.getParameter().get(0);
if (parameters.getParameter().isEmpty()) {
return null;
}

Parameters.Builder returnParametersBuilder = Parameters.builder();

for (Parameter inputParameter : parameters.getParameter()) {
returnParametersBuilder.parameter(Parameter.builder()
.name(string(inputParameter.getName().getValue().replace("input", "output")))
.value(inputParameter.getValue())
.build());
}

return Parameters.builder().parameter(Parameter.builder().name(String.of("output")).value(inputParameter.getValue()).build()).build();
return returnParametersBuilder.build();
} catch (Exception e) {
throw new FHIROperationException("An error occured invoking operation: " + getName(), e);
}
Expand Down
Loading