Skip to content

Commit

Permalink
Add tests for issues #1589 and #1449
Browse files Browse the repository at this point in the history
We don't make it very easy to add component-level tests for our rest
layer.  This pull request works with what we have to test the public
JAX-RS methods for building the CapabilityStatement resource and the
smart-configuration JSON from various configs.

Signed-off-by: Lee Surprenant <[email protected]>
  • Loading branch information
lmsurpre committed Oct 20, 2020
1 parent dc560b6 commit ce32f11
Show file tree
Hide file tree
Showing 11 changed files with 1,304 additions and 56 deletions.
127 changes: 75 additions & 52 deletions fhir-model/src/main/java/com/ibm/fhir/model/util/ModelSupport.java
Original file line number Diff line number Diff line change
Expand Up @@ -112,61 +112,64 @@ public final class ModelSupport {
private static final Map<Class<?>, Class<?>> CONCRETE_TYPE_MAP = buildConcreteTypeMap();
private static final Map<Class<?>, Map<String, ElementInfo>> MODEL_CLASS_ELEMENT_INFO_MAP = buildModelClassElementInfoMap();
private static final Map<String, Class<? extends Resource>> RESOURCE_TYPE_MAP = buildResourceTypeMap();
private static final Set<Class<? extends Resource>> CONCRETE_RESOURCE_TYPES = getResourceTypes().stream()
.filter(rt -> !isAbstract(rt))
.collect(Collectors.toSet());
private static final Map<Class<?>, Set<Constraint>> MODEL_CLASS_CONSTRAINT_MAP = buildModelClassConstraintMap();
// LinkedHashSet is used just to preserve the order, for convenience only
private static final Set<Class<? extends Element>> CHOICE_ELEMENT_TYPES = new LinkedHashSet<>(Arrays.asList(
Base64Binary.class,
com.ibm.fhir.model.type.Boolean.class,
Canonical.class,
Code.class,
Date.class,
DateTime.class,
Decimal.class,
Id.class,
Instant.class,
com.ibm.fhir.model.type.Integer.class,
Markdown.class,
Oid.class,
PositiveInt.class,
com.ibm.fhir.model.type.String.class,
Time.class,
UnsignedInt.class,
Uri.class,
Url.class,
Uuid.class,
Address.class,
Age.class,
Annotation.class,
Attachment.class,
CodeableConcept.class,
Coding.class,
ContactPoint.class,
Count.class,
Distance.class,
Duration.class,
HumanName.class,
Identifier.class,
Money.class,
MoneyQuantity.class, // profiled type
Period.class,
Quantity.class,
Range.class,
Ratio.class,
Reference.class,
SampledData.class,
SimpleQuantity.class, // profiled type
Signature.class,
Timing.class,
ContactDetail.class,
Contributor.class,
DataRequirement.class,
Expression.class,
ParameterDefinition.class,
RelatedArtifact.class,
TriggerDefinition.class,
UsageContext.class,
Dosage.class,
Meta.class));
Base64Binary.class,
com.ibm.fhir.model.type.Boolean.class,
Canonical.class,
Code.class,
Date.class,
DateTime.class,
Decimal.class,
Id.class,
Instant.class,
com.ibm.fhir.model.type.Integer.class,
Markdown.class,
Oid.class,
PositiveInt.class,
com.ibm.fhir.model.type.String.class,
Time.class,
UnsignedInt.class,
Uri.class,
Url.class,
Uuid.class,
Address.class,
Age.class,
Annotation.class,
Attachment.class,
CodeableConcept.class,
Coding.class,
ContactPoint.class,
Count.class,
Distance.class,
Duration.class,
HumanName.class,
Identifier.class,
Money.class,
MoneyQuantity.class, // profiled type
Period.class,
Quantity.class,
Range.class,
Ratio.class,
Reference.class,
SampledData.class,
SimpleQuantity.class, // profiled type
Signature.class,
Timing.class,
ContactDetail.class,
Contributor.class,
DataRequirement.class,
Expression.class,
ParameterDefinition.class,
RelatedArtifact.class,
TriggerDefinition.class,
UsageContext.class,
Dosage.class,
Meta.class));
private static final Set<Class<? extends Element>> DATA_TYPES;
static {
// LinkedHashSet is used just to preserve the order, for convenience only
Expand Down Expand Up @@ -645,6 +648,17 @@ public static Collection<Class<? extends Resource>> getResourceTypes() {
return RESOURCE_TYPE_MAP.values();
}

/**
* @return a collection of FHIR resource type model classes
*/
public static Collection<Class<? extends Resource>> getResourceTypes(boolean includeAbstractTypes) {
if (includeAbstractTypes) {
return RESOURCE_TYPE_MAP.values();
} else {
return CONCRETE_RESOURCE_TYPES;
}
}

/**
* @return the set of classes for the FHIR elements
*/
Expand Down Expand Up @@ -865,6 +879,15 @@ public static boolean isResourceType(Class<?> modelClass) {
return Resource.class.isAssignableFrom(modelClass);
}

/**
* @param modelClass
* a model class which represents a FHIR resource or element
* @return true if {@code modelClass} is an abstract FHIR model class; otherwise false
*/
public static boolean isAbstract(Class<?> modelClass) {
return Modifier.isAbstract(modelClass.getModifiers());
}

/**
* @param name
* the resource type name in titlecase to match the corresponding model class name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import com.ibm.fhir.model.resource.CapabilityStatement.Rest.Resource.Operation;
import com.ibm.fhir.model.resource.DomainResource;
import com.ibm.fhir.model.resource.OperationDefinition;
import com.ibm.fhir.model.resource.Resource;
import com.ibm.fhir.model.resource.SearchParameter;
import com.ibm.fhir.model.type.Canonical;
import com.ibm.fhir.model.type.Code;
Expand Down Expand Up @@ -98,7 +99,9 @@ public class Capabilities extends FHIRResource {
private static final String BASE_CAPABILITY_URL = "http://hl7.org/fhir/CapabilityStatement/base";
private static final String BASE_2_CAPABILITY_URL = "http://hl7.org/fhir/CapabilityStatement/base2";
private static final List<String> ALL_INTERACTIONS = Arrays.asList("create", "read", "vread", "update", "patch", "delete", "history", "search");
private static final List<ResourceType.ValueSet> ALL_RESOURCE_TYPES = Arrays.asList(ResourceType.ValueSet.values());
private static final List<ResourceType.ValueSet> ALL_RESOURCE_TYPES = ModelSupport.getResourceTypes(false).stream()
.map(rt -> ResourceType.ValueSet.from(rt.getSimpleName()))
.collect(Collectors.toList());

// Error Messages
private static final String ERROR_MSG = "Caught exception while processing 'metadata' request.";
Expand Down Expand Up @@ -450,10 +453,14 @@ private List<ResourceType.ValueSet> getSupportedResourceTypes(PropertyGroup rsrc
List<PropertyEntry> rsrcsEntries = rsrcsGroup.getProperties();
if (rsrcsEntries != null && !rsrcsEntries.isEmpty()) {
for (PropertyEntry rsrcsEntry : rsrcsEntries) {

String name = rsrcsEntry.getName();
// Ensure we skip over the special property "open" and process only the others
if (!FHIRConfiguration.PROPERTY_FIELD_RESOURCES_OPEN.equals(rsrcsEntry.getName())) {
resourceTypes.add(ResourceType.ValueSet.from(rsrcsEntry.getName()));
if (!FHIRConfiguration.PROPERTY_FIELD_RESOURCES_OPEN.equals(name)) {
// Skip the abstract types Resource and DomainResource
if (!Resource.class.equals(ModelSupport.getResourceType(name)) &&
!DomainResource.class.equals(ModelSupport.getResourceType(name))) {
resourceTypes.add(ResourceType.ValueSet.from(name));
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import static com.ibm.fhir.server.util.IssueTypeToHttpStatusMapper.issueListToStatus;

import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
Expand Down Expand Up @@ -107,6 +108,10 @@ private JsonObject buildSmartConfig() throws Exception {
revokeURLTemplate = FHIRConfigHelper.getStringProperty(PROPERTY_SECURITY_OAUTH_REVOKE_URL, "");
supportedScopes = FHIRConfigHelper.getStringListProperty(PROPERTY_SECURITY_OAUTH_SMART_SCOPES);
capabilities = FHIRConfigHelper.getStringListProperty(PROPERTY_SECURITY_OAUTH_SMART_CAPABILITIES);
if (capabilities == null) {
// Set an empty list since capabilities is a required field
capabilities = new ArrayList<>();
}
} catch (Exception e) {
log.log(Level.SEVERE, "An error occurred while reading the OAuth/SMART properties", e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* (C) Copyright IBM Corp. 2020
*
* SPDX-License-Identifier: Apache-2.0
*/
package com.ibm.fhir.server.resources;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;

import java.util.List;
import java.util.Optional;

import javax.ws.rs.core.Response;

import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import com.ibm.fhir.config.FHIRConfiguration;
import com.ibm.fhir.config.FHIRRequestContext;
import com.ibm.fhir.exception.FHIRException;
import com.ibm.fhir.model.resource.CapabilityStatement;
import com.ibm.fhir.model.resource.CapabilityStatement.Rest.Resource.Interaction;
import com.ibm.fhir.model.type.code.ResourceType;
import com.ibm.fhir.server.test.MockHttpServletRequest;
import com.ibm.fhir.server.test.MockServletContext;

public class CapabilitiesTest {

@BeforeClass
void setup() {
FHIRConfiguration.setConfigHome("target/test-classes");
}

@AfterClass
void tearDown() throws FHIRException {
FHIRConfiguration.setConfigHome("");
FHIRRequestContext.get().setTenantId("default");
}

@Test
void testBuildCapabilityStatement_resources_omitted() throws Exception {
FHIRRequestContext.get().setTenantId("omitted");
FHIRRequestContext.get().setOriginalRequestUri("http://example.com/metadata");
CapabilitiesChild c = new CapabilitiesChild();

Response capabilities = c.capabilities();
CapabilityStatement capabilityStatement = capabilities.readEntity(CapabilityStatement.class);

assertEquals(capabilityStatement.getRest().size(), 1, "Number of REST Elements");
CapabilityStatement.Rest restDefinition = capabilityStatement.getRest().get(0);

assertRestDefinition(restDefinition, 146, 8, 8);
}

@Test
void testBuildCapabilityStatement_resources_empty() throws Exception {
FHIRRequestContext.get().setTenantId("empty");
FHIRRequestContext.get().setOriginalRequestUri("http://example.com/metadata");
CapabilitiesChild c = new CapabilitiesChild();

Response capabilities = c.capabilities();
CapabilityStatement capabilityStatement = capabilities.readEntity(CapabilityStatement.class);

assertEquals(capabilityStatement.getRest().size(), 1, "Number of REST Elements");
CapabilityStatement.Rest restDefinition = capabilityStatement.getRest().get(0);

assertRestDefinition(restDefinition, 146, 0, 0);
}

@Test
void testBuildCapabilityStatement_resources_filtered() throws Exception {
FHIRRequestContext.get().setTenantId("smart-enabled");
FHIRRequestContext.get().setOriginalRequestUri("http://example.com/metadata");
CapabilitiesChild c = new CapabilitiesChild();

Response capabilities = c.capabilities();
CapabilityStatement capabilityStatement = capabilities.readEntity(CapabilityStatement.class);

assertEquals(capabilityStatement.getRest().size(), 1, "Number of REST Elements");
CapabilityStatement.Rest restDefinition = capabilityStatement.getRest().get(0);

assertRestDefinition(restDefinition, 2, 2, 4);
}

private void assertRestDefinition(CapabilityStatement.Rest restDefinition, int numOfResources, int patientInteractions, int practitionerInteractions) {
assertEquals(restDefinition.getResource().size(), numOfResources, "Number of supported resources");
assertFalse(restDefinition.getResource().stream().anyMatch(r -> r.getType().getValueAsEnumConstant() == ResourceType.ValueSet.RESOURCE));
assertFalse(restDefinition.getResource().stream().anyMatch(r -> r.getType().getValueAsEnumConstant() == ResourceType.ValueSet.DOMAIN_RESOURCE));

assertInteractions(restDefinition, ResourceType.ValueSet.PATIENT, patientInteractions);
assertInteractions(restDefinition, ResourceType.ValueSet.PRACTITIONER, practitionerInteractions);
}

private void assertInteractions(CapabilityStatement.Rest restDefinition, ResourceType.ValueSet resourceType, int numOfInteractions) {
Optional<CapabilityStatement.Rest.Resource> resource = restDefinition.getResource().stream()
.filter(r -> r.getType().getValueAsEnumConstant() == resourceType)
.findFirst();
assertTrue(resource.isPresent());

List<Interaction> interactions = resource.get().getInteraction();
assertEquals(interactions.size(), numOfInteractions, "Number of supported interactions for the Patient resource type");
}

/**
* This class is required because Capabilities uses a few protected fields
* that are normally injected by JAX-RS and so this is the only way to set them.
*/
private static class CapabilitiesChild extends Capabilities {
public CapabilitiesChild() throws Exception {
super();
this.context = new MockServletContext();
}

@Override
public Response capabilities() {
httpServletRequest = new MockHttpServletRequest();
return super.capabilities();
}
}
}
Loading

0 comments on commit ce32f11

Please sign in to comment.