Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add test for constrained interceptor #1518

Merged
merged 1 commit into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading