diff --git a/parameters-adapter/parameters-adapter-rest/src/main/java/be/kwakeroni/parameters/adapter/rest/RestBackendAdapter.java b/parameters-adapter/parameters-adapter-rest/src/main/java/be/kwakeroni/parameters/adapter/rest/RestBackendAdapter.java index 98e5e9e..1309e23 100644 --- a/parameters-adapter/parameters-adapter-rest/src/main/java/be/kwakeroni/parameters/adapter/rest/RestBackendAdapter.java +++ b/parameters-adapter/parameters-adapter-rest/src/main/java/be/kwakeroni/parameters/adapter/rest/RestBackendAdapter.java @@ -8,7 +8,13 @@ import org.slf4j.LoggerFactory; import org.slf4j.MDC; -import javax.ws.rs.*; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; import java.util.Map; import java.util.Objects; @@ -37,7 +43,6 @@ public RestBackendAdapter(BusinessParametersBackend backend, BackendWireForma this.wireFormatterContext = Objects.requireNonNull(wireFormatterContext, "wireFormatterContext"); } - @Path("/") @GET @Produces({TEXT_PLAIN}) public String getInfo() { diff --git a/parameters-application/parameters-standalone-app/pom.xml b/parameters-application/parameters-standalone-app/pom.xml new file mode 100644 index 0000000..525050c --- /dev/null +++ b/parameters-application/parameters-standalone-app/pom.xml @@ -0,0 +1,169 @@ + + + + parameters-application + be.kwakeroni.parameters.application + 0.1.0-SNAPSHOT + + 4.0.0 + + parameters-standalone-app + + + + + be.kwakeroni.parameters + parameters-deps + ${project.version} + pom + import + + + be.kwakeroni.parameters.management + parameters-management + ${project.version} + pom + import + + + be.kwakeroni.parameters.backend + parameters-backend + ${project.version} + pom + import + + + be.kwakeroni.parameters.adapter + parameters-adapter + ${project.version} + pom + import + + + be.kwakeroni.parameters.basic + parameters-basic-backend + ${project.version} + pom + import + + + be.kwakeroni.parameters.client + parameters-client + ${project.version} + pom + import + + + be.kwakeroni.parameters.core + parameters-core + ${project.version} + pom + import + + + + + + + be.kwakeroni.parameters.management + parameters-management-rest + + + be.kwakeroni.parameters.adapter + parameters-adapter-direct + + + be.kwakeroni.parameters.adapter + parameters-adapter-rest + + + be.kwakeroni.parameters.backend + parameters-backend-inmemory + + + be.kwakeroni.parameters.basic + parameters-basic-backend + + + be.kwakeroni.parameters.basic + parameters-basic-common + + + be.kwakeroni.parameters.basic + parameters-basic-inmemory + + + be.kwakeroni.parameters.client + parameters-client-api + + + be.kwakeroni.parameters.basic + parameters-basic-client + + + be.kwakeroni.parameters.basic + parameters-basic-wireformat-raw + + + javax.ws.rs + jsr311-api + 1.1.1 + compile + + + org.json + json + 20160810 + runtime + + + com.sun.jersey + jersey-client + 1.19.1 + runtime + + + com.sun.jersey + jersey-json + 1.19.1 + runtime + + + com.sun.jersey + jersey-servlet + 1.19.1 + compile + + + org.slf4j + slf4j-log4j12 + runtime + + + org.assertj + assertj-core + + + org.mockito + mockito-core + + + junit + junit + + + org.junit.jupiter + junit-jupiter-api + + + org.junit.jupiter + junit-jupiter-params + + + be.kwakeroni.parameters.core + parameters-test-support + + + \ No newline at end of file diff --git a/parameters-application/parameters-standalone-app/src/main/java/be/kwakeroni/parameters/app/Configuration.java b/parameters-application/parameters-standalone-app/src/main/java/be/kwakeroni/parameters/app/Configuration.java new file mode 100644 index 0000000..eb52534 --- /dev/null +++ b/parameters-application/parameters-standalone-app/src/main/java/be/kwakeroni/parameters/app/Configuration.java @@ -0,0 +1,9 @@ +package be.kwakeroni.parameters.app; + +interface Configuration { + + public int getPort(); + + public String getContextPath(); + +} diff --git a/parameters-application/parameters-standalone-app/src/main/java/be/kwakeroni/parameters/app/Server.java b/parameters-application/parameters-standalone-app/src/main/java/be/kwakeroni/parameters/app/Server.java new file mode 100644 index 0000000..b3f5a5a --- /dev/null +++ b/parameters-application/parameters-standalone-app/src/main/java/be/kwakeroni/parameters/app/Server.java @@ -0,0 +1,94 @@ +package be.kwakeroni.parameters.app; + +import be.kwakeroni.parameters.adapter.rest.RestBackendAdapter; +import be.kwakeroni.parameters.adapter.rest.factory.RestBackendAdapterFactory; +import be.kwakeroni.parameters.management.rest.RestParameterManagement; +import be.kwakeroni.parameters.management.rest.factory.RestParameterManagementFactory; +import com.sun.jersey.api.container.ContainerFactory; +import com.sun.jersey.api.container.httpserver.HttpServerFactory; +import com.sun.jersey.api.core.ApplicationAdapter; +import com.sun.jersey.api.core.ResourceConfig; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.Application; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +class Server implements AutoCloseable { + + private final Configuration configuration; + private HttpServer httpServer; + + + Server(Configuration configuration) { + this.configuration = configuration; + } + + synchronized void start() throws IOException { + if (this.httpServer != null) return; + + String uri = String.format("http://127.0.0.1:%s/%s", this.configuration.getPort(), this.configuration.getContextPath()); + + this.httpServer = HttpServerFactory.create(uri, + ContainerFactory.createContainer(HttpHandler.class, getResourceConfig(), null)); + + this.httpServer.start(); + + } + + synchronized void stop() { + if (this.httpServer != null) { + this.httpServer.stop(0); + this.httpServer = null; + } + } + + @Override + public void close() { + this.stop(); + } + + private ResourceConfig getResourceConfig() { + + Root root = new Root(); + RestBackendAdapter restBackend = new RestBackendAdapterFactory().newInstance(); + RestParameterManagement restManagement = new RestParameterManagementFactory().newInstance(); + + @Path("/web") + class WebManagement extends StaticContent { + private WebManagement() { + // TODO path + super(Paths.get("."), "index.html"); + } + } + StaticContent webManagement = new WebManagement(); + + ResourceConfig config = new ApplicationAdapter(ofSingletons(root, restBackend, restManagement, webManagement)); + config.setPropertiesAndFeatures(Collections.singletonMap("com.sun.jersey.api.json.POJOMappingFeature", true)); + return config; + } + + @Path("/") + public static final class Root { + @GET + public String hello() { + return "Business Parameters"; + } + } + + private static Application ofSingletons(Object... singletons) { + return new Application() { + @Override + public Set getSingletons() { + return new HashSet<>(Arrays.asList(singletons)); + } + }; + } +} diff --git a/parameters-application/parameters-standalone-app/src/main/java/be/kwakeroni/parameters/app/StaticContent.java b/parameters-application/parameters-standalone-app/src/main/java/be/kwakeroni/parameters/app/StaticContent.java new file mode 100644 index 0000000..3eca9dc --- /dev/null +++ b/parameters-application/parameters-standalone-app/src/main/java/be/kwakeroni/parameters/app/StaticContent.java @@ -0,0 +1,72 @@ +package be.kwakeroni.parameters.app; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import java.net.URI; +import java.nio.file.Files; +import java.util.Optional; + +public abstract class StaticContent { + + private final java.nio.file.Path contentDirectory; + private final String indexPage; + + StaticContent(java.nio.file.Path contentDirectory, String indexPage) { + this.contentDirectory = contentDirectory.toAbsolutePath().normalize(); + this.indexPage = indexPage; + } + + + @GET + public Response getInfo(@Context UriInfo uriInfo) { + URI requestUri = uriInfo.getRequestUri(); + URI contextUri = (requestUri.getPath().endsWith("/")) ? requestUri : requestUri.resolve(requestUri.getPath() + "/"); + + return Response.seeOther(contextUri.resolve(indexPage)).build(); + } + + @GET + @Path("{path:.*}") + public Response get(@PathParam("path") String path) { + + java.nio.file.Path resourcePath = contentDirectory.resolve(path); + + return ifExists(resourcePath) + .map(this::getContents) + .orElseGet(() -> Response.status(Response.Status.NOT_FOUND)) + .build(); + } + + private Optional ifExists(java.nio.file.Path resource) { + // Do not serve files outside of the content directory. + // For security reasons it is preferable to treat such requests like non-existing files (404) + // rather than returning a 'forbidden' response (403). + + return Optional.of(resource) + .map(java.nio.file.Path::normalize) + .filter(path -> path.startsWith(contentDirectory)) + .filter(Files::exists) + .filter(Files::isRegularFile) + .filter(Files::isReadable); + } + + private Response.ResponseBuilder getContents(java.nio.file.Path resource) { + try { + return getContents0(resource); + } catch (Exception e) { + // TODO Log + e.printStackTrace(); + return Response.serverError(); + } + } + + protected Response.ResponseBuilder getContents0(java.nio.file.Path resource) throws Exception { + return Response.ok() + .type(Files.probeContentType(resource)) + .entity(Files.newInputStream(resource)); + } +} diff --git a/parameters-application/parameters-standalone-app/src/test/java/be/kwakeroni/parameters/app/ServerTest.java b/parameters-application/parameters-standalone-app/src/test/java/be/kwakeroni/parameters/app/ServerTest.java new file mode 100644 index 0000000..c4e507e --- /dev/null +++ b/parameters-application/parameters-standalone-app/src/test/java/be/kwakeroni/parameters/app/ServerTest.java @@ -0,0 +1,118 @@ +package be.kwakeroni.parameters.app; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.extension.mockito.MockitoExtension; +import org.mockito.Mock; + +import static be.kwakeroni.test.assertion.RestAssert.assertThat; +import static be.kwakeroni.test.assertion.RestAssert.get; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ServerTest { + + private static final int PORT = 9999; + private static final String CONTEXT_PATH = "parameters"; + private static final String CONTEXT_URL = "http://127.0.0.1:9999/parameters"; + private static final String REST_URL = "http://127.0.0.1:9999/parameters/client/"; + private static final String MGMT_REST_URL = "http://127.0.0.1:9999/parameters/management/"; + private static final String MGMT_WEB_URL = "http://127.0.0.1:9999/parameters/web/"; + private static final String MGMT_WEB_INDEX = "http://127.0.0.1:9999/parameters/web/index.html"; + + + @Mock + private Configuration configuration; + + @BeforeEach + private void setupConfiguration() { + when(configuration.getPort()).thenReturn(PORT); + when(configuration.getContextPath()).thenReturn(CONTEXT_PATH); + } + + @Nested + @DisplayName("Starts a server") + class StartServerTest { + + @Test + @DisplayName("At the configured URL") + void testStart() throws Exception { + try (Server server = new Server(configuration)) { + server.start(); + + assertThat(get(CONTEXT_URL)) + .isSuccess() + .andText().isEqualTo("Business Parameters"); + + } + } + + @Test + @DisplayName("Exposing the Parameters REST Service") + void testRestService() throws Exception { + try (Server server = new Server(configuration)) { + server.start(); + + assertThat(get(REST_URL)) + .isSuccess() + .andText().isEqualTo("Business Parameters Rest Adapter"); + + } + } + + @Test + @DisplayName("Exposing the Management REST Service") + void testManagementRestService() throws Exception { + try (Server server = new Server(configuration)) { + server.start(); + + assertThat(get(MGMT_REST_URL)) + .isSuccess() + .andText().isEqualTo("Business Parameters Management Rest Service"); + + } + } + + @Test + @DisplayName("Exposing the Management Web Application") + void testManagementWebApp() throws Exception { + try (Server server = new Server(configuration)) { + server.start(); + + assertThat(get(MGMT_WEB_URL)) + .redirectsTo(MGMT_WEB_INDEX); + } + } + } + + @Nested + @DisplayName("Stops a server") + class StopServerTest { + + @Test + @DisplayName("Using the stop command") + void testStop() throws Exception { + try (Server server = new Server(configuration)) { + server.start(); + assertThat(get(CONTEXT_URL)).isSuccess(); + + server.stop(); + assertThat(get(CONTEXT_URL)).failsToConnect(); + } + } + + @Test + @DisplayName("When closing the Server resource") + void testClose() throws Exception { + try (Server server = new Server(configuration)) { + server.start(); + assertThat(get(CONTEXT_URL)).isSuccess(); + } + assertThat(get(CONTEXT_URL)).failsToConnect(); + } + } + +} diff --git a/parameters-application/parameters-standalone-app/src/test/java/be/kwakeroni/parameters/app/StaticContentTest.java b/parameters-application/parameters-standalone-app/src/test/java/be/kwakeroni/parameters/app/StaticContentTest.java new file mode 100644 index 0000000..3d9c848 --- /dev/null +++ b/parameters-application/parameters-standalone-app/src/test/java/be/kwakeroni/parameters/app/StaticContentTest.java @@ -0,0 +1,174 @@ +package be.kwakeroni.parameters.app; + +import com.sun.jersey.api.container.ContainerFactory; +import com.sun.jersey.api.container.httpserver.HttpServerFactory; +import com.sun.jersey.api.core.ApplicationAdapter; +import com.sun.jersey.api.core.ResourceConfig; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.rules.TemporaryFolder; + +import javax.ws.rs.Path; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.Response; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; +import java.util.function.Function; + +import static be.kwakeroni.test.assertion.RestAssert.assertThat; +import static be.kwakeroni.test.assertion.RestAssert.get; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +public class StaticContentTest { + + private static final String CONTEXT_PATH = "/content"; + private static final String INDEX_PAGE = "index.test"; + private static final String SERVER_URL = "http://127.0.0.1:9999/test"; + private static final String CONTEXT_URL = SERVER_URL + CONTEXT_PATH; + private static final String INDEX_URL = CONTEXT_URL + "/" + INDEX_PAGE; + private static final String IMAGES = "images"; + private static final String IMAGES_URL = CONTEXT_URL + "/" + IMAGES; + private static final String IMAGE = "images/test.jpg"; + private static final String IMAGE_URL = CONTEXT_URL + "/" + IMAGE; + private static final String NON_READABLE = "private.txt"; + private static final String NON_READABLE_URL = CONTEXT_URL + "/" + NON_READABLE; + + private static HttpServer httpServer; + private static TemporaryFolder contentDirectory = new TemporaryFolder(); + + @SuppressWarnings("unchecked") + private static Function getFileContents = mock(Function.class); + + @Path(CONTEXT_PATH) + public static class TestStaticContent extends StaticContent { + private TestStaticContent(java.nio.file.Path contentDirectory) { + super(contentDirectory, INDEX_PAGE); + } + + @Override + protected Response.ResponseBuilder getContents0(java.nio.file.Path resource) throws Exception { + Response.ResponseBuilder mockResult = getFileContents.apply(resource); + return (mockResult == null) ? super.getContents0(resource) : mockResult; + } + } + + @BeforeAll + static void setUpRestService() throws Exception { + + contentDirectory.create(); + + // File outside the content directory to make sure this file cannot be accessed + java.nio.file.Path outsideFilePath = contentDirectory.getRoot().toPath().getParent().resolve(INDEX_PAGE); + Files.write(outsideFilePath, Arrays.asList("one", "two", "three")); + + // Non-readable file to make sure this file cannot be accessed + java.nio.file.Path nonReadablePath = contentDirectory.getRoot().toPath().resolve(NON_READABLE); + Files.write(nonReadablePath, Arrays.asList("one", "two", "three")); + Files.setPosixFilePermissions(nonReadablePath, PosixFilePermissions.fromString("--x------")); + + java.nio.file.Path imagePath = contentDirectory.getRoot().toPath().resolve(IMAGE); + Files.createDirectories(imagePath.getParent()); + Files.copy(getImage(), imagePath); + + ResourceConfig config = new ApplicationAdapter(new Application() { + @Override + public Set getSingletons() { + return Collections.singleton(new TestStaticContent(contentDirectory.getRoot().toPath())); + } + }); + config.setPropertiesAndFeatures(Collections.singletonMap("com.sun.jersey.api.json.POJOMappingFeature", true)); + + httpServer = HttpServerFactory.create(SERVER_URL, + ContainerFactory.createContainer(HttpHandler.class, config, null)); + + httpServer.start(); + } + + @AfterEach + @SuppressWarnings("unchecked") + void tearDown() { + reset(getFileContents); + } + + @AfterAll + static void tearDownRestService() { + httpServer.stop(0); + httpServer = null; + contentDirectory.delete(); + } + + @Test + @DisplayName("Redirects root context to the index page (without trailing slash)") + void testRedirectRootWithoutSlash() { + assertThat(get(CONTEXT_URL)) + .redirectsTo(INDEX_URL); + } + + @Test + @DisplayName("Redirects root context to the index page (with trailing slash)") + void testRedirectRootWithSlash() { + assertThat(get(CONTEXT_URL + "/")) + .redirectsTo(INDEX_URL); + } + + @Test + @DisplayName("Serves files from a local folder") + void testServesFiles() { + assertThat(get(IMAGE_URL)) + .isSuccess() + .hasContentType("image/jpeg") + .andResponse().hasSameContentAs(getImage()); + } + + @Test + @DisplayName("Does not serve files outside the local folder") + void testServesNoFilesOutsideFolder() { + assertThat(get(CONTEXT_URL + "/../" + INDEX_PAGE)) + .isNotFound(); + } + + @Test + @DisplayName("Reports non-existing files as not found") + void testNotFoundNonExistingFiles() { + assertThat(get(INDEX_URL)) + .isNotFound(); + } + + @Test + @DisplayName("Reports directories as not found") + void testNotFoundDirectories() { + assertThat(get(IMAGES_URL)) + .isNotFound(); + } + + @Test + @DisplayName("Reports non-readable files as not found") + void testNotFoundNonReadableFiles() { + assertThat(get(NON_READABLE_URL)) + .isNotFound(); + } + + @Test + @DisplayName("Reports blank server error in case of exceptions") + void testServerError() { + when(getFileContents.apply(any())).thenThrow(new UnsupportedOperationException()); + + assertThat(get(IMAGE_URL)) + .hasStatus(Response.Status.INTERNAL_SERVER_ERROR) + .andText().isEmpty(); + } + + private static InputStream getImage() { + return StaticContentTest.class.getResourceAsStream("test.jpg"); + } +} diff --git a/parameters-application/pom.xml b/parameters-application/pom.xml new file mode 100644 index 0000000..3ddd629 --- /dev/null +++ b/parameters-application/pom.xml @@ -0,0 +1,18 @@ + + + + parameters + be.kwakeroni.parameters + 0.1.0-SNAPSHOT + + 4.0.0 + + be.kwakeroni.parameters.application + parameters-application + pom + + parameters-standalone-app + + \ No newline at end of file diff --git a/parameters-core/parameters-test-support/pom.xml b/parameters-core/parameters-test-support/pom.xml index eadc411..8363384 100644 --- a/parameters-core/parameters-test-support/pom.xml +++ b/parameters-core/parameters-test-support/pom.xml @@ -39,5 +39,16 @@ mockito-core compile + + org.assertj + assertj-core + compile + + + com.sun.jersey + jersey-client + compile + true + \ No newline at end of file diff --git a/parameters-core/parameters-test-support/src/main/java/be/kwakeroni/test/assertion/RestAssert.java b/parameters-core/parameters-test-support/src/main/java/be/kwakeroni/test/assertion/RestAssert.java new file mode 100644 index 0000000..516501c --- /dev/null +++ b/parameters-core/parameters-test-support/src/main/java/be/kwakeroni/test/assertion/RestAssert.java @@ -0,0 +1,193 @@ +package be.kwakeroni.test.assertion; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientHandlerException; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource; +import com.sun.jersey.api.client.config.ClientConfig; +import org.assertj.core.api.AbstractAssert; +import org.assertj.core.api.AbstractCharSequenceAssert; +import org.assertj.core.api.AbstractInputStreamAssert; +import org.assertj.core.api.Assertions; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.io.InputStream; +import java.net.ConnectException; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; + +public class RestAssert extends AbstractAssert { + + private static final Client CLIENT = new Client(); + + private ClientResponse clientResponse = null; + + public static RestAssert assertThat(RestCall restCall) { + return new RestAssert(restCall); + } + + public static RestCall get(String uri) { + return new Call(uri, WebResource::get); + } + + public static RestCall options(String uri) { + return new Call(uri, WebResource::options); + } + + public static RestCall head(String uri) { + return new Call(uri, (resource, $class) -> resource.head()); + } + + private RestAssert(RestCall call) { + super(call, RestAssert.class); + } + + public ClientResponse getResponse() { + if (this.clientResponse == null) { + this.clientResponse = actual.call(); + } + return this.clientResponse; + } + + public String getResponseText() { + return getResponse().getEntity(String.class); + } + + public String getResponseHeader(String name) { + List values = getResponse().getHeaders().get(name); + if (values.size() > 1) { + throw new IllegalStateException("Multiple values for header " + name + ": " + values); + } + return values.get(0); + } + + private void failWithMessage(String pattern, ClientResponse response) { + failWithMessage(pattern, + actual.getUri(), + response.getStatus(), + response.getEntity(String.class) + ); + } + + public RestAssert isSuccess() { + ClientResponse response = getResponse(); + if (!Response.Status.Family.SUCCESSFUL.equals(response.getStatusInfo().getFamily())) { + failWithMessage("Expected call to %s to be successful, but error code was returned: %s, with response content: %n%s", response); + } + return this; + } + + public RestAssert redirectsTo(String uri) { + ClientResponse response = getResponse(); + if (!Response.Status.Family.REDIRECTION.equals(response.getStatusInfo().getFamily())) { + failWithMessage("Expected call to %s to redirect, but unexpected status was returned: %s, with response content: %n%s", response); + } + String redirectUrl = getResponseHeader(HttpHeaders.LOCATION); + if (!redirectUrl.equals(uri)) { + failWithMessage("Expected call to %s to redirect to %s but response redirects to %s", actual.getUri(), uri, redirectUrl); + } + return this; + } + + public RestAssert isNotFound() { + ClientResponse response = getResponse(); + if (response.getStatus() != 404) { + failWithMessage("Expected call to %s to be 'not found', but unexpected status was returned: %s, with response content: %n%s", response); + } + return this; + } + + public RestAssert hasStatus(Response.Status status) { + return hasStatus(status.getStatusCode()); + } + + public RestAssert hasStatus(int status) { + ClientResponse response = getResponse(); + if (response.getStatus() != status) { + failWithMessage("Expected call to %s to have status %s, but unexpected status was returned: %s, with response content: %n%s", actual.getUri(), status, response.getStatus(), response.getEntity(String.class)); + } + return this; + } + + public RestAssert failsToConnect() { + try { + ClientResponse response = getResponse(); + failWithMessage("Expected connection to %s to fail, but response was returned with status: %s and content: %n%s", response); + } catch (ClientHandlerException exc) { + if (!(exc.getCause() instanceof ConnectException)) { + failWithMessage("Expected connection to %s to fail, but unexpected exception was thrown with cause: %s", actual.getUri(), exc.getCause()); + } + } catch (Exception exc) { + failWithMessage("Expected connection to %s to fail, but unexpected exception was thrown: %s", actual.getUri(), exc); + } + return this; + } + + public RestAssert hasContentType(MediaType type) { + return hasContentType(type.getType()); + } + + public RestAssert hasContentType(String expectedType) { + String actualType = getResponseHeader(HttpHeaders.CONTENT_TYPE); + if (!expectedType.equals(actualType)) { + failWithMessage("Expected connection to %s to have content-type %s, but actual type was %s", actual.getUri(), expectedType, actualType); + } + return this; + } + + public AbstractCharSequenceAssert andText() { + return Assertions.assertThat(getResponseText()); + } + + public AbstractInputStreamAssert andResponse() { + InputStream stream = getResponse().getEntityInputStream(); + return Assertions.assertThat(stream); + } + + public RestAssert withResponse(Consumer action) { + action.accept(getResponse()); + return this; + } + + public RestAssert withText(Consumer action) { + action.accept(getResponseText()); + return this; + } + + public RestAssert withHeaders(BiConsumer action) { + getResponse().getHeaders().forEach((key, values) -> action.accept(key, String.valueOf(values))); + return this; + } + + public interface RestCall { + public String getUri(); + + public ClientResponse call(); + } + + private static class Call implements RestCall { + private final WebResource resource; + private final BiFunction, ClientResponse> caller; + + + public Call(String uri, BiFunction, ClientResponse> caller) { + this.caller = caller; + this.resource = CLIENT.resource(uri); + this.resource.setProperty(ClientConfig.PROPERTY_FOLLOW_REDIRECTS, false); + } + + @Override + public String getUri() { + return resource.getURI().toString(); + } + + @Override + public ClientResponse call() { + return caller.apply(this.resource, ClientResponse.class); + } + } +} diff --git a/parameters-core/parameters-test-support/src/main/java/org/junit/jupiter/extension/mockito/MockitoExtension.java b/parameters-core/parameters-test-support/src/main/java/org/junit/jupiter/extension/mockito/MockitoExtension.java index 4be732a..d000c11 100644 --- a/parameters-core/parameters-test-support/src/main/java/org/junit/jupiter/extension/mockito/MockitoExtension.java +++ b/parameters-core/parameters-test-support/src/main/java/org/junit/jupiter/extension/mockito/MockitoExtension.java @@ -23,6 +23,11 @@ import static org.mockito.Mockito.mock; +/* + * This particular class was modified from the original to take into account + * the parameter index for injected mocks in the standard case where parameter names are absent. + */ + /** * {@code MockitoExtension} showcases the {@link TestInstancePostProcessor} * and {@link ParameterResolver} extension APIs of JUnit 5 by providing diff --git a/parameters-management/parameters-management-rest/src/main/java/be/kwakeroni/parameters/management/rest/RestParameterManagement.java b/parameters-management/parameters-management-rest/src/main/java/be/kwakeroni/parameters/management/rest/RestParameterManagement.java index 34e264c..24f2559 100644 --- a/parameters-management/parameters-management-rest/src/main/java/be/kwakeroni/parameters/management/rest/RestParameterManagement.java +++ b/parameters-management/parameters-management-rest/src/main/java/be/kwakeroni/parameters/management/rest/RestParameterManagement.java @@ -38,7 +38,6 @@ public RestParameterManagement(BusinessParametersBackend backend) { this.backend = backend; } - @Path("/") @GET @Produces({TEXT_PLAIN}) public String getInfo() { diff --git a/pom.xml b/pom.xml index b65b5bf..64c812c 100644 --- a/pom.xml +++ b/pom.xml @@ -13,6 +13,7 @@ parameters-adapter + parameters-application parameters-backend parameters-basic parameters-client