|
|
|
@ -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,298 +41,341 @@ 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;
|
|
|
|
|
|
|
|
|
|
@BeforeEach
|
|
|
|
|
public void before() {
|
|
|
|
|
myClient = HttpClientBuilder.create().build();
|
|
|
|
|
}
|
|
|
|
|
@Nested
|
|
|
|
|
class R4 extends BaseOpenApiInterceptorTest {
|
|
|
|
|
|
|
|
|
|
@AfterEach
|
|
|
|
|
public void after() throws IOException {
|
|
|
|
|
myClient.close();
|
|
|
|
|
myServer.getRestfulServer().getInterceptorService().unregisterAllInterceptors();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void testFetchSwagger() throws IOException {
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new OpenApiInterceptor());
|
|
|
|
|
|
|
|
|
|
String resp;
|
|
|
|
|
HttpGet get = new HttpGet("http://localhost:" + myServer.getPort() + "/fhir/metadata?_pretty=true");
|
|
|
|
|
try (CloseableHttpResponse response = myClient.execute(get)) {
|
|
|
|
|
resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
|
|
|
|
ourLog.info("CapabilityStatement: {}", resp);
|
|
|
|
|
@Override
|
|
|
|
|
FhirContext getContext() {
|
|
|
|
|
return FhirContext.forR4Cached();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get = new HttpGet("http://localhost:" + myServer.getPort() + "/fhir/api-docs");
|
|
|
|
|
try (CloseableHttpResponse response = myClient.execute(get)) {
|
|
|
|
|
resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
|
|
|
|
ourLog.info("Response: {}", response.getStatusLine());
|
|
|
|
|
ourLog.debug("Response: {}", resp);
|
|
|
|
|
@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"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OpenAPI parsed = Yaml.mapper().readValue(resp, OpenAPI.class);
|
|
|
|
|
@Test
|
|
|
|
|
public void testSwaggerUiWithResourceCounts_OneResourceOnly() throws IOException {
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new AddResourceCountsInterceptor("OperationDefinition"));
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new OpenApiInterceptor());
|
|
|
|
|
|
|
|
|
|
PathItem fooOpPath = parsed.getPaths().get("/$foo-op");
|
|
|
|
|
assertNull(fooOpPath.getGet());
|
|
|
|
|
assertNotNull(fooOpPath.getPost());
|
|
|
|
|
assertEquals("Foo Op Description", fooOpPath.getPost().getDescription());
|
|
|
|
|
assertEquals("Foo Op Short", fooOpPath.getPost().getSummary());
|
|
|
|
|
|
|
|
|
|
PathItem lastNPath = parsed.getPaths().get("/Observation/$lastn");
|
|
|
|
|
assertNotNull(lastNPath.getPost());
|
|
|
|
|
assertEquals("LastN Description", lastNPath.getPost().getDescription());
|
|
|
|
|
assertEquals("LastN Short", lastNPath.getPost().getSummary());
|
|
|
|
|
assertNull(lastNPath.getPost().getParameters());
|
|
|
|
|
assertNotNull(lastNPath.getPost().getRequestBody());
|
|
|
|
|
assertNotNull(lastNPath.getGet());
|
|
|
|
|
assertEquals("LastN Description", lastNPath.getGet().getDescription());
|
|
|
|
|
assertEquals("LastN Short", lastNPath.getGet().getSummary());
|
|
|
|
|
assertEquals(4, lastNPath.getGet().getParameters().size());
|
|
|
|
|
assertEquals("Subject description", lastNPath.getGet().getParameters().get(0).getDescription());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void testRedirectFromBaseUrl() throws IOException {
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new OpenApiInterceptor());
|
|
|
|
|
|
|
|
|
|
HttpGet get;
|
|
|
|
|
|
|
|
|
|
get = new HttpGet("http://localhost:" + myServer.getPort() + "/fhir/");
|
|
|
|
|
try (CloseableHttpResponse response = myClient.execute(get)) {
|
|
|
|
|
assertEquals(400, response.getStatusLine().getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get = new HttpGet("http://localhost:" + myServer.getPort() + "/fhir/");
|
|
|
|
|
get.addHeader(Constants.HEADER_ACCEPT, Constants.CT_HTML);
|
|
|
|
|
try (CloseableHttpResponse response = myClient.execute(get)) {
|
|
|
|
|
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
|
|
|
|
ourLog.info("Response: {}", response);
|
|
|
|
|
ourLog.info("Response: {}", responseString);
|
|
|
|
|
assertEquals(200, response.getStatusLine().getStatusCode());
|
|
|
|
|
assertThat(responseString, containsString("<title>Swagger UI</title>"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get = new HttpGet("http://localhost:" + myServer.getPort() + "/fhir/?foo=foo");
|
|
|
|
|
get.addHeader(Constants.HEADER_ACCEPT, Constants.CT_HTML);
|
|
|
|
|
try (CloseableHttpResponse response = myClient.execute(get)) {
|
|
|
|
|
assertEquals(400, response.getStatusLine().getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get = new HttpGet("http://localhost:" + myServer.getPort() + "/fhir?foo=foo");
|
|
|
|
|
get.addHeader(Constants.HEADER_ACCEPT, Constants.CT_HTML);
|
|
|
|
|
try (CloseableHttpResponse response = myClient.execute(get)) {
|
|
|
|
|
assertEquals(400, response.getStatusLine().getStatusCode());
|
|
|
|
|
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 {
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void testSwaggerUiWithResourceCounts() throws IOException {
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new AddResourceCountsInterceptor());
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new OpenApiInterceptor());
|
|
|
|
|
/**
|
|
|
|
|
* A provider that uses a resource type not present in R4
|
|
|
|
|
*/
|
|
|
|
|
private MyTypeLevelActorDefinitionProviderR5 myActorDefinitionProvider = new MyTypeLevelActorDefinitionProviderR5();
|
|
|
|
|
|
|
|
|
|
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"));
|
|
|
|
|
@BeforeEach
|
|
|
|
|
void beforeEach() {
|
|
|
|
|
myServer.registerProvider(myActorDefinitionProvider);
|
|
|
|
|
ServerCapabilityStatementProvider a = (ServerCapabilityStatementProvider) myServer.getRestfulServer().getServerConformanceProvider();
|
|
|
|
|
myServer.getRestfulServer().getServerConformanceProvider();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@AfterEach
|
|
|
|
|
void afterEach() {
|
|
|
|
|
myServer.unregisterProvider(myActorDefinitionProvider);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
FhirContext getContext() {
|
|
|
|
|
return FhirContext.forR5Cached();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void testSwaggerUiWithCopyright() 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);
|
|
|
|
|
assertThat(resp, resp, containsString("<p>This server is copyright <strong>Example Org</strong> 2021</p>"));
|
|
|
|
|
assertThat(resp, resp, not(containsString("swagger-ui-custom.css")));
|
|
|
|
|
}
|
|
|
|
|
@SuppressWarnings("JUnitMalformedDeclaration")
|
|
|
|
|
abstract static class BaseOpenApiInterceptorTest {
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void testSwaggerUiWithNoBannerUrl() throws IOException {
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new AddResourceCountsInterceptor());
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new OpenApiInterceptor().setBannerImage(""));
|
|
|
|
|
@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();
|
|
|
|
|
|
|
|
|
|
String url = "http://localhost:" + myServer.getPort() + "/fhir/swagger-ui/";
|
|
|
|
|
String resp = fetchSwaggerUi(url);
|
|
|
|
|
assertThat(resp, resp, not(containsString("img id=\"banner_img\"")));
|
|
|
|
|
}
|
|
|
|
|
abstract FhirContext getContext();
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void testSwaggerUiWithCustomStylesheet() throws IOException {
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new AddResourceCountsInterceptor());
|
|
|
|
|
@AfterEach
|
|
|
|
|
public void after() {
|
|
|
|
|
myServer.getRestfulServer().getInterceptorService().unregisterAllInterceptors();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OpenApiInterceptor interceptor = new OpenApiInterceptor();
|
|
|
|
|
interceptor.setCssText("BODY {\nfont-size: 1.1em;\n}");
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(interceptor);
|
|
|
|
|
@Test
|
|
|
|
|
public void testFetchSwagger() throws IOException {
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new OpenApiInterceptor());
|
|
|
|
|
|
|
|
|
|
// Fetch Swagger UI HTML
|
|
|
|
|
String url = "http://localhost:" + myServer.getPort() + "/fhir/swagger-ui/";
|
|
|
|
|
String resp = fetchSwaggerUi(url);
|
|
|
|
|
assertThat(resp, resp, containsString("<link rel=\"stylesheet\" type=\"text/css\" href=\"./swagger-ui-custom.css\"/>"));
|
|
|
|
|
|
|
|
|
|
// Fetch Custom CSS
|
|
|
|
|
url = "http://localhost:" + myServer.getPort() + "/fhir/swagger-ui/swagger-ui-custom.css";
|
|
|
|
|
resp = fetchSwaggerUi(url);
|
|
|
|
|
String expected = """
|
|
|
|
|
BODY {
|
|
|
|
|
font-size: 1.1em;
|
|
|
|
|
String resp;
|
|
|
|
|
HttpGet get = new HttpGet("http://localhost:" + myServer.getPort() + "/fhir/metadata?_pretty=true");
|
|
|
|
|
try (CloseableHttpResponse response = myClient.execute(get)) {
|
|
|
|
|
resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
|
|
|
|
ourLog.info("CapabilityStatement: {}", resp);
|
|
|
|
|
}
|
|
|
|
|
""";
|
|
|
|
|
assertEquals(removeCtrlR(expected), removeCtrlR(resp));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected String removeCtrlR (String source) {
|
|
|
|
|
String result = source;
|
|
|
|
|
if (source != null) {
|
|
|
|
|
result = StringUtils.remove(source, '\r');
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
get = new HttpGet("http://localhost:" + myServer.getPort() + "/fhir/api-docs");
|
|
|
|
|
try (CloseableHttpResponse response = myClient.execute(get)) {
|
|
|
|
|
resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
|
|
|
|
ourLog.info("Response: {}", response.getStatusLine());
|
|
|
|
|
ourLog.debug("Response: {}", resp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void testSwaggerUiNotPaged() throws IOException {
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new AddResourceCountsInterceptor());
|
|
|
|
|
OpenAPI parsed = Yaml.mapper().readValue(resp, OpenAPI.class);
|
|
|
|
|
|
|
|
|
|
OpenApiInterceptor interceptor = new OpenApiInterceptor();
|
|
|
|
|
interceptor.setUseResourcePages(false);
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(interceptor);
|
|
|
|
|
PathItem fooOpPath = parsed.getPaths().get("/$foo-op");
|
|
|
|
|
assertNull(fooOpPath.getGet());
|
|
|
|
|
assertNotNull(fooOpPath.getPost());
|
|
|
|
|
assertEquals("Foo Op Description", fooOpPath.getPost().getDescription());
|
|
|
|
|
assertEquals("Foo Op Short", fooOpPath.getPost().getSummary());
|
|
|
|
|
|
|
|
|
|
// Fetch Swagger UI HTML
|
|
|
|
|
String url = "http://localhost:" + myServer.getPort() + "/fhir/swagger-ui/";
|
|
|
|
|
String resp = fetchSwaggerUi(url);
|
|
|
|
|
List<String> buttonTexts = parsePageButtonTexts(resp, url);
|
|
|
|
|
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();
|
|
|
|
|
String url1 = interceptor.removeTrailingSlash("http://localhost:8000");
|
|
|
|
|
String url2 = interceptor.removeTrailingSlash("http://localhost:8000/");
|
|
|
|
|
String url3 = interceptor.removeTrailingSlash("http://localhost:8000//");
|
|
|
|
|
String expect = "http://localhost:8000";
|
|
|
|
|
assertEquals(expect, url1);
|
|
|
|
|
assertEquals(expect, url2);
|
|
|
|
|
assertEquals(expect, url3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void testRemoveTrailingSlashWithNullUrl() {
|
|
|
|
|
OpenApiInterceptor interceptor = new OpenApiInterceptor();
|
|
|
|
|
String url = interceptor.removeTrailingSlash(null);
|
|
|
|
|
assertEquals(null, url);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void testStandardRedirectScriptIsAccessible() throws IOException {
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new AddResourceCountsInterceptor());
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new OpenApiInterceptor());
|
|
|
|
|
|
|
|
|
|
HttpGet get = new HttpGet("http://localhost:" + myServer.getPort() + "/fhir/swagger-ui/oauth2-redirect.html");
|
|
|
|
|
try (CloseableHttpResponse response = myClient.execute(get)) {
|
|
|
|
|
assertEquals(200, response.getStatusLine().getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private String fetchSwaggerUi(String url) throws IOException {
|
|
|
|
|
String resp;
|
|
|
|
|
HttpGet get = new HttpGet(url);
|
|
|
|
|
try (CloseableHttpResponse response = myClient.execute(get)) {
|
|
|
|
|
resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
|
|
|
|
ourLog.info("Response: {}", response.getStatusLine());
|
|
|
|
|
ourLog.debug("Response: {}", resp);
|
|
|
|
|
}
|
|
|
|
|
return resp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private 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) {
|
|
|
|
|
return Collections.emptyList();
|
|
|
|
|
PathItem lastNPath = parsed.getPaths().get("/Observation/$lastn");
|
|
|
|
|
assertNotNull(lastNPath.getPost());
|
|
|
|
|
assertEquals("LastN Description", lastNPath.getPost().getDescription());
|
|
|
|
|
assertEquals("LastN Short", lastNPath.getPost().getSummary());
|
|
|
|
|
assertNull(lastNPath.getPost().getParameters());
|
|
|
|
|
assertNotNull(lastNPath.getPost().getRequestBody());
|
|
|
|
|
assertNotNull(lastNPath.getGet());
|
|
|
|
|
assertEquals("LastN Description", lastNPath.getGet().getDescription());
|
|
|
|
|
assertEquals("LastN Short", lastNPath.getGet().getSummary());
|
|
|
|
|
assertEquals(4, lastNPath.getGet().getParameters().size());
|
|
|
|
|
assertEquals("Subject description", lastNPath.getGet().getParameters().get(0).getDescription());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<String> buttonTexts = new ArrayList<>();
|
|
|
|
|
for (DomElement next : pageButtons.getChildElements()) {
|
|
|
|
|
buttonTexts.add(next.asNormalizedText());
|
|
|
|
|
}
|
|
|
|
|
return buttonTexts;
|
|
|
|
|
}
|
|
|
|
|
@Test
|
|
|
|
|
public void testRedirectFromBaseUrl() throws IOException {
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new OpenApiInterceptor());
|
|
|
|
|
|
|
|
|
|
HttpGet get;
|
|
|
|
|
|
|
|
|
|
public static class AddResourceCountsInterceptor {
|
|
|
|
|
get = new HttpGet("http://localhost:" + myServer.getPort() + "/fhir/");
|
|
|
|
|
try (CloseableHttpResponse response = myClient.execute(get)) {
|
|
|
|
|
assertEquals(400, response.getStatusLine().getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private final HashSet<String> myResourceNamesToAddTo;
|
|
|
|
|
get = new HttpGet("http://localhost:" + myServer.getPort() + "/fhir/");
|
|
|
|
|
get.addHeader(Constants.HEADER_ACCEPT, Constants.CT_HTML);
|
|
|
|
|
try (CloseableHttpResponse response = myClient.execute(get)) {
|
|
|
|
|
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
|
|
|
|
ourLog.info("Response: {}", response);
|
|
|
|
|
ourLog.info("Response: {}", responseString);
|
|
|
|
|
assertEquals(200, response.getStatusLine().getStatusCode());
|
|
|
|
|
assertThat(responseString, containsString("<title>Swagger UI</title>"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get = new HttpGet("http://localhost:" + myServer.getPort() + "/fhir/?foo=foo");
|
|
|
|
|
get.addHeader(Constants.HEADER_ACCEPT, Constants.CT_HTML);
|
|
|
|
|
try (CloseableHttpResponse response = myClient.execute(get)) {
|
|
|
|
|
assertEquals(400, response.getStatusLine().getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get = new HttpGet("http://localhost:" + myServer.getPort() + "/fhir?foo=foo");
|
|
|
|
|
get.addHeader(Constants.HEADER_ACCEPT, Constants.CT_HTML);
|
|
|
|
|
try (CloseableHttpResponse response = myClient.execute(get)) {
|
|
|
|
|
assertEquals(400, response.getStatusLine().getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public AddResourceCountsInterceptor(String... theResourceNamesToAddTo) {
|
|
|
|
|
myResourceNamesToAddTo = new HashSet<>(Arrays.asList(theResourceNamesToAddTo));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Hook(Pointcut.SERVER_CAPABILITY_STATEMENT_GENERATED)
|
|
|
|
|
public void capabilityStatementGenerated(IBaseConformance theCapabilityStatement) {
|
|
|
|
|
CapabilityStatement cs = (CapabilityStatement) theCapabilityStatement;
|
|
|
|
|
cs.setCopyright("This server is copyright **Example Org** 2021");
|
|
|
|
|
@Test
|
|
|
|
|
public void testSwaggerUiWithCopyright() throws IOException {
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new AddResourceCountsInterceptor());
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new OpenApiInterceptor());
|
|
|
|
|
|
|
|
|
|
int numResources = cs.getRestFirstRep().getResource().size();
|
|
|
|
|
for (int i = 0; i < numResources; i++) {
|
|
|
|
|
String url = "http://localhost:" + myServer.getPort() + "/fhir/swagger-ui/";
|
|
|
|
|
String resp = fetchSwaggerUi(url);
|
|
|
|
|
assertThat(resp, resp, containsString("<p>This server is copyright <strong>Example Org</strong> 2021</p>"));
|
|
|
|
|
assertThat(resp, resp, not(containsString("swagger-ui-custom.css")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CapabilityStatement.CapabilityStatementRestResourceComponent restResource = cs.getRestFirstRep().getResource().get(i);
|
|
|
|
|
if (!myResourceNamesToAddTo.isEmpty() && !myResourceNamesToAddTo.contains(restResource.getType())) {
|
|
|
|
|
continue;
|
|
|
|
|
@Test
|
|
|
|
|
public void testSwaggerUiWithNoBannerUrl() throws IOException {
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new AddResourceCountsInterceptor());
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new OpenApiInterceptor().setBannerImage(""));
|
|
|
|
|
|
|
|
|
|
String url = "http://localhost:" + myServer.getPort() + "/fhir/swagger-ui/";
|
|
|
|
|
String resp = fetchSwaggerUi(url);
|
|
|
|
|
assertThat(resp, resp, not(containsString("img id=\"banner_img\"")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void testSwaggerUiWithCustomStylesheet() throws IOException {
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new AddResourceCountsInterceptor());
|
|
|
|
|
|
|
|
|
|
OpenApiInterceptor interceptor = new OpenApiInterceptor();
|
|
|
|
|
interceptor.setCssText("BODY {\nfont-size: 1.1em;\n}");
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(interceptor);
|
|
|
|
|
|
|
|
|
|
// Fetch Swagger UI HTML
|
|
|
|
|
String url = "http://localhost:" + myServer.getPort() + "/fhir/swagger-ui/";
|
|
|
|
|
String resp = fetchSwaggerUi(url);
|
|
|
|
|
assertThat(resp, resp, containsString("<link rel=\"stylesheet\" type=\"text/css\" href=\"./swagger-ui-custom.css\"/>"));
|
|
|
|
|
|
|
|
|
|
// Fetch Custom CSS
|
|
|
|
|
url = "http://localhost:" + myServer.getPort() + "/fhir/swagger-ui/swagger-ui-custom.css";
|
|
|
|
|
resp = fetchSwaggerUi(url);
|
|
|
|
|
String expected = """
|
|
|
|
|
BODY {
|
|
|
|
|
font-size: 1.1em;
|
|
|
|
|
}
|
|
|
|
|
""";
|
|
|
|
|
assertEquals(removeCtrlR(expected), removeCtrlR(resp));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
restResource.addExtension(
|
|
|
|
|
ExtensionConstants.CONF_RESOURCE_COUNT,
|
|
|
|
|
new DecimalType(i) // reverse order
|
|
|
|
|
);
|
|
|
|
|
protected String removeCtrlR(String source) {
|
|
|
|
|
String result = source;
|
|
|
|
|
if (source != null) {
|
|
|
|
|
result = StringUtils.remove(source, '\r');
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void testSwaggerUiNotPaged() throws IOException {
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new AddResourceCountsInterceptor());
|
|
|
|
|
|
|
|
|
|
OpenApiInterceptor interceptor = new OpenApiInterceptor();
|
|
|
|
|
interceptor.setUseResourcePages(false);
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(interceptor);
|
|
|
|
|
|
|
|
|
|
// Fetch Swagger UI HTML
|
|
|
|
|
String url = "http://localhost:" + myServer.getPort() + "/fhir/swagger-ui/";
|
|
|
|
|
String resp = fetchSwaggerUi(url);
|
|
|
|
|
List<String> buttonTexts = parsePageButtonTexts(resp, url);
|
|
|
|
|
assertThat(buttonTexts.toString(), buttonTexts, empty());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void testRemoveTrailingSlash() {
|
|
|
|
|
OpenApiInterceptor interceptor = new OpenApiInterceptor();
|
|
|
|
|
String url1 = interceptor.removeTrailingSlash("http://localhost:8000");
|
|
|
|
|
String url2 = interceptor.removeTrailingSlash("http://localhost:8000/");
|
|
|
|
|
String url3 = interceptor.removeTrailingSlash("http://localhost:8000//");
|
|
|
|
|
String expect = "http://localhost:8000";
|
|
|
|
|
assertEquals(expect, url1);
|
|
|
|
|
assertEquals(expect, url2);
|
|
|
|
|
assertEquals(expect, url3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void testRemoveTrailingSlashWithNullUrl() {
|
|
|
|
|
OpenApiInterceptor interceptor = new OpenApiInterceptor();
|
|
|
|
|
String url = interceptor.removeTrailingSlash(null);
|
|
|
|
|
assertEquals(null, url);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void testStandardRedirectScriptIsAccessible() throws IOException {
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new AddResourceCountsInterceptor());
|
|
|
|
|
myServer.getRestfulServer().registerInterceptor(new OpenApiInterceptor());
|
|
|
|
|
|
|
|
|
|
HttpGet get = new HttpGet("http://localhost:" + myServer.getPort() + "/fhir/swagger-ui/oauth2-redirect.html");
|
|
|
|
|
try (CloseableHttpResponse response = myClient.execute(get)) {
|
|
|
|
|
assertEquals(200, response.getStatusLine().getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected String fetchSwaggerUi(String url) throws IOException {
|
|
|
|
|
String resp;
|
|
|
|
|
HttpGet get = new HttpGet(url);
|
|
|
|
|
try (CloseableHttpResponse response = myClient.execute(get)) {
|
|
|
|
|
resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
|
|
|
|
ourLog.info("Response: {}", response.getStatusLine());
|
|
|
|
|
ourLog.debug("Response: {}", resp);
|
|
|
|
|
}
|
|
|
|
|
return resp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
return Collections.emptyList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<String> buttonTexts = new ArrayList<>();
|
|
|
|
|
for (DomElement next : pageButtons.getChildElements()) {
|
|
|
|
|
buttonTexts.add(next.asNormalizedText());
|
|
|
|
|
}
|
|
|
|
|
return buttonTexts;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static class AddResourceCountsInterceptor {
|
|
|
|
|
|
|
|
|
|
private final HashSet<String> myResourceNamesToAddTo;
|
|
|
|
|
|
|
|
|
|
public AddResourceCountsInterceptor(String... theResourceNamesToAddTo) {
|
|
|
|
|
myResourceNamesToAddTo = new HashSet<>(Arrays.asList(theResourceNamesToAddTo));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Hook(Pointcut.SERVER_CAPABILITY_STATEMENT_GENERATED)
|
|
|
|
|
public void capabilityStatementGenerated(IBaseConformance 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++) {
|
|
|
|
|
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 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|