|
|
|
@ -4,20 +4,19 @@ import ca.uhn.fhir.context.FhirContext;
|
|
|
|
|
import ca.uhn.fhir.interceptor.api.Hook;
|
|
|
|
|
import ca.uhn.fhir.interceptor.api.Pointcut;
|
|
|
|
|
import ca.uhn.fhir.model.api.annotation.Description;
|
|
|
|
|
import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
|
|
|
|
|
import ca.uhn.fhir.rest.annotation.IdParam;
|
|
|
|
|
import ca.uhn.fhir.rest.annotation.Operation;
|
|
|
|
|
import ca.uhn.fhir.rest.annotation.OperationParam;
|
|
|
|
|
import ca.uhn.fhir.rest.annotation.Patch;
|
|
|
|
|
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
|
|
|
|
import ca.uhn.fhir.rest.annotation.*;
|
|
|
|
|
import ca.uhn.fhir.rest.api.Constants;
|
|
|
|
|
import ca.uhn.fhir.rest.api.EncodingEnum;
|
|
|
|
|
import ca.uhn.fhir.rest.api.MethodOutcome;
|
|
|
|
|
import ca.uhn.fhir.rest.api.PatchTypeEnum;
|
|
|
|
|
import ca.uhn.fhir.rest.api.ValidationModeEnum;
|
|
|
|
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
|
|
|
|
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
|
|
|
|
|
import ca.uhn.fhir.rest.server.provider.HashMapResourceProvider;
|
|
|
|
|
import ca.uhn.fhir.rest.server.provider.ServerCapabilityStatementProvider;
|
|
|
|
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
|
|
|
|
import ca.uhn.fhir.test.utilities.HtmlUtil;
|
|
|
|
|
import ca.uhn.fhir.test.utilities.HttpClientExtension;
|
|
|
|
|
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
|
|
|
|
|
import ca.uhn.fhir.util.ExtensionConstants;
|
|
|
|
|
import com.gargoylesoftware.htmlunit.html.DomElement;
|
|
|
|
@ -30,24 +29,10 @@ import org.apache.commons.io.IOUtils;
|
|
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
|
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
|
|
|
|
import org.apache.http.client.methods.HttpGet;
|
|
|
|
|
import org.apache.http.impl.client.CloseableHttpClient;
|
|
|
|
|
import org.apache.http.impl.client.HttpClientBuilder;
|
|
|
|
|
import org.hamcrest.Matchers;
|
|
|
|
|
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
|
|
|
|
import org.hl7.fhir.instance.model.api.IBaseCoding;
|
|
|
|
|
import org.hl7.fhir.instance.model.api.IBaseConformance;
|
|
|
|
|
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
|
|
|
|
import org.hl7.fhir.instance.model.api.IBaseReference;
|
|
|
|
|
import org.hl7.fhir.instance.model.api.IIdType;
|
|
|
|
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
|
|
|
|
import org.hl7.fhir.r4.model.CapabilityStatement;
|
|
|
|
|
import org.hl7.fhir.r4.model.DecimalType;
|
|
|
|
|
import org.hl7.fhir.r4.model.Observation;
|
|
|
|
|
import org.hl7.fhir.r4.model.Patient;
|
|
|
|
|
import org.junit.jupiter.api.AfterEach;
|
|
|
|
|
import org.junit.jupiter.api.BeforeEach;
|
|
|
|
|
import org.junit.jupiter.api.Order;
|
|
|
|
|
import org.junit.jupiter.api.Test;
|
|
|
|
|
import org.hl7.fhir.instance.model.api.*;
|
|
|
|
|
import org.hl7.fhir.r5.model.ActorDefinition;
|
|
|
|
|
import org.junit.jupiter.api.*;
|
|
|
|
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
@ -56,42 +41,95 @@ import javax.servlet.http.HttpServletRequest;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.net.URL;
|
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.Arrays;
|
|
|
|
|
import java.util.Collections;
|
|
|
|
|
import java.util.HashSet;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.*;
|
|
|
|
|
|
|
|
|
|
import static org.hamcrest.CoreMatchers.not;
|
|
|
|
|
import static org.hamcrest.MatcherAssert.assertThat;
|
|
|
|
|
import static org.hamcrest.Matchers.containsString;
|
|
|
|
|
import static org.hamcrest.Matchers.empty;
|
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
|
|
|
|
import static org.junit.jupiter.api.Assertions.*;
|
|
|
|
|
|
|
|
|
|
public class OpenApiInterceptorTest {
|
|
|
|
|
|
|
|
|
|
private static final Logger ourLog = LoggerFactory.getLogger(OpenApiInterceptorTest.class);
|
|
|
|
|
private final FhirContext myFhirContext = FhirContext.forR4Cached();
|
|
|
|
|
@RegisterExtension
|
|
|
|
|
@Order(0)
|
|
|
|
|
protected RestfulServerExtension myServer = new RestfulServerExtension(myFhirContext)
|
|
|
|
|
.withServletPath("/fhir/*")
|
|
|
|
|
.withServer(t -> t.registerProvider(new HashMapResourceProvider<>(myFhirContext, Patient.class)))
|
|
|
|
|
.withServer(t -> t.registerProvider(new HashMapResourceProvider<>(myFhirContext, Observation.class)))
|
|
|
|
|
.withServer(t -> t.registerProvider(new MyLastNProvider()))
|
|
|
|
|
.withServer(t -> t.registerInterceptor(new ResponseHighlighterInterceptor()));
|
|
|
|
|
private CloseableHttpClient myClient;
|
|
|
|
|
|
|
|
|
|
@Nested
|
|
|
|
|
class R4 extends BaseOpenApiInterceptorTest {
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
FhirContext getContext() {
|
|
|
|
|
return FhirContext.forR4Cached();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void testSwaggerUiWithResourceCounts() throws IOException {
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new AddResourceCountsInterceptor());
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new OpenApiInterceptor());
|
|
|
|
|
|
|
|
|
|
String url = "http://localhost:" + myServer.getPort() + "/fhir/swagger-ui/";
|
|
|
|
|
String resp = fetchSwaggerUi(url);
|
|
|
|
|
List<String> buttonTexts = parsePageButtonTexts(resp, url);
|
|
|
|
|
assertThat(buttonTexts.toString(), buttonTexts, Matchers.contains("All", "System Level Operations", "Patient 2", "OperationDefinition 1", "Observation 0"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void testSwaggerUiWithResourceCounts_OneResourceOnly() throws IOException {
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new AddResourceCountsInterceptor("OperationDefinition"));
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new OpenApiInterceptor());
|
|
|
|
|
|
|
|
|
|
String url = "http://localhost:" + myServer.getPort() + "/fhir/swagger-ui/";
|
|
|
|
|
String resp = fetchSwaggerUi(url);
|
|
|
|
|
List<String> buttonTexts = parsePageButtonTexts(resp, url);
|
|
|
|
|
assertThat(buttonTexts.toString(), buttonTexts, Matchers.contains("All", "System Level Operations", "OperationDefinition 1", "Observation", "Patient"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Nested
|
|
|
|
|
class R5 extends BaseOpenApiInterceptorTest {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A provider that uses a resource type not present in R4
|
|
|
|
|
*/
|
|
|
|
|
private MyTypeLevelActorDefinitionProviderR5 myActorDefinitionProvider = new MyTypeLevelActorDefinitionProviderR5();
|
|
|
|
|
|
|
|
|
|
@BeforeEach
|
|
|
|
|
public void before() {
|
|
|
|
|
myClient = HttpClientBuilder.create().build();
|
|
|
|
|
void beforeEach() {
|
|
|
|
|
myServer.registerProvider(myActorDefinitionProvider);
|
|
|
|
|
ServerCapabilityStatementProvider a = (ServerCapabilityStatementProvider) myServer.getRestfulServer().getServerConformanceProvider();
|
|
|
|
|
myServer.getRestfulServer().getServerConformanceProvider();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@AfterEach
|
|
|
|
|
public void after() throws IOException {
|
|
|
|
|
myClient.close();
|
|
|
|
|
void afterEach() {
|
|
|
|
|
myServer.unregisterProvider(myActorDefinitionProvider);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
FhirContext getContext() {
|
|
|
|
|
return FhirContext.forR5Cached();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings("JUnitMalformedDeclaration")
|
|
|
|
|
abstract static class BaseOpenApiInterceptorTest {
|
|
|
|
|
|
|
|
|
|
@RegisterExtension
|
|
|
|
|
@Order(0)
|
|
|
|
|
protected RestfulServerExtension myServer = new RestfulServerExtension(getContext())
|
|
|
|
|
.withServletPath("/fhir/*")
|
|
|
|
|
.withServer(t -> t.registerProvider(new HashMapResourceProvider<>(getContext(), getContext().getResourceDefinition("Patient").getImplementingClass())))
|
|
|
|
|
.withServer(t -> t.registerProvider(new HashMapResourceProvider<>(getContext(), getContext().getResourceDefinition("Observation").getImplementingClass())))
|
|
|
|
|
.withServer(t -> t.registerProvider(new MySystemLevelOperationProvider()))
|
|
|
|
|
.withServer(t -> t.registerInterceptor(new ResponseHighlighterInterceptor()));
|
|
|
|
|
@RegisterExtension
|
|
|
|
|
private HttpClientExtension myClient = new HttpClientExtension();
|
|
|
|
|
|
|
|
|
|
abstract FhirContext getContext();
|
|
|
|
|
|
|
|
|
|
@AfterEach
|
|
|
|
|
public void after() {
|
|
|
|
|
myServer.getRestfulServer().getInterceptorService().unregisterAllInterceptors();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -169,18 +207,6 @@ public class OpenApiInterceptorTest {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void testSwaggerUiWithResourceCounts() throws IOException {
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new AddResourceCountsInterceptor());
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new OpenApiInterceptor());
|
|
|
|
|
|
|
|
|
|
String url = "http://localhost:" + myServer.getPort() + "/fhir/swagger-ui/";
|
|
|
|
|
String resp = fetchSwaggerUi(url);
|
|
|
|
|
List<String> buttonTexts = parsePageButtonTexts(resp, url);
|
|
|
|
|
assertThat(buttonTexts.toString(), buttonTexts, Matchers.contains("All", "System Level Operations", "Patient 2", "OperationDefinition 1", "Observation 0"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void testSwaggerUiWithCopyright() throws IOException {
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new AddResourceCountsInterceptor());
|
|
|
|
@ -226,7 +252,7 @@ public class OpenApiInterceptorTest {
|
|
|
|
|
assertEquals(removeCtrlR(expected), removeCtrlR(resp));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected String removeCtrlR (String source) {
|
|
|
|
|
protected String removeCtrlR(String source) {
|
|
|
|
|
String result = source;
|
|
|
|
|
if (source != null) {
|
|
|
|
|
result = StringUtils.remove(source, '\r');
|
|
|
|
@ -249,17 +275,6 @@ public class OpenApiInterceptorTest {
|
|
|
|
|
assertThat(buttonTexts.toString(), buttonTexts, empty());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void testSwaggerUiWithResourceCounts_OneResourceOnly() throws IOException {
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new AddResourceCountsInterceptor("OperationDefinition"));
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new OpenApiInterceptor());
|
|
|
|
|
|
|
|
|
|
String url = "http://localhost:" + myServer.getPort() + "/fhir/swagger-ui/";
|
|
|
|
|
String resp = fetchSwaggerUi(url);
|
|
|
|
|
List<String> buttonTexts = parsePageButtonTexts(resp, url);
|
|
|
|
|
assertThat(buttonTexts.toString(), buttonTexts, Matchers.contains("All", "System Level Operations", "OperationDefinition 1", "Observation", "Patient"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void testRemoveTrailingSlash() {
|
|
|
|
|
OpenApiInterceptor interceptor = new OpenApiInterceptor();
|
|
|
|
@ -290,7 +305,7 @@ public class OpenApiInterceptorTest {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private String fetchSwaggerUi(String url) throws IOException {
|
|
|
|
|
protected String fetchSwaggerUi(String url) throws IOException {
|
|
|
|
|
String resp;
|
|
|
|
|
HttpGet get = new HttpGet(url);
|
|
|
|
|
try (CloseableHttpResponse response = myClient.execute(get)) {
|
|
|
|
@ -301,7 +316,7 @@ public class OpenApiInterceptorTest {
|
|
|
|
|
return resp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<String> parsePageButtonTexts(String resp, String url) throws IOException {
|
|
|
|
|
protected List<String> parsePageButtonTexts(String resp, String url) throws IOException {
|
|
|
|
|
HtmlPage html = HtmlUtil.parseAsHtml(resp, new URL(url));
|
|
|
|
|
HtmlDivision pageButtons = (HtmlDivision) html.getElementById("pageButtons");
|
|
|
|
|
if (pageButtons == null) {
|
|
|
|
@ -326,28 +341,41 @@ public class OpenApiInterceptorTest {
|
|
|
|
|
|
|
|
|
|
@Hook(Pointcut.SERVER_CAPABILITY_STATEMENT_GENERATED)
|
|
|
|
|
public void capabilityStatementGenerated(IBaseConformance theCapabilityStatement) {
|
|
|
|
|
CapabilityStatement cs = (CapabilityStatement) theCapabilityStatement;
|
|
|
|
|
if (theCapabilityStatement instanceof org.hl7.fhir.r4.model.CapabilityStatement) {
|
|
|
|
|
org.hl7.fhir.r4.model.CapabilityStatement cs = (org.hl7.fhir.r4.model.CapabilityStatement) theCapabilityStatement;
|
|
|
|
|
cs.setCopyright("This server is copyright **Example Org** 2021");
|
|
|
|
|
|
|
|
|
|
int numResources = cs.getRestFirstRep().getResource().size();
|
|
|
|
|
for (int i = 0; i < numResources; i++) {
|
|
|
|
|
|
|
|
|
|
CapabilityStatement.CapabilityStatementRestResourceComponent restResource = cs.getRestFirstRep().getResource().get(i);
|
|
|
|
|
org.hl7.fhir.r4.model.CapabilityStatement.CapabilityStatementRestResourceComponent restResource = cs.getRestFirstRep().getResource().get(i);
|
|
|
|
|
if (!myResourceNamesToAddTo.isEmpty() && !myResourceNamesToAddTo.contains(restResource.getType())) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
restResource.addExtension(
|
|
|
|
|
ExtensionConstants.CONF_RESOURCE_COUNT,
|
|
|
|
|
new DecimalType(i) // reverse order
|
|
|
|
|
new org.hl7.fhir.r4.model.DecimalType(i) // reverse order
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
org.hl7.fhir.r5.model.CapabilityStatement cs = (org.hl7.fhir.r5.model.CapabilityStatement) theCapabilityStatement;
|
|
|
|
|
cs.setCopyright("This server is copyright **Example Org** 2021");
|
|
|
|
|
int numResources = cs.getRestFirstRep().getResource().size();
|
|
|
|
|
for (int i = 0; i < numResources; i++) {
|
|
|
|
|
org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceComponent restResource = cs.getRestFirstRep().getResource().get(i);
|
|
|
|
|
if (!myResourceNamesToAddTo.isEmpty() && !myResourceNamesToAddTo.contains(restResource.getType())) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
restResource.addExtension(
|
|
|
|
|
ExtensionConstants.CONF_RESOURCE_COUNT,
|
|
|
|
|
new org.hl7.fhir.r5.model.DecimalType(i) // reverse order
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static class MyLastNProvider {
|
|
|
|
|
public static class MySystemLevelOperationProvider {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Description(value = "LastN Description", shortDefinition = "LastN Short")
|
|
|
|
@ -375,11 +403,35 @@ public class OpenApiInterceptorTest {
|
|
|
|
|
throw new IllegalStateException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Patch(type = Patient.class)
|
|
|
|
|
@Patch(typeName = "Patient")
|
|
|
|
|
public MethodOutcome patch(HttpServletRequest theRequest, @IdParam IIdType theId, @ConditionalUrlParam String theConditionalUrl, RequestDetails theRequestDetails, @ResourceParam String theBody, PatchTypeEnum thePatchType, @ResourceParam IBaseParameters theRequestBody) {
|
|
|
|
|
throw new IllegalStateException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static class MyTypeLevelActorDefinitionProviderR5 extends HashMapResourceProvider<ActorDefinition> {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Constructor
|
|
|
|
|
*/
|
|
|
|
|
public MyTypeLevelActorDefinitionProviderR5() {
|
|
|
|
|
super(FhirContext.forR5Cached(), ActorDefinition.class);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Validate
|
|
|
|
|
public MethodOutcome validate(
|
|
|
|
|
@ResourceParam IBaseResource theResource,
|
|
|
|
|
@ResourceParam String theRawResource,
|
|
|
|
|
@ResourceParam EncodingEnum theEncoding,
|
|
|
|
|
@Validate.Mode ValidationModeEnum theMode,
|
|
|
|
|
@Validate.Profile String theProfile,
|
|
|
|
|
RequestDetails theRequestDetails) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|