Skip to content

Commit

Permalink
Make TestHttpEndpoint work for ResteasyReactive
Browse files Browse the repository at this point in the history
Also add support for setting the root path using
config.

Fixes quarkusio#15001
  • Loading branch information
stuartwdouglas committed Feb 12, 2021
1 parent 8a9f1c9 commit b6b6a98
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 17 deletions.
19 changes: 19 additions & 0 deletions docs/src/main/asciidoc/resteasy-reactive.adoc
Expand Up @@ -103,6 +103,25 @@ public class Endpoint {

See <<uri-parameters,URI parameters>> for more information about URI mapping.

You can set the root path for all rest endpoints using the `@ApplicationPath` annotation, as shown below.

[source,java]
----
package org.acme.rest;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
@ApplicationPath("/api")
public static class MyApplication extends Application {
}
----

This will cause all rest endpoints to be resolve relative to `/api`, so the endpoint above with `@Path("rest")` would
be accessible at `/api/rest/. You can also set the `quarkus.rest.path` build time property to set the root path if you
don't want to use an annotation.

=== Declaring endpoints: HTTP methods

Each endpoint method must be annotated with one of the following annotations, which defines which HTTP
Expand Down
Expand Up @@ -244,7 +244,8 @@ public void setupEndpoints(Capabilities capabilities, BeanArchiveIndexBuildItem
ExceptionMappersBuildItem exceptionMappersBuildItem,
ParamConverterProvidersBuildItem paramConverterProvidersBuildItem,
ContextResolversBuildItem contextResolversBuildItem,
List<MethodScannerBuildItem> methodScanners) throws NoSuchMethodException {
List<MethodScannerBuildItem> methodScanners, ResteasyReactiveServerConfig serverConfig)
throws NoSuchMethodException {

if (!resourceScanningResultBuildItem.isPresent()) {
// no detected @Path, bail out
Expand Down Expand Up @@ -475,7 +476,7 @@ private boolean hasAnnotation(MethodInfo method, short paramPosition, DotName an
BeanFactory<ResteasyReactiveInitialiser> initClassFactory = recorder.factory(QUARKUS_INIT_CLASS,
beanContainerBuildItem.getValue());

String applicationPath = determineApplicationPath(index);
String applicationPath = determineApplicationPath(index, serverConfig.path);
// spec allows the path contain encoded characters
if ((applicationPath != null) && applicationPath.contains("%")) {
applicationPath = Encode.decodePath(applicationPath);
Expand Down Expand Up @@ -572,10 +573,10 @@ public List<HandlerChainCustomizer> scan(MethodInfo method, Map<String, Object>
});
}

private String determineApplicationPath(IndexView index) {
private String determineApplicationPath(IndexView index, String defaultPath) {
Collection<AnnotationInstance> applicationPaths = index.getAnnotations(ResteasyReactiveDotNames.APPLICATION_PATH);
if (applicationPaths.isEmpty()) {
return null;
return defaultPath;
}
// currently we only examine the first class that is annotated with @ApplicationPath so best
// fail if the user code has multiple such annotations instead of surprising the user
Expand Down
@@ -0,0 +1,15 @@
package io.quarkus.resteasy.reactive.server.deployment;

import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigRoot;

@ConfigRoot(name = "rest")
public class ResteasyReactiveServerConfig {

/**
* Set this to override the default path for JAX-RS resources if there are no
* annotated application classes.
*/
@ConfigItem(defaultValue = "/")
String path;
}
@@ -0,0 +1,13 @@
package io.quarkus.resteasy.reactive.server.test.path;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("/hello")
public class HelloResource {

@GET
public String hello() {
return "hello";
}
}
@@ -0,0 +1,28 @@
package io.quarkus.resteasy.reactive.server.test.path;

import org.hamcrest.Matchers;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;

public class RestPathTestCase {

@RegisterExtension
static QuarkusUnitTest test = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClass(HelloResource.class)
.addAsResource(new StringAsset("quarkus.rest.path=/foo"), "application.properties"));

@Test
public void testRestPath() {
RestAssured.get("/hello")
.then().statusCode(404);
RestAssured.get("/foo/hello")
.then().statusCode(200).body(Matchers.equalTo("hello"));

}
}
@@ -0,0 +1,70 @@
package io.quarkus.resteasy.reactive.server.runtime;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.util.Optional;
import java.util.function.Function;

import javax.ws.rs.Path;

import org.eclipse.microprofile.config.ConfigProvider;

import io.quarkus.runtime.test.TestHttpEndpointProvider;

public class ResteasyReactiveTestHttpProvider implements TestHttpEndpointProvider {
@Override
public Function<Class<?>, String> endpointProvider() {
return new Function<Class<?>, String>() {
@Override
public String apply(Class<?> aClass) {
String value = getPath(aClass);
if (value == null) {
return null;
}
if (value.startsWith("/")) {
value = value.substring(1);
}
//TODO: there is not really any way to handle @ApplicationPath, we could do something for @QuarkusTest apps but we can't for
//native apps, so we just have to document the limitation
String path = "/";
Optional<String> appPath = ConfigProvider.getConfig().getOptionalValue("quarkus.rest.path", String.class);
if (appPath.isPresent()) {
path = appPath.get();
}
if (!path.endsWith("/")) {
path = path + "/";
}
value = path + value;
return value;
}
};
}

private String getPath(Class<?> aClass) {
String value = null;
for (Annotation annotation : aClass.getAnnotations()) {
if (annotation.annotationType().getName().equals(Path.class.getName())) {
try {
value = (String) annotation.annotationType().getMethod("value").invoke(annotation);
break;
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}
if (value == null) {
for (Class<?> i : aClass.getInterfaces()) {
value = getPath(i);
if (value != null) {
break;
}
}
}
if (value == null) {
if (aClass.getSuperclass() != Object.class) {
value = getPath(aClass.getSuperclass());
}
}
return value;
}
}
@@ -0,0 +1 @@
io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveTestHttpProvider
Expand Up @@ -4,59 +4,61 @@

import org.junit.jupiter.api.Test;

import io.quarkus.test.common.http.TestHTTPEndpoint;
import io.quarkus.test.junit.QuarkusTest;
import io.restassured.RestAssured;

/**
* Test various JPA operations running in Quarkus when the session is injected without Uni or CompletionStage.
*/
@QuarkusTest
@TestHTTPEndpoint(HibernateReactiveTestEndpointAlternative.class)
public class HibernateReactiveAlternativeTest {

@Test
public void reactiveFind() {
RestAssured.when()
.get("/alternative-tests/reactiveFind")
.get("/reactiveFind")
.then()
.body(is("{\"id\":5,\"name\":\"Aloi\"}"));
}

@Test
public void reactiveFindMutiny() {
RestAssured.when()
.get("/alternative-tests/reactiveFindMutiny")
.get("/reactiveFindMutiny")
.then()
.body(is("{\"id\":5,\"name\":\"Aloi\"}"));
}

@Test
public void reactivePersist() {
RestAssured.when()
.get("/alternative-tests/reactivePersist")
.get("/reactivePersist")
.then()
.body(is("Tulip"));
}

@Test
public void reactiveRemoveTransientEntity() {
RestAssured.when()
.get("/alternative-tests/reactiveRemoveTransientEntity")
.get("/reactiveRemoveTransientEntity")
.then()
.body(is("OK"));
}

@Test
public void reactiveRemoveManagedEntity() {
RestAssured.when()
.get("/alternative-tests/reactiveRemoveManagedEntity")
.get("/reactiveRemoveManagedEntity")
.then()
.body(is("OK"));
}

@Test
public void reactiveUpdate() {
RestAssured.when()
.get("/alternative-tests/reactiveUpdate")
.get("/reactiveUpdate")
.then()
.body(is("Tina"));
}
Expand Down
Expand Up @@ -5,67 +5,69 @@

import org.junit.jupiter.api.Test;

import io.quarkus.test.common.http.TestHTTPEndpoint;
import io.quarkus.test.junit.QuarkusTest;
import io.restassured.RestAssured;

/**
* Test various JPA operations running in Quarkus
*/
@QuarkusTest
@TestHTTPEndpoint(HibernateReactiveTestEndpoint.class)
public class HibernateReactiveTest {

@Test
public void reactiveFind() {
RestAssured.when()
.get("/tests/reactiveFind")
.get("/reactiveFind")
.then()
.body(is("{\"id\":5,\"name\":\"Aloi\"}"));
}

@Test
public void reactiveCowPersist() {
RestAssured.when()
.get("/tests/reactiveCowPersist")
.get("/reactiveCowPersist")
.then()
.body(containsString("\"name\":\"Carolina\"}")); //Use containsString as we don't know the Id this object will have
}

@Test
public void reactiveFindMutiny() {
RestAssured.when()
.get("/tests/reactiveFindMutiny")
.get("/reactiveFindMutiny")
.then()
.body(is("{\"id\":5,\"name\":\"Aloi\"}"));
}

@Test
public void reactivePersist() {
RestAssured.when()
.get("/tests/reactivePersist")
.get("/reactivePersist")
.then()
.body(is("Tulip"));
}

@Test
public void reactiveRemoveTransientEntity() {
RestAssured.when()
.get("/tests/reactiveRemoveTransientEntity")
.get("/reactiveRemoveTransientEntity")
.then()
.body(is("OK"));
}

@Test
public void reactiveRemoveManagedEntity() {
RestAssured.when()
.get("/tests/reactiveRemoveManagedEntity")
.get("/reactiveRemoveManagedEntity")
.then()
.body(is("OK"));
}

@Test
public void reactiveUpdate() {
RestAssured.when()
.get("/tests/reactiveUpdate")
.get("/reactiveUpdate")
.then()
.body(is("Tina"));
}
Expand Down

0 comments on commit b6b6a98

Please sign in to comment.