diff --git a/core/src/main/java/org/opencds/cqf/ruler/utility/Operations.java b/core/src/main/java/org/opencds/cqf/ruler/utility/Operations.java index a0038e9e2..161bf29db 100644 --- a/core/src/main/java/org/opencds/cqf/ruler/utility/Operations.java +++ b/core/src/main/java/org/opencds/cqf/ruler/utility/Operations.java @@ -2,13 +2,12 @@ import static com.google.common.base.Preconditions.checkArgument; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; import java.util.List; -import java.util.TimeZone; +import java.util.Optional; import java.util.regex.Pattern; +import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.param.DateRangeParam; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -23,61 +22,16 @@ private Operations() { } public static final Pattern PATIENT_OR_GROUP_REFERENCE = Pattern - .compile("(Patient|Group)\\/[A-Za-z0-9\\-\\.]{1,64}"); + .compile("(Patient|Group)/[A-Za-z\\d\\-.]{1,64}"); public static final Pattern PRACTITIONER_REFERENCE = Pattern - .compile("Practitioner\\/[A-Za-z0-9\\-\\.]{1,64}"); + .compile("Practitioner/[A-Za-z\\d\\-.]{1,64}"); public static final Pattern ORGANIZATION_REFERENCE = Pattern - .compile("Organization\\/[A-Za-z0-9\\-\\.]{1,64}"); + .compile("Organization/[A-Za-z\\d\\-.]{1,64}"); public static final Pattern FHIR_DATE = Pattern - .compile("-?[0-9]{4}(-(0[1-9]|1[0-2])(-(0[0-9]|[1-2][0-9]|3[0-1]))?)?"); - - /** - * This function converts a string representation of a FHIR period date to a - * java.util.Date. - * - * @param date the date to convert - * @param start whether the date is the start of a period - * @return the FHIR period date as a java.util.Date type - */ - public static Date resolveRequestDate(String date, boolean start) { - // split it up - support dashes or slashes - String[] dissect = date.contains("-") ? date.split("-") : date.split("/"); - List dateVals = new ArrayList<>(); - for (String dateElement : dissect) { - dateVals.add(Integer.parseInt(dateElement)); - } - - if (dateVals.isEmpty()) { - throw new IllegalArgumentException("Invalid date"); - } - - // for now support dates up to day precision - Calendar calendar = Calendar.getInstance(); - calendar.clear(); - calendar.setTimeZone(TimeZone.getDefault()); - calendar.set(Calendar.YEAR, dateVals.get(0)); - if (dateVals.size() > 1) { - // java.util.Date months are zero based, hence the negative 1 -- 2014-01 == - // February 2014 - calendar.set(Calendar.MONTH, dateVals.get(1) - 1); - } - if (dateVals.size() > 2) - calendar.set(Calendar.DAY_OF_MONTH, dateVals.get(2)); - else { - if (start) { - calendar.set(Calendar.DAY_OF_MONTH, 1); - } else { - // get last day of month for end period - calendar.add(Calendar.MONTH, 1); - calendar.set(Calendar.DAY_OF_MONTH, 1); - calendar.add(Calendar.DATE, -1); - } - } - return calendar.getTime(); - } + .compile("-?\\d{4}(-(0[1-9]|1[0-2])(-(0\\d|[1-2]\\d|3[0-1]))?)?"); /** * This function returns a fullUrl for a resource. @@ -126,7 +80,40 @@ public static void validateDate(String theParameter, String theValue) { */ public static void validateSingularDate(RequestDetails theRequestDetails, String theParameter) { validateCardinality(theRequestDetails, theParameter, 1, 1); - validateDate(theParameter, theRequestDetails.getParameters().get(theParameter)[0]); + if (theRequestDetails.getRequestType() == RequestTypeEnum.GET) { + validateDate(theParameter, theRequestDetails.getParameters().get(theParameter)[0]); + } + else { + Optional theValue = Parameters.getSingularStringPart( + theRequestDetails.getFhirContext(), theRequestDetails.getResource(), theParameter); + theValue.ifPresent(s -> validateDate(theParameter, s)); + } + } + + /** + * This function returns the number of input parameters for a given request. + * + * @param requestDetails metadata about the current request being processed. + * Generally auto-populated by the HAPI FHIR server + * framework. + * @param parameter the name of the parameter + * @return the number of input parameters as an integer + */ + public static int getNumberOfParametersFromRequest(RequestDetails requestDetails, String parameter) { + if (requestDetails.getRequestType() == RequestTypeEnum.POST) { + List parameterList = Parameters.getPartsByName(requestDetails.getFhirContext(), requestDetails.getResource(), parameter); + if (parameterList == null) { + return 0; + } + return parameterList.size(); + } + else { + String[] parameterArr = requestDetails.getParameters().get(parameter); + if (parameterArr == null) { + return 0; + } + return parameterArr.length; + } } /** @@ -144,8 +131,7 @@ public static void validateSingularDate(RequestDetails theRequestDetails, String */ public static void validateCardinality(RequestDetails theRequestDetails, String theParameter, int min, int max) { validateCardinality(theRequestDetails, theParameter, min); - String[] potentialValue = theRequestDetails.getParameters().get(theParameter); - checkArgument(potentialValue == null || potentialValue.length <= max, + checkArgument(getNumberOfParametersFromRequest(theRequestDetails, theParameter) <= max, "Parameter '%s' must be provided a maximum of %s time(s).", theParameter, String.valueOf(max)); } @@ -165,8 +151,7 @@ public static void validateCardinality(RequestDetails theRequestDetails, String if (min <= 0) { return; } - String[] potentialValue = theRequestDetails.getParameters().get(theParameter); - checkArgument(potentialValue != null && potentialValue.length >= min, + checkArgument(getNumberOfParametersFromRequest(theRequestDetails, theParameter) >= min, "Parameter '%s' must be provided a minimum of %s time(s).", theParameter, String.valueOf(min)); } @@ -186,13 +171,21 @@ public static void validateCardinality(RequestDetails theRequestDetails, String */ public static void validatePeriod(RequestDetails theRequestDetails, String theStartParameter, String theEndParameter) { - validateSingularDate(theRequestDetails, theStartParameter); - validateSingularDate(theRequestDetails, theEndParameter); - checkArgument( - Operations.resolveRequestDate(theRequestDetails.getParameters().get(theStartParameter)[0], true) - .before(Operations.resolveRequestDate(theRequestDetails.getParameters().get(theEndParameter)[0], - false)), - "Parameter '%s' must be before parameter '%s'.", theStartParameter, theEndParameter); + validateCardinality(theRequestDetails, theStartParameter, 1, 1); + validateCardinality(theRequestDetails, theEndParameter, 1, 1); + if (theRequestDetails.getRequestType() == RequestTypeEnum.GET) { + new DateRangeParam(theRequestDetails.getParameters().get(theStartParameter)[0], + theRequestDetails.getParameters().get(theEndParameter)[0]); + } + else { + Optional start = Parameters.getSingularStringPart( + theRequestDetails.getFhirContext(), theRequestDetails.getResource(), theStartParameter); + Optional end = Parameters.getSingularStringPart( + theRequestDetails.getFhirContext(), theRequestDetails.getResource(), theEndParameter); + if (start.isPresent() && end.isPresent()) { + new DateRangeParam(start.get(), end.get()); + } + } } /** @@ -223,11 +216,16 @@ public static void validatePattern(String theParameter, String theValue, Pattern */ public static void validateSingularPattern(RequestDetails theRequestDetails, String theParameter, Pattern thePattern) { - String[] potentialValue = theRequestDetails.getParameters().get(theParameter); - if (potentialValue == null || potentialValue.length == 0) { + if (getNumberOfParametersFromRequest(theRequestDetails, theParameter) == 0) { return; } - validatePattern(theParameter, potentialValue[0], thePattern); + if (theRequestDetails.getRequestType() == RequestTypeEnum.GET) { + validatePattern(theParameter, theRequestDetails.getParameters().get(theParameter)[0], thePattern); + } + else { + Optional theValue = Parameters.getSingularStringPart(theRequestDetails.getFhirContext(), theRequestDetails.getResource(), theParameter); + theValue.ifPresent(s -> validatePattern(theParameter, s, thePattern)); + } } /** @@ -246,18 +244,12 @@ public static void validateSingularPattern(RequestDetails theRequestDetails, Str */ public static void validateExclusive(RequestDetails theRequestDetails, String theParameter, String... theExcludedParameters) { - String[] potentialValue = theRequestDetails.getParameters().get(theParameter); - if (potentialValue == null || potentialValue.length == 0) { + if (getNumberOfParametersFromRequest(theRequestDetails, theParameter) == 0) { return; } - String[] potentialExcludedValue = null; for (String excludedParameter : theExcludedParameters) { - potentialExcludedValue = theRequestDetails.getParameters().get(excludedParameter); - if (potentialExcludedValue == null) { - continue; - } - checkArgument(potentialExcludedValue.length <= 0, - "Parameter '%s' cannot be included with parameter '%s'.", excludedParameter, theParameter); + checkArgument(getNumberOfParametersFromRequest(theRequestDetails, excludedParameter) <= 0, + "Parameter '%s' cannot be included with parameter '%s'.", excludedParameter, theParameter); } } @@ -277,15 +269,12 @@ public static void validateExclusive(RequestDetails theRequestDetails, String th */ public static void validateInclusive(RequestDetails theRequestDetails, String theParameter, String... theIncludedParameters) { - String[] potentialValue = theRequestDetails.getParameters().get(theParameter); - if (potentialValue == null || potentialValue.length == 0) { + if (getNumberOfParametersFromRequest(theRequestDetails, theParameter) == 0) { return; } for (String includedParameter : theIncludedParameters) { - checkArgument( - theRequestDetails.getParameters().get(includedParameter) != null - && theRequestDetails.getParameters().get(includedParameter).length > 0, - "Parameter '%s' must be included with parameter '%s'.", includedParameter, theParameter); + checkArgument(getNumberOfParametersFromRequest(theRequestDetails, includedParameter) > 0, + "Parameter '%s' must be included with parameter '%s'.", includedParameter, theParameter); } } @@ -304,11 +293,9 @@ public static void validateInclusive(RequestDetails theRequestDetails, String th */ public static void validateExclusiveOr(RequestDetails theRequestDetails, String theLeftParameter, String theRightParameter) { - String[] potentialLeftValue = theRequestDetails.getParameters().get(theLeftParameter); - String[] potentialRightValue = theRequestDetails.getParameters().get(theRightParameter); checkArgument( - (potentialLeftValue != null && potentialLeftValue.length > 0) - ^ (potentialRightValue != null && potentialRightValue.length > 0), + (getNumberOfParametersFromRequest(theRequestDetails, theLeftParameter) > 0) + ^ (getNumberOfParametersFromRequest(theRequestDetails, theRightParameter) > 0), "Either one of parameter '%s' or parameter '%s' must be included, but not both.", theLeftParameter, theRightParameter); } @@ -325,20 +312,14 @@ public static void validateExclusiveOr(RequestDetails theRequestDetails, String * @param theParameters the set of parameters to validate */ public static void validateAtLeastOne(RequestDetails theRequestDetails, String... theParameters) { - String[] potentialValue = null; for (String includedParameter : theParameters) { - potentialValue = theRequestDetails.getParameters().get(includedParameter); - if (potentialValue == null) { - continue; - } - if (potentialValue.length > 0) { + if (getNumberOfParametersFromRequest(theRequestDetails, includedParameter) > 0) { return; } } throw new IllegalArgumentException(String .format("At least one of the following parameters must be included: %s.", StringUtils.join(theParameters, ", "))); - } } diff --git a/core/src/main/java/org/opencds/cqf/ruler/utility/Parameters.java b/core/src/main/java/org/opencds/cqf/ruler/utility/Parameters.java index 323a8a97e..f237db150 100644 --- a/core/src/main/java/org/opencds/cqf/ruler/utility/Parameters.java +++ b/core/src/main/java/org/opencds/cqf/ruler/utility/Parameters.java @@ -12,182 +12,189 @@ import java.util.List; import java.util.Objects; +import java.util.Optional; import static com.google.common.base.Preconditions.checkNotNull; public class Parameters { - private Parameters() { - } - - private static BaseRuntimeChildDefinition getParameterChild(FhirContext fhirContext) { - return fhirContext.getResourceDefinition("Parameters").getChildByName("parameter"); - } - - private static BaseRuntimeElementDefinition getParameterElement(FhirContext fhirContext) { - return getParameterChild(fhirContext).getChildByName("parameter"); - } - - private static BaseRuntimeChildDefinition.IMutator getValueMutator(FhirContext fhirContext) { - return getParameterElement(fhirContext) - .getChildByName("value[x]").getMutator(); - } - - private static void validateNameAndValue(String name, Object value) { - checkNotNull(name); - checkNotNull(value); - } - - public static IBaseParameters newParameters(FhirContext fhirContext, IIdType theId, IBase... parts) { - checkNotNull(theId); - IBaseParameters newParameters = ParametersUtil.newInstance(fhirContext); - newParameters.setId(theId); - BaseRuntimeChildDefinition.IMutator mutator = getParameterChild(fhirContext).getMutator(); - for (IBase part : parts) { - mutator.addValue(newParameters, part); - } - return newParameters; - } - - public static IBaseParameters newParameters(FhirContext fhirContext, String theId, IBase... parts) { - checkNotNull(theId); - IIdType id = (IIdType) Objects.requireNonNull(fhirContext.getElementDefinition("id")).newInstance(); - id.setValue(theId); - return newParameters(fhirContext, id, parts); - } - - public static IBaseParameters newParameters(FhirContext fhirContext, IBase... parts) { - IBaseParameters newParameters = ParametersUtil.newInstance(fhirContext); - BaseRuntimeChildDefinition.IMutator mutator = getParameterChild(fhirContext).getMutator(); - for (IBase part : parts) { - mutator.addValue(newParameters, part); - } - return newParameters; - } - - public static IBase newPart(FhirContext fhirContext, String name, IBase... parts) { - checkNotNull(name); - BaseRuntimeChildDefinition.IMutator nameMutator = getParameterElement(fhirContext) - .getChildByName("name").getMutator(); - BaseRuntimeChildDefinition.IMutator partMutator = getParameterElement(fhirContext) - .getChildByName("part").getMutator(); - IBase parameterBase = getParameterElement(fhirContext).newInstance(); - IBase theName = Objects.requireNonNull(fhirContext.getElementDefinition("string")).newInstance(name); - nameMutator.setValue(parameterBase, theName); - for (IBase part : parts) { - partMutator.addValue(parameterBase, part); - } - return parameterBase; - } - - public static IBase newPart(FhirContext fhirContext, Class type, - String name, Object value, IBase... parts) { - validateNameAndValue(name, value); - IBase newPpc = newPart(fhirContext, name, parts); - IBase typeValue = Objects.requireNonNull(fhirContext.getElementDefinition(type)).newInstance(value); - getValueMutator(fhirContext).setValue(newPpc, typeValue); - return newPpc; - } - - public static IBase newPart(FhirContext fhirContext, String typeName, - String name, Object value, IBase... parts) { - validateNameAndValue(name, value); - IBase newPpc = newPart(fhirContext, name, parts); - IBase typeValue = Objects.requireNonNull(fhirContext.getElementDefinition(typeName)).newInstance(value.toString()); - getValueMutator(fhirContext).setValue(newPpc, typeValue); - return newPpc; - } - - public static IBase newPart(FhirContext fhirContext, String name, IBaseResource value, IBase... parts) { - validateNameAndValue(name, value); - IBase newPpc = newPart(fhirContext, name, parts); - getParameterElement(fhirContext).getChildByName("resource").getMutator().setValue(newPpc, value); - return newPpc; - } - - public static List getPartsByName(FhirContext fhirContext, IBaseResource parameters, String name) { - checkNotNull(parameters); - checkNotNull(name); - return ParametersUtil.getNamedParameters(fhirContext, parameters, name); - } - - public static IBase newBase64BinaryPart(FhirContext fhirContext, String name, String value, IBase... parts) { - return newPart(fhirContext, "base64binary", name, value, parts); - } - - public static IBase newBooleanPart(FhirContext fhirContext, String name, boolean value, IBase... parts) { - return newPart(fhirContext, "boolean", name, value, parts); - } - - public static IBase newCanonicalPart(FhirContext fhirContext, String name, String value, IBase... parts) { - return newPart(fhirContext, "canonical", name, value, parts); - } - - public static IBase newCodePart(FhirContext fhirContext, String name, String value, IBase... parts) { - return newPart(fhirContext, "code", name, value, parts); - } - - public static IBase newDatePart(FhirContext fhirContext, String name, String value, IBase... parts) { - return newPart(fhirContext, "date", name, value, parts); - } - - public static IBase newDateTimePart(FhirContext fhirContext, String name, String value, IBase... parts) { - return newPart(fhirContext, "datetime", name, value, parts); - } - - public static IBase newDecimalPart(FhirContext fhirContext, String name, double value, IBase... parts) { - return newPart(fhirContext, "decimal", name, value, parts); - } - - public static IBase newIdPart(FhirContext fhirContext, String name, String value, IBase... parts) { - return newPart(fhirContext, "id", name, value, parts); - } - - public static IBase newInstantPart(FhirContext fhirContext, String name, String value, IBase... parts) { - return newPart(fhirContext, "instant", name, value, parts); - } - - public static IBase newIntegerPart(FhirContext fhirContext, String name, int value, IBase... parts) { - return newPart(fhirContext, "integer", name, value, parts); - } - - public static IBase newInteger64Part(FhirContext fhirContext, String name, long value, IBase... parts) { - return newPart(fhirContext, "integer64", name, value, parts); - } - - public static IBase newMarkdownPart(FhirContext fhirContext, String name, String value, IBase... parts) { - return newPart(fhirContext, "markdown", name, value, parts); - } - - public static IBase newOidPart(FhirContext fhirContext, String name, String value, IBase... parts) { - return newPart(fhirContext, "oid", name, value, parts); - } - - public static IBase newPositiveIntPart(FhirContext fhirContext, String name, int value, IBase... parts) { - return newPart(fhirContext, "positiveint", name, value, parts); - } - - public static IBase newStringPart(FhirContext fhirContext, String name, String value, IBase... parts) { - return newPart(fhirContext, "string", name, value, parts); - } - - public static IBase newTimePart(FhirContext fhirContext, String name, String value, IBase... parts) { - return newPart(fhirContext, "time", name, value, parts); - } - - public static IBase newUnsignedIntPart(FhirContext fhirContext, String name, int value, IBase... parts) { - return newPart(fhirContext, "unsignedint", name, value, parts); - } - - public static IBase newUriPart(FhirContext fhirContext, String name, String value, IBase... parts) { - return newPart(fhirContext, "uri", name, value, parts); - } - - public static IBase newUrlPart(FhirContext fhirContext, String name, String value, IBase... parts) { - return newPart(fhirContext, "url", name, value, parts); - } - - public static IBase newUuidPart(FhirContext fhirContext, String name, String value, IBase... parts) { - return newPart(fhirContext, "uuid", name, value, parts); - } + private Parameters() { + } + + private static BaseRuntimeChildDefinition getParameterChild(FhirContext fhirContext) { + return fhirContext.getResourceDefinition("Parameters").getChildByName("parameter"); + } + + private static BaseRuntimeElementDefinition getParameterElement(FhirContext fhirContext) { + return getParameterChild(fhirContext).getChildByName("parameter"); + } + + private static BaseRuntimeChildDefinition.IMutator getValueMutator(FhirContext fhirContext) { + return getParameterElement(fhirContext) + .getChildByName("value[x]").getMutator(); + } + + private static void validateNameAndValue(String name, Object value) { + checkNotNull(name); + checkNotNull(value); + } + + public static IBaseParameters newParameters(FhirContext fhirContext, IIdType theId, IBase... parts) { + checkNotNull(theId); + IBaseParameters newParameters = ParametersUtil.newInstance(fhirContext); + newParameters.setId(theId); + BaseRuntimeChildDefinition.IMutator mutator = getParameterChild(fhirContext).getMutator(); + for (IBase part : parts) { + mutator.addValue(newParameters, part); + } + return newParameters; + } + + public static IBaseParameters newParameters(FhirContext fhirContext, String theId, IBase... parts) { + checkNotNull(theId); + IIdType id = (IIdType) Objects.requireNonNull(fhirContext.getElementDefinition("id")).newInstance(); + id.setValue(theId); + return newParameters(fhirContext, id, parts); + } + + public static IBaseParameters newParameters(FhirContext fhirContext, IBase... parts) { + IBaseParameters newParameters = ParametersUtil.newInstance(fhirContext); + BaseRuntimeChildDefinition.IMutator mutator = getParameterChild(fhirContext).getMutator(); + for (IBase part : parts) { + mutator.addValue(newParameters, part); + } + return newParameters; + } + + public static IBase newPart(FhirContext fhirContext, String name, IBase... parts) { + checkNotNull(name); + BaseRuntimeChildDefinition.IMutator nameMutator = getParameterElement(fhirContext) + .getChildByName("name").getMutator(); + BaseRuntimeChildDefinition.IMutator partMutator = getParameterElement(fhirContext) + .getChildByName("part").getMutator(); + IBase parameterBase = getParameterElement(fhirContext).newInstance(); + IBase theName = Objects.requireNonNull(fhirContext.getElementDefinition("string")).newInstance(name); + nameMutator.setValue(parameterBase, theName); + for (IBase part : parts) { + partMutator.addValue(parameterBase, part); + } + return parameterBase; + } + + public static IBase newPart(FhirContext fhirContext, Class type, + String name, Object value, IBase... parts) { + validateNameAndValue(name, value); + IBase newPpc = newPart(fhirContext, name, parts); + IBase typeValue = Objects.requireNonNull(fhirContext.getElementDefinition(type)).newInstance(value); + getValueMutator(fhirContext).setValue(newPpc, typeValue); + return newPpc; + } + + public static IBase newPart(FhirContext fhirContext, String typeName, + String name, Object value, IBase... parts) { + validateNameAndValue(name, value); + IBase newPpc = newPart(fhirContext, name, parts); + IBase typeValue = Objects.requireNonNull(fhirContext.getElementDefinition(typeName)).newInstance(value.toString()); + getValueMutator(fhirContext).setValue(newPpc, typeValue); + return newPpc; + } + + public static IBase newPart(FhirContext fhirContext, String name, IBaseResource value, IBase... parts) { + validateNameAndValue(name, value); + IBase newPpc = newPart(fhirContext, name, parts); + getParameterElement(fhirContext).getChildByName("resource").getMutator().setValue(newPpc, value); + return newPpc; + } + + public static Optional getSingularStringPart(FhirContext fhirContext, IBaseResource parameters, String name) { + checkNotNull(parameters); + checkNotNull(name); + return ParametersUtil.getNamedParameterValueAsString(fhirContext, (IBaseParameters) parameters, name); + } + + public static List getPartsByName(FhirContext fhirContext, IBaseResource parameters, String name) { + checkNotNull(parameters); + checkNotNull(name); + return ParametersUtil.getNamedParameters(fhirContext, parameters, name); + } + + public static IBase newBase64BinaryPart(FhirContext fhirContext, String name, String value, IBase... parts) { + return newPart(fhirContext, "base64binary", name, value, parts); + } + + public static IBase newBooleanPart(FhirContext fhirContext, String name, boolean value, IBase... parts) { + return newPart(fhirContext, "boolean", name, value, parts); + } + + public static IBase newCanonicalPart(FhirContext fhirContext, String name, String value, IBase... parts) { + return newPart(fhirContext, "canonical", name, value, parts); + } + + public static IBase newCodePart(FhirContext fhirContext, String name, String value, IBase... parts) { + return newPart(fhirContext, "code", name, value, parts); + } + + public static IBase newDatePart(FhirContext fhirContext, String name, String value, IBase... parts) { + return newPart(fhirContext, "date", name, value, parts); + } + + public static IBase newDateTimePart(FhirContext fhirContext, String name, String value, IBase... parts) { + return newPart(fhirContext, "datetime", name, value, parts); + } + + public static IBase newDecimalPart(FhirContext fhirContext, String name, double value, IBase... parts) { + return newPart(fhirContext, "decimal", name, value, parts); + } + + public static IBase newIdPart(FhirContext fhirContext, String name, String value, IBase... parts) { + return newPart(fhirContext, "id", name, value, parts); + } + + public static IBase newInstantPart(FhirContext fhirContext, String name, String value, IBase... parts) { + return newPart(fhirContext, "instant", name, value, parts); + } + + public static IBase newIntegerPart(FhirContext fhirContext, String name, int value, IBase... parts) { + return newPart(fhirContext, "integer", name, value, parts); + } + + public static IBase newInteger64Part(FhirContext fhirContext, String name, long value, IBase... parts) { + return newPart(fhirContext, "integer64", name, value, parts); + } + + public static IBase newMarkdownPart(FhirContext fhirContext, String name, String value, IBase... parts) { + return newPart(fhirContext, "markdown", name, value, parts); + } + + public static IBase newOidPart(FhirContext fhirContext, String name, String value, IBase... parts) { + return newPart(fhirContext, "oid", name, value, parts); + } + + public static IBase newPositiveIntPart(FhirContext fhirContext, String name, int value, IBase... parts) { + return newPart(fhirContext, "positiveint", name, value, parts); + } + + public static IBase newStringPart(FhirContext fhirContext, String name, String value, IBase... parts) { + return newPart(fhirContext, "string", name, value, parts); + } + + public static IBase newTimePart(FhirContext fhirContext, String name, String value, IBase... parts) { + return newPart(fhirContext, "time", name, value, parts); + } + + public static IBase newUnsignedIntPart(FhirContext fhirContext, String name, int value, IBase... parts) { + return newPart(fhirContext, "unsignedint", name, value, parts); + } + + public static IBase newUriPart(FhirContext fhirContext, String name, String value, IBase... parts) { + return newPart(fhirContext, "uri", name, value, parts); + } + + public static IBase newUrlPart(FhirContext fhirContext, String name, String value, IBase... parts) { + return newPart(fhirContext, "url", name, value, parts); + } + + public static IBase newUuidPart(FhirContext fhirContext, String name, String value, IBase... parts) { + return newPart(fhirContext, "uuid", name, value, parts); + } } diff --git a/core/src/test/java/org/opencds/cqf/ruler/utility/OperationsTest.java b/core/src/test/java/org/opencds/cqf/ruler/utility/OperationsTest.java index 78993d3bb..d46d2e2e4 100644 --- a/core/src/test/java/org/opencds/cqf/ruler/utility/OperationsTest.java +++ b/core/src/test/java/org/opencds/cqf/ruler/utility/OperationsTest.java @@ -2,19 +2,22 @@ import static org.junit.jupiter.api.Assertions.assertThrows; +import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.rest.api.RequestTypeEnum; import org.junit.jupiter.api.Test; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; -public class OperationsTest { +class OperationsTest { private static final String dateValid = "2022-01-01"; private static final String dateValidAfter = "2022-06-01"; private static final String dateInvalid = "bad-date"; private static final RequestDetails requestDetails = new ServletRequestDetails(); - { + static { + requestDetails.setRequestType(RequestTypeEnum.GET); requestDetails.addParameter("dateValid", new String[] { dateValid }); requestDetails.addParameter("dateAfter", new String[] { dateValidAfter }); requestDetails.addParameter("dateMultiple", new String[] { dateValid, dateValidAfter }); @@ -26,81 +29,72 @@ public class OperationsTest { // TODO - add more coverage @Test - public void operationValidateDateValid() { + void operationValidateDateValid() { Operations.validateDate("dateValid", dateValid); } @Test - public void operationValidateDateInvalid() { - assertThrows(IllegalArgumentException.class, () -> { - Operations.validateDate("dateInvalid", dateInvalid); - }); + void operationValidateDateInvalid() { + assertThrows(IllegalArgumentException.class, + () -> Operations.validateDate("dateInvalid", dateInvalid)); } @Test - public void operationValidateDateNull() { - assertThrows(NullPointerException.class, () -> { - Operations.validateDate("dateNull", null); - }); + void operationValidateDateNull() { + assertThrows(NullPointerException.class, + () -> Operations.validateDate("dateNull", null)); } @Test - public void operationValidateDateEmpty() { - assertThrows(IllegalArgumentException.class, () -> { - Operations.validateDate("dateEmpty", ""); - }); + void operationValidateDateEmpty() { + assertThrows(IllegalArgumentException.class, + () -> Operations.validateDate("dateEmpty", "")); } @Test - public void operationValidateSingularDateValid() { + void operationValidateSingularDateValid() { Operations.validateSingularDate(requestDetails, "dateValid"); } @Test - public void operationValidateSingularDateInvalid() { - assertThrows(IllegalArgumentException.class, () -> { - Operations.validateSingularDate(requestDetails, "dateInvalid"); - }); + void operationValidateSingularDateInvalid() { + assertThrows(IllegalArgumentException.class, + () -> Operations.validateSingularDate(requestDetails, "dateInvalid")); } @Test - public void operationValidateSingularDateNull() { - assertThrows(NullPointerException.class, () -> { - Operations.validateSingularDate(requestDetails, "dateNull"); - }); + void operationValidateSingularDateNull() { + assertThrows(NullPointerException.class, + () -> Operations.validateSingularDate(requestDetails, "dateNull")); } @Test - public void operationValidateSingularDateEmpty() { - assertThrows(IllegalArgumentException.class, () -> { - Operations.validateSingularDate(requestDetails, "dateEmpty"); - }); + void operationValidateSingularDateEmpty() { + assertThrows(IllegalArgumentException.class, + () -> Operations.validateSingularDate(requestDetails, "dateEmpty")); } @Test - public void operationValidateSingularDateMissing() { - assertThrows(IllegalArgumentException.class, () -> { - Operations.validateSingularDate(requestDetails, "dateMissing"); - }); + void operationValidateSingularDateMissing() { + assertThrows(IllegalArgumentException.class, + () -> Operations.validateSingularDate(requestDetails, "dateMissing")); } @Test - public void operationValidateSingularDateMultiple() { - assertThrows(IllegalArgumentException.class, () -> { - Operations.validateSingularDate(requestDetails, "dateMultiple"); - }); + void operationValidateSingularDateMultiple() { + assertThrows(IllegalArgumentException.class, + () -> Operations.validateSingularDate(requestDetails, "dateMultiple")); } @Test - public void operationValidatePeriodValid() { + void operationValidatePeriodValid() { Operations.validatePeriod(requestDetails, "dateValid", "dateAfter"); } @Test - public void operationValidatePeriodEndBeforeStart() { - assertThrows(IllegalArgumentException.class, () -> { - Operations.validatePeriod(requestDetails, "dateAfter", "dateValid"); - }); + void operationValidatePeriodEndBeforeStart() { + assertThrows(DataFormatException.class, + () -> Operations.validatePeriod(requestDetails, "dateAfter", "dateValid")); } } diff --git a/plugin/cr/src/test/java/org/opencds/cqf/ruler/cr/r4/provider/CareGapsProviderIT.java b/plugin/cr/src/test/java/org/opencds/cqf/ruler/cr/r4/provider/CareGapsProviderIT.java index 8788e0f2a..1b8880ec5 100644 --- a/plugin/cr/src/test/java/org/opencds/cqf/ruler/cr/r4/provider/CareGapsProviderIT.java +++ b/plugin/cr/src/test/java/org/opencds/cqf/ruler/cr/r4/provider/CareGapsProviderIT.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import org.hl7.fhir.r4.model.Measure; import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.StringType; @@ -111,7 +112,7 @@ void testPeriodStartInvalid() { params.addParameter().setName("status").setValue(new StringType(statusValid)); params.addParameter().setName("measureId").setValue(new StringType(measureIdValid)); - assertThrows(InternalErrorException.class, () -> + assertThrows(InvalidRequestException.class, () -> getClient().operation().onType(Measure.class).named("$care-gaps") .withParameters(params) .useHttpGet() @@ -146,7 +147,7 @@ void testPeriodEndInvalid() { params.addParameter().setName("status").setValue(new StringType(statusValid)); params.addParameter().setName("measureId").setValue(new StringType(measureIdValid)); - assertThrows(InternalErrorException.class, () -> + assertThrows(InvalidRequestException.class, () -> getClient().operation().onType(Measure.class).named("$care-gaps") .withParameters(params) .useHttpGet() @@ -164,7 +165,7 @@ void testPeriodInvalid() { params.addParameter().setName("status").setValue(new StringType(statusValid)); params.addParameter().setName("measureId").setValue(new StringType(measureIdValid)); - assertThrows(InternalErrorException.class, () -> + assertThrows(InvalidRequestException.class, () -> getClient().operation().onType(Measure.class).named("$care-gaps") .withParameters(params) .useHttpGet() diff --git a/plugin/ra/src/main/java/org/opencds/cqf/ruler/ra/RAConstants.java b/plugin/ra/src/main/java/org/opencds/cqf/ruler/ra/RAConstants.java new file mode 100644 index 000000000..74add3f8c --- /dev/null +++ b/plugin/ra/src/main/java/org/opencds/cqf/ruler/ra/RAConstants.java @@ -0,0 +1,23 @@ +package org.opencds.cqf.ruler.ra; + +public class RAConstants { + + private RAConstants() { + } + + // DaVinci IG constants + public static final String REPORT_ID_SUFFIX = "-report"; + public static final String PATIENT_REPORT_PROFILE_URL = "http://hl7.org/fhir/us/davinci-ra/StructureDefinition/ra-measurereport-bundle"; + + // Parameter validation constants + public static final String INVALID_PARAMETERS_NAME = "Invalid parameters"; + public static final String INVALID_PARAMETERS_SEVERITY = "error"; + + // Operation parameter name constants + public static final String PERIOD_START = "periodStart"; + public static final String PERIOD_END = "periodEnd"; + public static final String SUBJECT = "subject"; + public static final String MEASURE_ID = "measureId"; + public static final String MEASURE_IDENTIFIER = "measureIdentifier"; + public static final String MEASURE_URL = "measureUrl"; +} diff --git a/plugin/ra/src/main/java/org/opencds/cqf/ruler/ra/r4/ReportProvider.java b/plugin/ra/src/main/java/org/opencds/cqf/ruler/ra/r4/ReportProvider.java index 55938184c..209b48c45 100644 --- a/plugin/ra/src/main/java/org/opencds/cqf/ruler/ra/r4/ReportProvider.java +++ b/plugin/ra/src/main/java/org/opencds/cqf/ruler/ra/r4/ReportProvider.java @@ -6,10 +6,10 @@ import java.util.Map; import java.util.UUID; -import ca.uhn.fhir.rest.api.RequestTypeEnum; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.MeasureReport; @@ -22,6 +22,7 @@ import org.opencds.cqf.ruler.behavior.r4.MeasureReportUser; import org.opencds.cqf.ruler.behavior.r4.ParameterUser; import org.opencds.cqf.ruler.provider.DaoRegistryOperationProvider; +import org.opencds.cqf.ruler.ra.RAConstants; import org.opencds.cqf.ruler.utility.Operations; import org.opencds.cqf.ruler.utility.Searches; @@ -35,14 +36,15 @@ import static org.opencds.cqf.ruler.utility.r4.Parameters.part; public class ReportProvider extends DaoRegistryOperationProvider - implements ParameterUser, ResourceCreator, MeasureReportUser { + implements ParameterUser, ResourceCreator, MeasureReportUser { + /** * Implements the $report * operation found in the * Da Vinci Risk * Adjustment IG. - * + * * @param requestDetails metadata about the current request being processed. * Generally auto-populated by the HAPI FHIR server * framework. @@ -52,96 +54,92 @@ public class ReportProvider extends DaoRegistryOperationProvider * @return a Parameters with Bundles of MeasureReports and evaluatedResource * Resources */ - @Description(shortDefinition = "$report", value = "Implements the $report operation found in the Da Vinci Risk Adjustment IG.") + @Description(shortDefinition = "$report operation", + value = "Implements the $report operation found in the Da Vinci Risk Adjustment IG.") @Operation(name = "$report", idempotent = true, type = MeasureReport.class) public Parameters report( - RequestDetails requestDetails, - @OperationParam(name = "periodStart") String periodStart, - @OperationParam(name = "periodEnd") String periodEnd, - @OperationParam(name = "subject") String subject, - @OperationParam(name = "measureId") List measureId, - @OperationParam(name = "measureIdentifier") List measureIdentifier, - @OperationParam(name = "measureUrl") List measureUrl) throws FHIRException { - - if (requestDetails.getRequestType() == RequestTypeEnum.GET) { - try { - validateParameters(requestDetails); - Operations.validatePattern("subject", subject, Operations.PATIENT_OR_GROUP_REFERENCE); - Operations.validatePeriod(requestDetails, "periodStart", "periodEnd"); - } catch (Exception e) { - return parameters(part("Invalid parameters", - generateIssue("error", e.getMessage()))); - } + RequestDetails requestDetails, + @OperationParam(name = RAConstants.PERIOD_START, typeName = "date") IPrimitiveType periodStart, + @OperationParam(name = RAConstants.PERIOD_END, typeName = "date") IPrimitiveType periodEnd, + @OperationParam(name = RAConstants.SUBJECT) String subject, + @OperationParam(name = RAConstants.MEASURE_ID) List measureId, + @OperationParam(name = RAConstants.MEASURE_IDENTIFIER) List measureIdentifier, + @OperationParam(name = RAConstants.MEASURE_URL) List measureUrl) throws FHIRException { + + try { + validateParameters(requestDetails); + } catch (Exception e) { + return parameters(part(RAConstants.INVALID_PARAMETERS_NAME, + generateIssue(RAConstants.INVALID_PARAMETERS_SEVERITY, e.getMessage()))); } ensureSupplementalDataElementSearchParameter(requestDetails); - Parameters result = newResource(Parameters.class, subject.replace("/", "-") + "-report"); - Date periodStartDate = Operations.resolveRequestDate(periodStart, true); - Date periodEndDate = Operations.resolveRequestDate(periodEnd, false); - Period period = new Period().setStart(periodStartDate).setEnd(periodEndDate); + Parameters result = newResource(Parameters.class, + subject.replace("/", "-") + RAConstants.REPORT_ID_SUFFIX); + Period period = new Period().setStart(periodStart.getValue()).setEnd(periodEnd.getValue()); List patients = getPatientListFromSubject(subject); (patients) - .forEach( - patient -> { - Parameters.ParametersParameterComponent patientParameter = patientReport(patient, period, - requestDetails.getFhirServerBase()); - result.addParameter(patientParameter); - }); + .forEach( + patient -> { + Parameters.ParametersParameterComponent patientParameter = patientReport(patient, period, + requestDetails.getFhirServerBase()); + result.addParameter(patientParameter); + }); return result; } public void validateParameters(RequestDetails requestDetails) { - Operations.validateCardinality(requestDetails, "periodStart", 1); - Operations.validateCardinality(requestDetails, "periodEnd", 1); - Operations.validateCardinality(requestDetails, "subject", 1); - Operations.validateAtLeastOne(requestDetails, "measureId", "measureIdentifier", "measureUrl"); + Operations.validateCardinality(requestDetails, RAConstants.PERIOD_START, 1); + Operations.validateCardinality(requestDetails, RAConstants.PERIOD_END, 1); + Operations.validatePeriod(requestDetails, RAConstants.PERIOD_START, RAConstants.PERIOD_END); + Operations.validateCardinality(requestDetails, RAConstants.SUBJECT, 1); + Operations.validateSingularPattern(requestDetails, RAConstants.SUBJECT, + Operations.PATIENT_OR_GROUP_REFERENCE); + Operations.validateAtLeastOne(requestDetails, RAConstants.MEASURE_ID, + RAConstants.MEASURE_IDENTIFIER, RAConstants.MEASURE_URL); } - private static final String PATIENT_REPORT_PROFILE_URL = "http://hl7.org/fhir/us/davinci-ra/StructureDefinition/ra-measurereport-bundle"; - private Parameters.ParametersParameterComponent patientReport(Patient thePatient, Period thePeriod, - String serverBase) { - + String serverBase) { String patientId = thePatient.getIdElement().getIdPart(); final Map bundleEntries = new HashMap<>(); bundleEntries.put(thePatient.getIdElement(), thePatient); ReferenceParam subjectParam = new ReferenceParam(patientId); - search(MeasureReport.class, Searches.byParam("subject", subjectParam)).getAllResourcesTyped() - .forEach(measureReport -> { - - if (measureReport.getPeriod().getEnd().before(thePeriod.getStart()) - || measureReport.getPeriod().getStart().after(thePeriod.getEnd())) { - return; - } + search(MeasureReport.class, Searches.byParam(RAConstants.SUBJECT, subjectParam)).getAllResourcesTyped() + .forEach(measureReport -> { + if (measureReport.getPeriod().getEnd().before(thePeriod.getStart()) + || measureReport.getPeriod().getStart().after(thePeriod.getEnd())) { + return; + } - bundleEntries.putIfAbsent(measureReport.getIdElement(), measureReport); + bundleEntries.putIfAbsent(measureReport.getIdElement(), measureReport); - getEvaluatedResources(measureReport) - .values() - .forEach(resource -> bundleEntries.putIfAbsent(resource.getIdElement(), resource)); - }); + getEvaluatedResources(measureReport) + .values() + .forEach(resource -> bundleEntries.putIfAbsent(resource.getIdElement(), resource)); + }); Bundle patientReportBundle = new Bundle(); - patientReportBundle.setMeta(new Meta().addProfile(PATIENT_REPORT_PROFILE_URL)); + patientReportBundle.setMeta(new Meta().addProfile(RAConstants.PATIENT_REPORT_PROFILE_URL)); patientReportBundle.setType(Bundle.BundleType.COLLECTION); patientReportBundle.setTimestamp(new Date()); - patientReportBundle.setId(patientId + "-report"); + patientReportBundle.setId(patientId + RAConstants.REPORT_ID_SUFFIX); patientReportBundle.setIdentifier( - new Identifier().setSystem("urn:ietf:rfc:3986").setValue("urn:uuid:" + UUID.randomUUID())); + new Identifier().setSystem("urn:ietf:rfc:3986").setValue("urn:uuid:" + UUID.randomUUID())); bundleEntries.forEach((key, value) -> patientReportBundle.addEntry( - new Bundle.BundleEntryComponent() - .setResource((Resource) value) - .setFullUrl(Operations.getFullUrl(serverBase, value.fhirType(), - value.getIdElement().getIdPart())))); + new Bundle.BundleEntryComponent() + .setResource((Resource) value) + .setFullUrl(Operations.getFullUrl(serverBase, value.fhirType(), + value.getIdElement().getIdPart())))); Parameters.ParametersParameterComponent patientParameter = new Parameters.ParametersParameterComponent(); patientParameter.setResource(patientReportBundle); - patientParameter.setId(thePatient.getIdElement().getIdPart() + "-report"); + patientParameter.setId(thePatient.getIdElement().getIdPart() + RAConstants.REPORT_ID_SUFFIX); patientParameter.setName("return"); return patientParameter; diff --git a/plugin/ra/src/test/java/org/opencds/cqf/ruler/ra/r4/ReportProviderIT.java b/plugin/ra/src/test/java/org/opencds/cqf/ruler/ra/r4/ReportProviderIT.java index e4c03235a..e84c41dc6 100644 --- a/plugin/ra/src/test/java/org/opencds/cqf/ruler/ra/r4/ReportProviderIT.java +++ b/plugin/ra/src/test/java/org/opencds/cqf/ruler/ra/r4/ReportProviderIT.java @@ -1,5 +1,7 @@ package org.opencds.cqf.ruler.ra.r4; +import ca.uhn.fhir.rest.gclient.IOperationUntypedWithInput; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Group; import org.hl7.fhir.r4.model.MeasureReport; @@ -7,6 +9,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.opencds.cqf.ruler.ra.RAConfig; +import org.opencds.cqf.ruler.ra.RAConstants; import org.opencds.cqf.ruler.ra.RAProperties; import org.opencds.cqf.ruler.test.RestIntegrationTest; import org.opencds.cqf.ruler.test.utility.Urls; @@ -20,12 +23,13 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opencds.cqf.ruler.utility.r4.Parameters.datePart; import static org.opencds.cqf.ruler.utility.r4.Parameters.parameters; import static org.opencds.cqf.ruler.utility.r4.Parameters.stringPart; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - classes = { ReportProviderIT.class, RAConfig.class }, - properties = { "hapi.fhir.fhir_version=r4" }) + classes = { ReportProviderIT.class, RAConfig.class }, + properties = { "hapi.fhir.fhir_version=r4" }) class ReportProviderIT extends RestIntegrationTest { @Autowired private RAProperties myRaProperties; @@ -37,179 +41,374 @@ public void beforeEach() { } @Test - void testMissingPeriodStartParam() { + void testMissingPeriodStartParamGET() { Parameters params = parameters( - stringPart("periodEnd", "2021-12-31"), - stringPart("subject", "Patient/testReport01"), - stringPart("measureId", "Measure-RAModelExample01")); + stringPart(RAConstants.PERIOD_END, "2021-12-31"), + stringPart(RAConstants.SUBJECT, "Patient/testReport01"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); Parameters result = getClient().operation().onType(MeasureReport.class).named("$report") - .withParameters(params).useHttpGet().returnResourceType(Parameters.class).execute(); + .withParameters(params).useHttpGet().returnResourceType(Parameters.class).execute(); assertTrue(result.hasParameter("Invalid parameters")); } @Test - void testMissingPeriodEndParam() { + void testMissingPeriodStartParamPOST() { Parameters params = parameters( - stringPart("periodStart", "2021-01-01"), - stringPart("subject", "Patient/testReport01"), - stringPart("measureId", "Measure-RAModelExample01")); + datePart(RAConstants.PERIOD_END, "2021-12-31"), + stringPart(RAConstants.SUBJECT, "Patient/testReport01"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); Parameters result = getClient().operation().onType(MeasureReport.class).named("$report") - .withParameters(params).useHttpGet().returnResourceType(Parameters.class).execute(); + .withParameters(params).returnResourceType(Parameters.class).execute(); assertTrue(result.hasParameter("Invalid parameters")); } @Test - void testMissingSubjectParam() { + void testMissingPeriodEndParamGET() { Parameters params = parameters( - stringPart("periodStart", "2021-01-01"), - stringPart("periodEnd", "2021-12-31"), - stringPart("measureId", "Measure-RAModelExample01")); + stringPart(RAConstants.PERIOD_START, "2021-01-01"), + stringPart(RAConstants.SUBJECT, "Patient/testReport01"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); Parameters result = getClient().operation().onType(MeasureReport.class).named("$report") - .withParameters(params).useHttpGet().returnResourceType(Parameters.class).execute(); + .withParameters(params).useHttpGet().returnResourceType(Parameters.class).execute(); assertTrue(result.hasParameter("Invalid parameters")); } @Test - void testStartPeriodBeforeEndPeriod() { + void testMissingPeriodEndParamPOST() { Parameters params = parameters( - stringPart("periodStart", "2021-01-01"), - stringPart("periodEnd", "2020-12-31"), - stringPart("subject", "Patient/testReport01"), - stringPart("measureId", "Measure-RAModelExample01")); + datePart(RAConstants.PERIOD_START, "2021-01-01"), + stringPart(RAConstants.SUBJECT, "Patient/testReport01"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); Parameters result = getClient().operation().onType(MeasureReport.class).named("$report") - .withParameters(params).useHttpGet().returnResourceType(Parameters.class).execute(); + .withParameters(params).returnResourceType(Parameters.class).execute(); assertTrue(result.hasParameter("Invalid parameters")); } - // TODO: add the count of patients returned @Test - void testSubjectPatient() { + void testInvalidStartPeriodParamGET() { Parameters params = parameters( - stringPart("periodStart", "2021-01-01"), - stringPart("periodEnd", "2021-12-31"), - stringPart("subject", "Patient/ra-patient01"), - stringPart("measureId", "Measure-RAModelExample01")); + stringPart(RAConstants.PERIOD_START, "2021/01/01"), + stringPart(RAConstants.PERIOD_END, "2021-12-31"), + stringPart(RAConstants.SUBJECT, "Patient/testReport01"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); + + IOperationUntypedWithInput request = getClient().operation() + .onType(MeasureReport.class).named("$report").withParameters(params) + .useHttpGet().returnResourceType(Parameters.class); + assertThrows(InvalidRequestException.class, request::execute); + } + + @Test + void testInvalidEndPeriodParamGET() { + Parameters params = parameters( + stringPart(RAConstants.PERIOD_START, "2021-01-01"), + stringPart(RAConstants.PERIOD_END, "2021/12/31"), + stringPart(RAConstants.SUBJECT, "Patient/testReport01"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); + + IOperationUntypedWithInput request = getClient().operation() + .onType(MeasureReport.class).named("$report").withParameters(params) + .useHttpGet().returnResourceType(Parameters.class); + assertThrows(InvalidRequestException.class, request::execute); + } + + @Test + void testMissingSubjectParamGET() { + Parameters params = parameters( + stringPart(RAConstants.PERIOD_START, "2021-01-01"), + stringPart(RAConstants.PERIOD_END, "2021-12-31"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); + + Parameters result = getClient().operation().onType(MeasureReport.class).named("$report") + .withParameters(params).useHttpGet().returnResourceType(Parameters.class).execute(); + + assertTrue(result.hasParameter(RAConstants.INVALID_PARAMETERS_NAME)); + } + + @Test + void testMissingSubjectParamPOST() { + Parameters params = parameters( + datePart(RAConstants.PERIOD_START, "2021-01-01"), + datePart(RAConstants.PERIOD_END, "2021-12-31"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); + + Parameters result = getClient().operation().onType(MeasureReport.class).named("$report") + .withParameters(params).returnResourceType(Parameters.class).execute(); + + assertTrue(result.hasParameter(RAConstants.INVALID_PARAMETERS_NAME)); + } + + @Test + void testEndPeriodBeforeStartPeriodGET() { + Parameters params = parameters( + stringPart(RAConstants.PERIOD_START, "2021-01-01"), + stringPart(RAConstants.PERIOD_END, "2020-12-31"), + stringPart(RAConstants.SUBJECT, "Patient/testReport01"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); + + Parameters result = getClient().operation().onType(MeasureReport.class).named("$report") + .withParameters(params).useHttpGet().returnResourceType(Parameters.class).execute(); + + assertTrue(result.hasParameter(RAConstants.INVALID_PARAMETERS_NAME)); + } + + @Test + void testEndPeriodBeforeStartPeriodPOST() { + Parameters params = parameters( + datePart(RAConstants.PERIOD_START, "2021-01-01"), + datePart(RAConstants.PERIOD_END, "2020-12-31"), + stringPart(RAConstants.SUBJECT, "Patient/testReport01"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); + + Parameters result = getClient().operation().onType(MeasureReport.class).named("$report") + .withParameters(params).returnResourceType(Parameters.class).execute(); + + assertTrue(result.hasParameter(RAConstants.INVALID_PARAMETERS_NAME)); + } + + @Test + void testSubjectPatientGET() { + Parameters params = parameters( + stringPart(RAConstants.PERIOD_START, "2021-01-01"), + stringPart(RAConstants.PERIOD_END, "2021-12-31"), + stringPart(RAConstants.SUBJECT, "Patient/ra-patient01"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); loadResource("Patient-ra-patient01.json"); Parameters result = getClient().operation().onType(MeasureReport.class).named("$report") - .withParameters(params).useHttpGet().returnResourceType(Parameters.class).execute(); + .withParameters(params).useHttpGet().returnResourceType(Parameters.class).execute(); assertFalse(result.hasParameter("Invalid parameters")); } - // TODO: add the count of patients returned @Test - void testSubjectGroup() { + void testSubjectPatientPOST() { Parameters params = parameters( - stringPart("periodStart", "2021-01-01"), - stringPart("periodEnd", "2021-12-31"), - stringPart("subject", "Group/ra-group01"), - stringPart("measureId", "Measure-RAModelExample01")); + datePart(RAConstants.PERIOD_START, "2021-01-01"), + datePart(RAConstants.PERIOD_END, "2021-12-31"), + stringPart(RAConstants.SUBJECT, "Patient/ra-patient01"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); + + loadResource("Patient-ra-patient01.json"); + + Parameters result = getClient().operation().onType(MeasureReport.class).named("$report") + .withParameters(params).returnResourceType(Parameters.class).execute(); + + assertFalse(result.hasParameter("Invalid parameters")); + } + + @Test + void testSubjectGroupGET() { + Parameters params = parameters( + stringPart(RAConstants.PERIOD_START, "2021-01-01"), + stringPart(RAConstants.PERIOD_END, "2021-12-31"), + stringPart(RAConstants.SUBJECT, "Group/ra-group01"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); loadResource("Patient-ra-patient01.json"); loadResource("Group-ra-group01.json"); Parameters result = getClient().operation().onType(MeasureReport.class).named("$report") - .withParameters(params).useHttpGet().returnResourceType(Parameters.class).execute(); + .withParameters(params).useHttpGet().returnResourceType(Parameters.class).execute(); assertFalse(result.hasParameter("Invalid parameters")); + assertEquals(1, result.getParameter().size()); + } + + @Test + void testSubjectGroupPOST() { + Parameters params = parameters( + datePart(RAConstants.PERIOD_START, "2021-01-01"), + datePart(RAConstants.PERIOD_END, "2021-12-31"), + stringPart(RAConstants.SUBJECT, "Group/ra-group01"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); + + loadResource("Patient-ra-patient01.json"); + loadResource("Group-ra-group01.json"); + + Parameters result = getClient().operation().onType(MeasureReport.class).named("$report") + .withParameters(params).returnResourceType(Parameters.class).execute(); + + assertFalse(result.hasParameter("Invalid parameters")); + assertEquals(1, result.getParameter().size()); + } + + @Test + void testSubjectIsNotPatientOrGroupGET() { + Parameters params = parameters( + stringPart(RAConstants.PERIOD_START, "2021-01-01"), + stringPart(RAConstants.PERIOD_END, "2021-12-31"), + stringPart(RAConstants.SUBJECT, "ra-patient01"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); + + Parameters result = getClient().operation().onType(MeasureReport.class).named("$report") + .withParameters(params).useHttpGet().returnResourceType(Parameters.class).execute(); + + assertTrue(result.hasParameter("Invalid parameters")); } @Test - void testSubjectIsNotPatientOrGroup() { + void testSubjectIsNotPatientOrGroupPOST() { Parameters params = parameters( - stringPart("periodStart", "2021-01-01"), - stringPart("periodEnd", "2021-12-31"), - stringPart("subject", "ra-patient01"), - stringPart("measureId", "Measure-RAModelExample01")); + datePart(RAConstants.PERIOD_START, "2021-01-01"), + datePart(RAConstants.PERIOD_END, "2021-12-31"), + stringPart(RAConstants.SUBJECT, "ra-patient01"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); Parameters result = getClient().operation().onType(MeasureReport.class).named("$report") - .withParameters(params).useHttpGet().returnResourceType(Parameters.class).execute(); + .withParameters(params).returnResourceType(Parameters.class).execute(); assertTrue(result.hasParameter("Invalid parameters")); } @Test - void testPatientSubjectNotFound() { + void testPatientSubjectNotFoundGET() { + Parameters params = parameters( + stringPart(RAConstants.PERIOD_START, "2021-01-01"), + stringPart(RAConstants.PERIOD_END, "2021-12-31"), + stringPart(RAConstants.SUBJECT, "Patient/bad-patient"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); + + IOperationUntypedWithInput request = getClient().operation() + .onType(MeasureReport.class).named("$report").withParameters(params) + .useHttpGet().returnResourceType(Parameters.class); + assertThrows(ResourceNotFoundException.class, request::execute); + } + + @Test + void testPatientSubjectNotFoundPOST() { + Parameters params = parameters( + datePart(RAConstants.PERIOD_START, "2021-01-01"), + datePart(RAConstants.PERIOD_END, "2021-12-31"), + stringPart(RAConstants.SUBJECT, "Patient/bad-patient"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); + + IOperationUntypedWithInput request = getClient().operation() + .onType(MeasureReport.class).named("$report").withParameters(params) + .returnResourceType(Parameters.class); + assertThrows(ResourceNotFoundException.class, request::execute); + } + + @Test + void testGroupSubjectNotFoundGET() { Parameters params = parameters( - stringPart("periodStart", "2021-01-01"), - stringPart("periodEnd", "2021-12-31"), - stringPart("subject", "Patient/bad-patient"), - stringPart("measureId", "Measure-RAModelExample01")); - - assertThrows(ResourceNotFoundException.class, () -> getClient().operation() - .onType(MeasureReport.class).named("$report").withParameters(params) - .returnResourceType(Parameters.class).execute()); + stringPart(RAConstants.PERIOD_START, "2021-01-01"), + stringPart(RAConstants.PERIOD_END, "2021-12-31"), + stringPart(RAConstants.SUBJECT, "Group/bad-group"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); + + IOperationUntypedWithInput request = getClient().operation() + .onType(MeasureReport.class).named("$report").withParameters(params) + .useHttpGet().returnResourceType(Parameters.class); + assertThrows(ResourceNotFoundException.class, request::execute); } @Test - void testGroupSubjectNotFound() { + void testGroupSubjectNotFoundPOST() { Parameters params = parameters( - stringPart("periodStart", "2021-01-01"), - stringPart("periodEnd", "2021-12-31"), - stringPart("subject", "Group/bad-group"), - stringPart("measureId", "Measure-RAModelExample01")); - - assertThrows(ResourceNotFoundException.class, () -> getClient().operation() - .onType(MeasureReport.class).named("$report").withParameters(params) - .returnResourceType(Parameters.class).execute()); + datePart(RAConstants.PERIOD_START, "2021-01-01"), + datePart(RAConstants.PERIOD_END, "2021-12-31"), + stringPart(RAConstants.SUBJECT, "Group/bad-group"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); + + IOperationUntypedWithInput request = getClient().operation() + .onType(MeasureReport.class).named("$report").withParameters(params) + .returnResourceType(Parameters.class); + assertThrows(ResourceNotFoundException.class, request::execute); } // This test requires the following application setting: // enforce_referential_integrity_on_write: false @Test - void testSubjectPatientNotFoundInGroup() { + void testSubjectPatientNotFoundInGroupGET() { + Parameters params = parameters( + stringPart(RAConstants.PERIOD_START, "2021-01-01"), + stringPart(RAConstants.PERIOD_END, "2021-12-31"), + stringPart(RAConstants.SUBJECT, "Group/ra-group00"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); + + loadResource("Group-ra-group00.json"); + Group group = getClient().read().resource(Group.class).withId("ra-group00").execute(); + assertNotNull(group); + + Parameters result = getClient().operation().onType(MeasureReport.class).named("$report") + .withParameters(params).useHttpGet().returnResourceType(Parameters.class).execute(); + + assertFalse(result.hasParameter("Invalid parameters")); + } + + @Test + void testSubjectPatientNotFoundInGroupPOST() { Parameters params = parameters( - stringPart("periodStart", "2021-01-01"), - stringPart("periodEnd", "2021-12-31"), - stringPart("subject", "Group/ra-group00"), - stringPart("measureId", "Measure-RAModelExample01")); + datePart(RAConstants.PERIOD_START, "2021-01-01"), + datePart(RAConstants.PERIOD_END, "2021-12-31"), + stringPart(RAConstants.SUBJECT, "Group/ra-group00"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); loadResource("Group-ra-group00.json"); Group group = getClient().read().resource(Group.class).withId("ra-group00").execute(); assertNotNull(group); Parameters result = getClient().operation().onType(MeasureReport.class).named("$report") - .withParameters(params).useHttpGet().returnResourceType(Parameters.class).execute(); + .withParameters(params).returnResourceType(Parameters.class).execute(); + + assertFalse(result.hasParameter("Invalid parameters")); + } + + @Test + void testSubjectMultiplePatientGroupGET() { + Parameters params = parameters( + stringPart(RAConstants.PERIOD_START, "2021-01-01"), + stringPart(RAConstants.PERIOD_END, "2021-12-31"), + stringPart(RAConstants.SUBJECT, "Group/ra-group02"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); + + loadResource("Patient-ra-patient02.json"); + loadResource("Patient-ra-patient03.json"); + loadResource("Group-ra-group02.json"); + + Parameters result = getClient().operation().onType(MeasureReport.class).named("$report") + .withParameters(params).useHttpGet().returnResourceType(Parameters.class).execute(); assertFalse(result.hasParameter("Invalid parameters")); + assertEquals(2, result.getParameter().size()); } - // TODO: add the count of patients returned @Test - void testSubjectMultiplePatientGroup() { + void testSubjectMultiplePatientGroupPOST() { Parameters params = parameters( - stringPart("periodStart", "2021-01-01"), - stringPart("periodEnd", "2021-12-31"), - stringPart("subject", "Group/ra-group02"), - stringPart("measureId", "Measure-RAModelExample01")); + datePart(RAConstants.PERIOD_START, "2021-01-01"), + datePart(RAConstants.PERIOD_END, "2021-12-31"), + stringPart(RAConstants.SUBJECT, "Group/ra-group02"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); loadResource("Patient-ra-patient02.json"); loadResource("Patient-ra-patient03.json"); loadResource("Group-ra-group02.json"); Parameters result = getClient().operation().onType(MeasureReport.class).named("$report") - .withParameters(params).useHttpGet().returnResourceType(Parameters.class).execute(); + .withParameters(params).returnResourceType(Parameters.class).execute(); assertFalse(result.hasParameter("Invalid parameters")); + assertEquals(2, result.getParameter().size()); } @Test - void testSingleSubjectSingleReport() { + void testSingleSubjectSingleReportGET() { Parameters params = parameters( - stringPart("periodStart", "2021-01-01"), - stringPart("periodEnd", "2021-12-31"), - stringPart("subject", "Patient/ra-patient01"), - stringPart("measureId", "Measure-RAModelExample01")); + stringPart(RAConstants.PERIOD_START, "2021-01-01"), + stringPart(RAConstants.PERIOD_END, "2021-12-31"), + stringPart(RAConstants.SUBJECT, "Patient/ra-patient01"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); loadResource("Patient-ra-patient01.json"); loadResource("Condition-ra-condition02pat01.json"); @@ -234,7 +433,7 @@ void testSingleSubjectSingleReport() { loadResource("MeasureReport-ra-measurereport01.json"); Parameters result = getClient().operation().onType(MeasureReport.class).named("$report") - .withParameters(params).useHttpGet().returnResourceType(Parameters.class).execute(); + .withParameters(params).useHttpGet().returnResourceType(Parameters.class).execute(); assertNotNull(result); assertEquals(1, result.getParameter().size()); @@ -246,12 +445,95 @@ void testSingleSubjectSingleReport() { } @Test - void testReportDoesNotIncludeNonEvaluatedResources() { + void testSingleSubjectSingleReportPOST() { + Parameters params = parameters( + datePart(RAConstants.PERIOD_START, "2021-01-01"), + datePart(RAConstants.PERIOD_END, "2021-12-31"), + stringPart(RAConstants.SUBJECT, "Patient/ra-patient01"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); + + loadResource("Patient-ra-patient01.json"); + loadResource("Condition-ra-condition02pat01.json"); + loadResource("Condition-ra-condition03pat01.json"); + loadResource("Condition-ra-condition08pat01.json"); + loadResource("Condition-ra-condition09pat01.json"); + loadResource("Condition-ra-condition10pat01.json"); + loadResource("Condition-ra-condition11pat01.json"); + loadResource("Condition-ra-condition17pat01.json"); + loadResource("Condition-ra-condition18pat01.json"); + loadResource("Condition-ra-condition33pat01.json"); + loadResource("Condition-ra-condition43pat01.json"); + loadResource("Condition-ra-condition44pat01.json"); + loadResource("Observation-ra-obs21pat01.json"); + loadResource("Encounter-ra-encounter02pat01.json"); + loadResource("Encounter-ra-encounter03pat01.json"); + loadResource("Encounter-ra-encounter08pat01.json"); + loadResource("Encounter-ra-encounter09pat01.json"); + loadResource("Encounter-ra-encounter11pat01.json"); + loadResource("Encounter-ra-encounter43pat01.json"); + loadResource("Encounter-ra-encounter44pat01.json"); + loadResource("MeasureReport-ra-measurereport01.json"); + + Parameters result = getClient().operation().onType(MeasureReport.class).named("$report") + .withParameters(params).returnResourceType(Parameters.class).execute(); + + assertNotNull(result); + assertEquals(1, result.getParameter().size()); + + Bundle bundle = (Bundle) result.getParameter().get(0).getResource(); + assertNotNull(bundle); + // all the resources inserted above are in the bundle entry + assertEquals(21, bundle.getEntry().size()); + } + + @Test + void testReportDoesNotIncludeNonEvaluatedResourcesGET() { + Parameters params = parameters( + stringPart(RAConstants.PERIOD_START, "2021-01-01"), + stringPart(RAConstants.PERIOD_END, "2021-12-31"), + stringPart(RAConstants.SUBJECT, "Patient/ra-patient01"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); + + loadResource("Patient-ra-patient01.json"); + loadResource("Condition-ra-condition02pat01.json"); + loadResource("Condition-ra-condition03pat01.json"); + loadResource("Condition-ra-condition08pat01.json"); + loadResource("Condition-ra-condition09pat01.json"); + loadResource("Condition-ra-condition10pat01.json"); + loadResource("Condition-ra-condition11pat01.json"); + loadResource("Condition-ra-condition17pat01.json"); + loadResource("Condition-ra-condition18pat01.json"); + loadResource("Condition-ra-condition33pat01.json"); + loadResource("Condition-ra-condition43pat01.json"); + loadResource("Condition-ra-condition44pat01.json"); + loadResource("Observation-ra-obs21pat01.json"); + loadResource("Encounter-ra-encounter02pat01.json"); + loadResource("Encounter-ra-encounter03pat01.json"); + loadResource("Encounter-ra-encounter08pat01.json"); + loadResource("Encounter-ra-encounter09pat01.json"); + loadResource("Encounter-ra-encounter11pat01.json"); + loadResource("Encounter-ra-encounter43pat01.json"); + loadResource("Encounter-ra-encounter44pat01.json"); + loadResource("MeasureReport-ra-measurereport01.json"); + // this is not an evaluatedResource of the report + loadResource("Encounter-ra-encounter45pat01.json"); + + Parameters result = getClient().operation().onType(MeasureReport.class).named("$report") + .withParameters(params).useHttpGet().returnResourceType(Parameters.class).execute(); + + Bundle bundle = (Bundle) result.getParameter().get(0).getResource(); + // all the resources inserted above are in the bundle entry except the one that + // was not evaluated + assertEquals(21, bundle.getEntry().size()); + } + + @Test + void testReportDoesNotIncludeNonEvaluatedResourcesPOST() { Parameters params = parameters( - stringPart("periodStart", "2021-01-01"), - stringPart("periodEnd", "2021-12-31"), - stringPart("subject", "Patient/ra-patient01"), - stringPart("measureId", "Measure-RAModelExample01")); + datePart(RAConstants.PERIOD_START, "2021-01-01"), + datePart(RAConstants.PERIOD_END, "2021-12-31"), + stringPart(RAConstants.SUBJECT, "Patient/ra-patient01"), + stringPart(RAConstants.MEASURE_ID, "Measure-RAModelExample01")); loadResource("Patient-ra-patient01.json"); loadResource("Condition-ra-condition02pat01.json"); @@ -278,7 +560,7 @@ void testReportDoesNotIncludeNonEvaluatedResources() { loadResource("Encounter-ra-encounter45pat01.json"); Parameters result = getClient().operation().onType(MeasureReport.class).named("$report") - .withParameters(params).useHttpGet().returnResourceType(Parameters.class).execute(); + .withParameters(params).returnResourceType(Parameters.class).execute(); Bundle bundle = (Bundle) result.getParameter().get(0).getResource(); // all the resources inserted above are in the bundle entry except the one that diff --git a/pom.xml b/pom.xml index 366639025..5adeec055 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,5 @@ - + 4.0.0 org.opencds.cqf.ruler