Skip to content

Commit

Permalink
test: add tests for endpoints (#22)
Browse files Browse the repository at this point in the history
Add tests for endpoints, reactive endpoints and endpoint security
Register form authentication mechanism only if form authentication is enabled

Co-authored-by: Dario Götze <[email protected]>
  • Loading branch information
mcollovati and Dudeplayz committed May 8, 2023
1 parent 1ec3e63 commit 6ee39b4
Show file tree
Hide file tree
Showing 14 changed files with 1,188 additions and 11 deletions.
26 changes: 26 additions & 0 deletions hilla-test-extension/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,32 @@
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-validator</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-security-deployment</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-security-test-utils</artifactId>
<scope>test</scope>
<version>${quarkus.version}</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.24.2</version>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,13 +222,17 @@ void registerHillaSecurityPolicy(HttpBuildTimeConfig buildTimeConfig,
@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
void registerHillaFormAuthenticationMechanism(
HttpBuildTimeConfig httpBuildTimeConfig,
HillaSecurityRecorder recorder,
BuildProducer<SyntheticBeanBuildItem> producer) {
producer.produce(SyntheticBeanBuildItem
.configure(HillaFormAuthenticationMechanism.class)
.types(HttpAuthenticationMechanism.class).setRuntimeInit()
.scope(Singleton.class).alternativePriority(1)
.supplier(recorder.setupFormAuthenticationMechanism()).done());
if (httpBuildTimeConfig.auth.form.enabled) {
producer.produce(SyntheticBeanBuildItem
.configure(HillaFormAuthenticationMechanism.class)
.types(HttpAuthenticationMechanism.class).setRuntimeInit()
.scope(Singleton.class).alternativePriority(1)
.supplier(recorder.setupFormAuthenticationMechanism())
.done());
}
}

@BuildStep
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package org.acme.hilla.test.extension.deployment;

import dev.hilla.exception.EndpointValidationException;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.acme.hilla.test.extension.deployment.TestUtils.Parameters;
import org.acme.hilla.test.extension.deployment.endpoints.TestEndpoint;
import org.hamcrest.CoreMatchers;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import static org.acme.hilla.test.extension.deployment.TestUtils.givenEndpointRequest;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;

class EndpointControllerTest {

private static final String ENDPOINT_NAME = TestEndpoint.class
.getSimpleName();

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(TestUtils.class, TestEndpoint.class));

@Test
void invokeEndpoint_singleSimpleParameter() {
String msg = "A text message";
givenEndpointRequest(ENDPOINT_NAME, "echo",
Parameters.param("message", msg)).then().assertThat()
.statusCode(200).and().body(equalTo("\"" + msg + "\""));
}

@Test
void invokeEndpoint_singleComplexParameter() {
String msg = "A text message";
TestEndpoint.Pojo pojo = new TestEndpoint.Pojo(10, msg);
givenEndpointRequest(ENDPOINT_NAME, "pojo",
Parameters.param("pojo", pojo)).then().assertThat()
.statusCode(200).and().body("number", equalTo(100)).and()
.body("text", equalTo(msg + msg));
}

@Test
void invokeEndpoint_multipleParameters() {
givenEndpointRequest(ENDPOINT_NAME, "calculate",
Parameters.param("operator", "+").add("a", 10).add("b", 20))
.then().assertThat().statusCode(200).and().body(equalTo("30"));
}

@Test
void invokeEndpoint_wrongParametersOrder_badRequest() {
givenEndpointRequest(ENDPOINT_NAME, "calculate",
Parameters.param("a", 10).add("operator", "+").add("b", 20))
.then().assertThat().statusCode(400).and()
.body("type", equalTo(EndpointValidationException.class.getName()))
.and()
.body("message",
CoreMatchers.allOf(containsString("Validation error"),
containsString("'TestEndpoint'"),
containsString("'calculate'")))
.body("validationErrorData[0].parameterName",
equalTo("operator"));
}

@Test
void invokeEndpoint_wrongNumberOfParameters_badRequest() {
givenEndpointRequest(ENDPOINT_NAME, "calculate",
Parameters.param("operator", "+")).then().assertThat()
.statusCode(400).and().body("message",
CoreMatchers.allOf(
containsString(
"Incorrect number of parameters"),
containsString("'TestEndpoint'"),
containsString("'calculate'"),
containsString("expected: 3, got: 1")));
}

@Test
void invokeEndpoint_wrongEndpointName_notFound() {
givenEndpointRequest("NotExistingTestEndpoint", "calculate",
Parameters.param("operator", "+")).then().assertThat()
.statusCode(404);
}

@Test
void invokeEndpoint_wrongMethodName_notFound() {
givenEndpointRequest(ENDPOINT_NAME, "notExistingMethod",
Parameters.param("operator", "+")).then().assertThat()
.statusCode(404);
}

@Test
void invokeEndpoint_emptyMethodName_notFound() {
givenEndpointRequest(ENDPOINT_NAME, "",
Parameters.param("operator", "+")).then().assertThat()
.statusCode(404);
}

@Test
void invokeEndpoint_missingMethodName_notFound() {
RestAssured.given().contentType(ContentType.JSON)
.cookie("csrfToken", "CSRF_TOKEN")
.header("X-CSRF-Token", "CSRF_TOKEN").basePath("/connect")
.when().post(ENDPOINT_NAME).then().assertThat().statusCode(404);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package org.acme.hilla.test.extension.deployment;

import java.util.function.UnaryOperator;
import java.util.stream.Stream;

import io.quarkus.security.test.utils.TestIdentityController;
import io.quarkus.security.test.utils.TestIdentityProvider;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.specification.RequestSpecification;
import org.acme.hilla.test.extension.deployment.TestUtils.User;
import org.acme.hilla.test.extension.deployment.endpoints.SecureEndpoint;
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.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import static org.acme.hilla.test.extension.deployment.TestUtils.ADMIN;
import static org.acme.hilla.test.extension.deployment.TestUtils.GUEST;
import static org.acme.hilla.test.extension.deployment.TestUtils.USER;
import static org.acme.hilla.test.extension.deployment.TestUtils.givenEndpointRequest;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;

class EndpointSecurityTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(TestIdentityProvider.class,
TestIdentityController.class, TestUtils.class,
SecureEndpoint.class)
.addAsResource(new StringAsset(
"quarkus.http.auth.basic=true\nquarkus.http.auth.proactive=true\n"),
"application.properties"));
public static final String SECURE_ENDPOINT = "SecureEndpoint";

@BeforeAll
public static void setupUsers() {
TestIdentityController.resetRoles()
.add(ADMIN.username, ADMIN.pwd, "ADMIN")
.add(USER.username, USER.pwd, "USER")
.add(GUEST.username, GUEST.pwd, "GUEST");
}

@Test
void securedEndpoint_permitAll_authenticatedUsersAllowed() {
Stream.of(USER, GUEST)
.forEach(user -> givenEndpointRequest(SECURE_ENDPOINT,
"authenticated", authenticate(user)).then().assertThat()
.statusCode(200).and()
.body(equalTo("\"AUTHENTICATED\"")));

givenEndpointRequest(SECURE_ENDPOINT, "authenticated").then()
.assertThat().statusCode(401).and()
.body("message", containsString(SECURE_ENDPOINT))
.body("message", containsString("reason: 'Access denied'"));
}

@Test
void securedEndpoint_adminOnly_onlyAdminAllowed() {
givenEndpointRequest(SECURE_ENDPOINT, "adminOnly", authenticate(ADMIN))
.then().assertThat().statusCode(200).and()
.body(equalTo("\"ADMIN\""));

Stream.of(USER, GUEST)
.forEach(user -> givenEndpointRequest(SECURE_ENDPOINT,
"adminOnly", authenticate(user)).then().assertThat()
.statusCode(401).and()
.body("message", containsString(SECURE_ENDPOINT))
.body("message",
containsString("reason: 'Access denied'")));

givenEndpointRequest(SECURE_ENDPOINT, "adminOnly").then().assertThat()
.statusCode(401).and()
.body("message", containsString(SECURE_ENDPOINT))
.body("message", containsString("reason: 'Access denied'"));
}

@Test
void securedEndpoint_userOnly_onlyUserAllowed() {
givenEndpointRequest(SECURE_ENDPOINT, "userOnly", authenticate(USER))
.then().assertThat().statusCode(200).and()
.body(equalTo("\"USER\""));

Stream.of(ADMIN, GUEST)
.forEach(user -> givenEndpointRequest(SECURE_ENDPOINT,
"userOnly", authenticate(user)).then().assertThat()
.statusCode(401).and()
.body("message", containsString(SECURE_ENDPOINT))
.body("message",
containsString("reason: 'Access denied'")));

givenEndpointRequest(SECURE_ENDPOINT, "userOnly").then().assertThat()
.statusCode(401).and()
.body("message", containsString(SECURE_ENDPOINT))
.body("message", containsString("reason: 'Access denied'"));
}

@Test
void securedEndpoint_adminAndUserOnly_onlyAdminAndUserAllowed() {
Stream.of(ADMIN, USER)
.forEach(user -> givenEndpointRequest(SECURE_ENDPOINT,
"userAndAdmin", authenticate(user)).then().assertThat()
.statusCode(200).and()
.body(equalTo("\"USER AND ADMIN\"")));

givenEndpointRequest(SECURE_ENDPOINT, "userAndAdmin",
authenticate(GUEST)).then().assertThat().statusCode(401).and()
.body("message", containsString(SECURE_ENDPOINT))
.body("message", containsString("reason: 'Access denied'"));

givenEndpointRequest(SECURE_ENDPOINT, "userAndAdmin").then()
.assertThat().statusCode(401).and()
.body("message", containsString(SECURE_ENDPOINT))
.body("message", containsString("reason: 'Access denied'"));
}

@Test
void securedEndpoint_deny_notAllowed() {
Stream.of(ADMIN, USER, GUEST)
.forEach(user -> givenEndpointRequest(SECURE_ENDPOINT, "deny",
authenticate(user)).then().assertThat().statusCode(401)
.and().body("message", containsString(SECURE_ENDPOINT))
.body("message",
containsString("reason: 'Access denied'")));

givenEndpointRequest(SECURE_ENDPOINT, "deny").then().assertThat()
.statusCode(401).and()
.body("message", containsString(SECURE_ENDPOINT))
.body("message", containsString("reason: 'Access denied'"));
}

@Test
void securedEndpoint_notAnnotatedMethod_denyAll() {
Stream.of(ADMIN, USER, GUEST)
.forEach(user -> givenEndpointRequest(SECURE_ENDPOINT,
"denyByDefault", authenticate(user)).then().assertThat()
.statusCode(401).and()
.body("message", containsString(SECURE_ENDPOINT))
.body("message",
containsString("reason: 'Access denied'")));

givenEndpointRequest(SECURE_ENDPOINT, "denyByDefault").then()
.assertThat().statusCode(401).and()
.body("message", containsString(SECURE_ENDPOINT))
.body("message", containsString("reason: 'Access denied'"));
}

private static UnaryOperator<RequestSpecification> authenticate(User user) {
return spec -> spec.auth().preemptive().basic(user.username, user.pwd);
}
}
Loading

0 comments on commit 6ee39b4

Please sign in to comment.