From ddc97b395ad3935f9ee91335f343b6e800e724c2 Mon Sep 17 00:00:00 2001 From: Volodymyr Kliushnichenko Date: Thu, 16 Oct 2025 17:44:15 +0300 Subject: [PATCH 1/3] test to reproduce the issue 3787 --- .../test/java/io/jooby/i3787/Application.java | 23 +++++++++++++++++++ .../test/java/io/jooby/i3787/Issue3787.java | 20 ++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 tests/src/test/java/io/jooby/i3787/Application.java create mode 100644 tests/src/test/java/io/jooby/i3787/Issue3787.java diff --git a/tests/src/test/java/io/jooby/i3787/Application.java b/tests/src/test/java/io/jooby/i3787/Application.java new file mode 100644 index 0000000000..e0951565f8 --- /dev/null +++ b/tests/src/test/java/io/jooby/i3787/Application.java @@ -0,0 +1,23 @@ +package io.jooby.i3787; + +import io.jooby.Jooby; +import io.jooby.problem.HttpProblem; + +class Application extends Jooby { + { + get("/throw", ctx -> { + throw new CustomException(); + }); + + error(CustomException.class, (ctx, throwable, statusCode) -> { + var problem = HttpProblem.badRequest("A Client Error — Obviously"); + ctx.getRouter().getErrorHandler().apply(ctx, problem, statusCode); + }); + } + + public static void main(String[] args) { + runApp(args, Application::new); + } + + static class CustomException extends RuntimeException {} +} diff --git a/tests/src/test/java/io/jooby/i3787/Issue3787.java b/tests/src/test/java/io/jooby/i3787/Issue3787.java new file mode 100644 index 0000000000..b894ea8bce --- /dev/null +++ b/tests/src/test/java/io/jooby/i3787/Issue3787.java @@ -0,0 +1,20 @@ +package io.jooby.i3787; + +import io.jooby.StatusCode; +import io.jooby.test.MockContext; +import io.jooby.test.MockRouter; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class Issue3787 { + + private final MockRouter router = new MockRouter(new Application()).setFullExecution(true); + + @Test + void test() { + var ctx = new MockContext(); + router.tryError(new Application.CustomException(), ctx); // throws java.lang.NullPointerException + assertEquals(StatusCode.BAD_REQUEST, ctx.getResponseCode()); + } +} From 639a0a7256837948d9303e5ea238035eeacb7ed9 Mon Sep 17 00:00:00 2001 From: Volodymyr Kliushnichenko Date: Thu, 16 Oct 2025 18:08:37 +0300 Subject: [PATCH 2/3] fix NPE --- .../jooby-test/src/main/java/io/jooby/test/MockRouter.java | 2 ++ tests/src/test/java/io/jooby/i3787/Issue3787.java | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/jooby-test/src/main/java/io/jooby/test/MockRouter.java b/modules/jooby-test/src/main/java/io/jooby/test/MockRouter.java index 6c9564bb9a..4a0f32590b 100644 --- a/modules/jooby-test/src/main/java/io/jooby/test/MockRouter.java +++ b/modules/jooby-test/src/main/java/io/jooby/test/MockRouter.java @@ -472,6 +472,8 @@ public void tryError(Throwable cause, Consumer consumer) { */ public void tryError(Throwable cause, Context ctx) { var app = supplier.get(); + MockContext findContext = ctx instanceof MockContext ? (MockContext) ctx : newContext(); + findContext.setRouter(app); var handler = app.getErrorHandler(); handler.apply(ctx, cause, app.errorCode(cause)); } diff --git a/tests/src/test/java/io/jooby/i3787/Issue3787.java b/tests/src/test/java/io/jooby/i3787/Issue3787.java index b894ea8bce..4f0f50ad0c 100644 --- a/tests/src/test/java/io/jooby/i3787/Issue3787.java +++ b/tests/src/test/java/io/jooby/i3787/Issue3787.java @@ -12,9 +12,9 @@ public class Issue3787 { private final MockRouter router = new MockRouter(new Application()).setFullExecution(true); @Test - void test() { + void issue3787() { var ctx = new MockContext(); - router.tryError(new Application.CustomException(), ctx); // throws java.lang.NullPointerException + router.tryError(new Application.CustomException(), ctx); assertEquals(StatusCode.BAD_REQUEST, ctx.getResponseCode()); } } From f0cfa185ec9771ae81b109d87dd3b7bb279109d9 Mon Sep 17 00:00:00 2001 From: Volodymyr Kliushnichenko Date: Thu, 16 Oct 2025 18:20:10 +0300 Subject: [PATCH 3/3] support Problem Details handler in the MockRouter --- .../src/main/java/io/jooby/test/MockRouter.java | 4 ++++ tests/src/test/java/io/jooby/i3787/Application.java | 12 +++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/modules/jooby-test/src/main/java/io/jooby/test/MockRouter.java b/modules/jooby-test/src/main/java/io/jooby/test/MockRouter.java index 4a0f32590b..0cfd91ce15 100644 --- a/modules/jooby-test/src/main/java/io/jooby/test/MockRouter.java +++ b/modules/jooby-test/src/main/java/io/jooby/test/MockRouter.java @@ -18,6 +18,7 @@ import io.jooby.Router; import io.jooby.SneakyThrows; import io.jooby.WebSocket; +import io.jooby.problem.ProblemDetailsHandler; /** * Utility class that allows us to execute routes using a {@link MockContext}. @@ -74,6 +75,9 @@ public Object value() { * @param application Source application. */ public MockRouter(@NonNull Jooby application) { + if (application.problemDetailsIsEnabled()) { + application.error(ProblemDetailsHandler.from(application.getConfig())); + } this.supplier = () -> application; } diff --git a/tests/src/test/java/io/jooby/i3787/Application.java b/tests/src/test/java/io/jooby/i3787/Application.java index e0951565f8..af2968dc7c 100644 --- a/tests/src/test/java/io/jooby/i3787/Application.java +++ b/tests/src/test/java/io/jooby/i3787/Application.java @@ -1,10 +1,19 @@ package io.jooby.i3787; +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; import io.jooby.Jooby; import io.jooby.problem.HttpProblem; +import java.util.Map; + class Application extends Jooby { { + Config problemDetailsConfig = ConfigFactory.parseMap( + Map.of("problem.details.enabled", true) + ); + getEnvironment().setConfig(problemDetailsConfig.withFallback(getConfig())); + get("/throw", ctx -> { throw new CustomException(); }); @@ -19,5 +28,6 @@ public static void main(String[] args) { runApp(args, Application::new); } - static class CustomException extends RuntimeException {} + static class CustomException extends RuntimeException { + } }