Skip to content

Commit

Permalink
Add tests to interceptors constrainedTo
Browse files Browse the repository at this point in the history
  • Loading branch information
mocenas committed Nov 14, 2023
1 parent 764e0f6 commit 85d24a4
Show file tree
Hide file tree
Showing 6 changed files with 286 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package io.quarkus.ts.http.advanced.reactive;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

import jakarta.ws.rs.ConstrainedTo;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.NameBinding;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.RuntimeType;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.ext.MessageBodyWriter;
import jakarta.ws.rs.ext.Provider;
import jakarta.ws.rs.ext.WriterInterceptor;
import jakarta.ws.rs.ext.WriterInterceptorContext;

@Path("/intercepted")
public class InterceptedResource {

/**
* Interceptors write their message to this list, when they are invoked
* It is a bit dumb way, but it is the easier to get indicators if interceptors were invoked to the client
*/
public static List<String> interceptorMessages = new ArrayList<>();

@WithWriterInterceptor
@GET
public InterceptedString getInterceptedString() {
return new InterceptedString("foo");
}

@GET()
@Path("/messages")
@Produces(MediaType.TEXT_PLAIN)
public String getMessages() {
StringBuilder outputMessage = new StringBuilder();
for (String string : interceptorMessages) {
outputMessage.append(string);
}
return outputMessage.toString();
}

public static class InterceptedString {
public String name;

public InterceptedString(String name) {
this.name = name;
}
}

/**
* This annotation binds the providers to only intercept the method in this class.
* Otherwise, they would be global and intercept all endpoints across the entire application.
*/
@NameBinding
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface WithWriterInterceptor {

}

@Provider
public static class InterceptedStringHandler implements MessageBodyWriter<InterceptedString> {
@Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return type == InterceptedString.class;
}

@Override
public void writeTo(InterceptedString interceptedString, Class<?> type, Type genericType, Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
throws IOException, WebApplicationException {
entityStream.write((interceptedString.name).getBytes(StandardCharsets.UTF_8));
}
}

@Provider
@WithWriterInterceptor
public static class UnconstrainedWriterInterceptor implements WriterInterceptor {
@Override
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
InterceptedResource.interceptorMessages.add("Unconstrained interceptor ");
context.proceed();
}
}

@Provider
@ConstrainedTo(RuntimeType.CLIENT)
@WithWriterInterceptor
public static class ClientWriterInterceptor implements WriterInterceptor {

@Override
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
InterceptedResource.interceptorMessages.add("Client interceptor ");
context.proceed();
}
}

@Provider
@ConstrainedTo(RuntimeType.SERVER)
@WithWriterInterceptor
public static class ServerWriterInterceptor implements WriterInterceptor {

@Override
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
InterceptedResource.interceptorMessages.add("Server interceptor ");
context.proceed();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ quarkus.keycloak.policy-enforcer.paths.grpc.path=/api/grpc/*
quarkus.keycloak.policy-enforcer.paths.grpc.enforcement-mode=DISABLED
quarkus.keycloak.policy-enforcer.paths.client.path=/api/client/*
quarkus.keycloak.policy-enforcer.paths.client.enforcement-mode=DISABLED
quarkus.keycloak.policy-enforcer.paths.intercepted.path=/api/intercepted*
quarkus.keycloak.policy-enforcer.paths.intercepted.enforcement-mode=DISABLED
quarkus.oidc.client-id=test-application-client
quarkus.oidc.credentials.secret=test-application-client-secret
# tolerate 1 minute of clock skew between the Keycloak server and the application
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,24 @@ public void constraintsExist() throws JsonProcessingException {
Assertions.assertEquals("^[A-Za-z]+$", validation.get("pattern").asText());
}

@Test
@Tag("https://github.com/quarkusio/quarkus/pull/36664")
public void interceptedTest() {
// make server to generate a response so interceptors might intercept it
// ignore response, we will read interceptors result later
getApp().given()
.get(ROOT_PATH + "/intercepted")
.thenReturn();

String response = getApp().given()
.get(ROOT_PATH + "/intercepted/messages")
.thenReturn().getBody().asString();

Assertions.assertTrue(response.contains("Unconstrained"), "Unconstrained interceptor should be invoked");
Assertions.assertTrue(response.contains("Server"), "Server interceptor should be invoked");
Assertions.assertFalse(response.contains("Client"), "Client interceptor should not be invoked");
}

private void assertAcceptedMediaTypeEqualsResponseBody(String acceptedMediaType) {
getApp()
.given()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package io.quarkus.ts.http.advanced;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

import jakarta.ws.rs.ConstrainedTo;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.NameBinding;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.RuntimeType;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.ext.MessageBodyWriter;
import jakarta.ws.rs.ext.Provider;
import jakarta.ws.rs.ext.WriterInterceptor;
import jakarta.ws.rs.ext.WriterInterceptorContext;

@Path("/intercepted")
public class InterceptedResource {

/**
* Interceptors write their message to this list, when they are invoked
* It is a bit dumb way, but it is the easier to get indicators if interceptors were invoked to the client
*/
public static List<String> interceptorMessages = new ArrayList<>();

@WithWriterInterceptor
@GET
public InterceptedString getInterceptedString() {
return new InterceptedString("foo");
}

@GET()
@Path("/messages")
@Produces(MediaType.TEXT_PLAIN)
public String getMessages() {
StringBuilder outputMessage = new StringBuilder();
for (String string : interceptorMessages) {
outputMessage.append(string);
}
return outputMessage.toString();
}

public static class InterceptedString {
public String name;

public InterceptedString(String name) {
this.name = name;
}
}

/**
* This annotation binds the providers to only intercept the method in this class.
* Otherwise, they would be global and intercept all endpoints across the entire application.
*/
@NameBinding
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface WithWriterInterceptor {

}

@Provider
public static class InterceptedStringHandler implements MessageBodyWriter<InterceptedString> {
@Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return type == InterceptedString.class;
}

@Override
public void writeTo(InterceptedString interceptedString, Class<?> type, Type genericType, Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
throws IOException, WebApplicationException {
entityStream.write((interceptedString.name).getBytes(StandardCharsets.UTF_8));
}
}

@Provider
@WithWriterInterceptor
public static class UnconstrainedWriterInterceptor implements WriterInterceptor {
@Override
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
InterceptedResource.interceptorMessages.add("Unconstrained interceptor ");
context.proceed();
}
}

@Provider
@ConstrainedTo(RuntimeType.CLIENT)
@WithWriterInterceptor
public static class ClientWriterInterceptor implements WriterInterceptor {

@Override
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
InterceptedResource.interceptorMessages.add("Client interceptor ");
context.proceed();
}
}

@Provider
@ConstrainedTo(RuntimeType.SERVER)
@WithWriterInterceptor
public static class ServerWriterInterceptor implements WriterInterceptor {

@Override
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
InterceptedResource.interceptorMessages.add("Server interceptor ");
context.proceed();
}
}
}
2 changes: 2 additions & 0 deletions http/http-advanced/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ quarkus.keycloak.policy-enforcer.paths.grpc.path=/api/grpc/*
quarkus.keycloak.policy-enforcer.paths.grpc.enforcement-mode=DISABLED
quarkus.keycloak.policy-enforcer.paths.client.path=/api/client/*
quarkus.keycloak.policy-enforcer.paths.client.enforcement-mode=DISABLED
quarkus.keycloak.policy-enforcer.paths.intercepted.path=/api/intercepted*
quarkus.keycloak.policy-enforcer.paths.intercepted.enforcement-mode=DISABLED
quarkus.oidc.client-id=test-application-client
quarkus.oidc.credentials.secret=test-application-client-secret
# tolerate 1 minute of clock skew between the Keycloak server and the application
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,24 @@ public void sseConnectionTest() {
assertTrue(response.contains("event: test234 test"), "SSE failed, unknown bug. Response: " + response);
}

@Test
@Tag("https://github.com/quarkusio/quarkus/pull/36664")
public void interceptedTest() {
// make server to generate a response so interceptors might intercept it
// ignore response, we will read interceptors result later
getApp().given()
.get(ROOT_PATH + "/intercepted")
.thenReturn();

String response = getApp().given()
.get(ROOT_PATH + "/intercepted/messages")
.thenReturn().getBody().asString();

Assertions.assertTrue(response.contains("Unconstrained"), "Unconstrained interceptor should be invoked");
Assertions.assertTrue(response.contains("Server"), "Server interceptor should be invoked");
Assertions.assertFalse(response.contains("Client"), "Client interceptor should not be invoked");
}

protected Protocol getProtocol() {
return Protocol.HTTPS;
}
Expand Down

0 comments on commit 85d24a4

Please sign in to comment.