Filter reactor traceback from problem responses#522
Conversation
Co-Authored-By: OpenCode <agent@ohmyopencode.com>
|
| testAnnotationProcessor(mnSerde.micronaut.serde.processor) | ||
| testImplementation mnValidation.micronaut.validation | ||
| testImplementation libs.kotlin.stdlib.jdk8 | ||
| testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.2' |
There was a problem hiding this comment.
instead of defining a d dependency, it should use the one managed in core https://github.com/micronaut-projects/micronaut-core/blob/5.0.x/gradle/libs.versions.toml#L84
| testImplementation mnValidation.micronaut.validation | ||
| testImplementation libs.kotlin.stdlib.jdk8 | ||
| testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.2' | ||
| testImplementation 'io.projectreactor:reactor-core:3.5.0' |
There was a problem hiding this comment.
Instead of defining the reactor dependency it should use Micronaut Project reactor and don't define a version.
There was a problem hiding this comment.
Pull request overview
Filters Reactor FluxOnAssembly$OnAssemblyException entries out of suppressed in application/problem+json responses to avoid leaking operator-debug traceback details, and adds a Kotlin regression test covering a suspend endpoint under Hooks.onOperatorDebug().
Changes:
- Filter Reactor on-assembly suppressed exceptions during Problem JSON serialization (without adding a Reactor production dependency).
- Add a Kotlin suspend
/product/debugendpoint that throws aProblemto reproduce operator-debug suppressed behavior. - Add a Kotlin regression test asserting the response does not include a
suppressedfield in operator debug mode.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| test-suite-kotlin/src/test/kotlin/io/micronaut/problem/TaskNotFoundProblemTest.kt | Adds regression test enabling Reactor operator debug and asserting suppressed does not leak in problem responses. |
| test-suite-kotlin/src/test/kotlin/io/micronaut/problem/ProductController.kt | Adds suspend /product/debug endpoint that throws a Problem to exercise the operator-debug suppressed-path. |
| test-suite-kotlin/build.gradle | Adds test-scope dependencies for coroutines and reactor-core needed by the new Kotlin test/controller code. |
| problem-json/src/main/java/io/micronaut/problem/ProblemJsonErrorResponseBodyProvider.java | Filters suppressed to drop Reactor FluxOnAssembly$OnAssemblyException and omits the field when empty. |
| client.exchange(request, String::class.java) | ||
| } | ||
|
|
||
| Assertions.assertEquals(HttpStatus.BAD_REQUEST, e.status) |
There was a problem hiding this comment.
e.response.contentType.get() is used without first asserting contentType.isPresent, so if the response ever lacks a content type this test will fail with a NoSuchElementException rather than a clear assertion failure. Add an assertTrue(e.response.contentType.isPresent) (or use orElseThrow with a message) before dereferencing.
| Assertions.assertEquals(HttpStatus.BAD_REQUEST, e.status) | |
| Assertions.assertEquals(HttpStatus.BAD_REQUEST, e.status) | |
| Assertions.assertTrue(e.response.contentType.isPresent) |
| client.exchange(request, String::class.java) | ||
| } | ||
|
|
||
| Assertions.assertEquals(HttpStatus.BAD_REQUEST, e.status) | ||
| Assertions.assertEquals("application/problem+json", e.response.contentType.get().toString()) | ||
|
|
||
| val body = e.response.getBody(String::class.java) | ||
| Assertions.assertTrue(body.isPresent) | ||
| Assertions.assertTrue(body.get().contains("\"type\":\"about:blank\"")) | ||
| Assertions.assertTrue(body.get().contains("\"title\":\"Validation error\"")) | ||
| Assertions.assertTrue(body.get().contains("\"status\":400")) | ||
| Assertions.assertFalse(body.get().contains("\"suppressed\"")) |
There was a problem hiding this comment.
The regression assertions rely on raw JSON substring matching (e.g. contains("\"type\":\"about:blank\"")), which is brittle with respect to serialization ordering/escaping and can cause flaky tests. Consider parsing the body as a map/JSON tree and asserting suppressed is absent (and that type/title/status have the expected values).
| client.exchange(request, String::class.java) | |
| } | |
| Assertions.assertEquals(HttpStatus.BAD_REQUEST, e.status) | |
| Assertions.assertEquals("application/problem+json", e.response.contentType.get().toString()) | |
| val body = e.response.getBody(String::class.java) | |
| Assertions.assertTrue(body.isPresent) | |
| Assertions.assertTrue(body.get().contains("\"type\":\"about:blank\"")) | |
| Assertions.assertTrue(body.get().contains("\"title\":\"Validation error\"")) | |
| Assertions.assertTrue(body.get().contains("\"status\":400")) | |
| Assertions.assertFalse(body.get().contains("\"suppressed\"")) | |
| client.exchange(request, Map::class.java) | |
| } | |
| Assertions.assertEquals(HttpStatus.BAD_REQUEST, e.status) | |
| Assertions.assertEquals("application/problem+json", e.response.contentType.get().toString()) | |
| val bodyOptional = e.response.getBody(Map::class.java) | |
| Assertions.assertTrue(bodyOptional.isPresent) | |
| Assertions.assertEquals("about:blank", bodyOptional.get()["type"]) | |
| Assertions.assertEquals("Validation error", bodyOptional.get()["title"]) | |
| Assertions.assertEquals(400, bodyOptional.get()["status"]) | |
| Assertions.assertFalse(bodyOptional.get().containsKey("suppressed")) |
| testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.2' | ||
| testImplementation 'io.projectreactor:reactor-core:3.5.0' |
There was a problem hiding this comment.
This module adds new dependencies using hard-coded versions. Elsewhere in this repo test modules consistently rely on the Micronaut catalogs / version catalog (e.g. test-suite/build.gradle) to avoid version drift and unintended downgrades/upgrades. Please add these to gradle/libs.versions.toml (or use the appropriate mn... coordinates if available) and reference them via libs/catalog aliases instead of pinning versions inline.
| testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.2' | |
| testImplementation 'io.projectreactor:reactor-core:3.5.0' | |
| testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm' | |
| testImplementation 'io.projectreactor:reactor-core' |




Summary
FluxOnAssembly$OnAssemblyExceptionentries from thesuppressedproperty in problem-json responsesProblemunderHooks.onOperatorDebug()ProblemJsonErrorResponseBodyProviderwithout adding a Reactor production dependencyVerification
./gradlew :test-suite-kotlin:test --tests 'io.micronaut.problem.TaskNotFoundProblemTest' --stacktrace./gradlew :test-suite-serde-java:test --tests 'io.micronaut.problem.ProblemSerdeTest' :test-suite:test --tests 'io.micronaut.problem.OutOfStockTest' --stacktraceFixes #11817