From aab6e3b5bf508fa556a4d3c2ceaa223db6403bdb Mon Sep 17 00:00:00 2001 From: thomas Date: Mon, 18 May 2026 17:23:02 +0200 Subject: [PATCH 1/3] Improve error handling and status code consistency across interceptors and tests --- .../predic8/membrane/core/cli/RouterCLI.java | 2 - .../core/exceptions/ProblemDetails.java | 52 ++++++++++++------- .../interceptor/xml/Xml2JsonInterceptor.java | 2 +- .../interceptor/xslt/XSLTInterceptor.java | 47 +++++++++-------- .../lang/xpath/XPathExchangeExpression.java | 48 ++++++++++------- .../xml/Xml2JsonInterceptorTest.java | 43 ++++++++++----- .../interceptor/xslt/XSLTInterceptorTest.java | 26 +++++----- .../OpenAPIPublisherInterceptorTest.java | 51 ++++++++++-------- .../exceptions/ExceptionInterceptorTest.java | 40 ++++++++------ 9 files changed, 183 insertions(+), 128 deletions(-) diff --git a/core/src/main/java/com/predic8/membrane/core/cli/RouterCLI.java b/core/src/main/java/com/predic8/membrane/core/cli/RouterCLI.java index 8bdd695b47..5a9bf86d52 100644 --- a/core/src/main/java/com/predic8/membrane/core/cli/RouterCLI.java +++ b/core/src/main/java/com/predic8/membrane/core/cli/RouterCLI.java @@ -47,9 +47,7 @@ import java.security.SecureRandom; import java.util.Arrays; import java.util.List; -import java.util.Optional; import java.util.Scanner; -import java.util.stream.Collectors; import static com.predic8.membrane.annot.Constants.*; import static com.predic8.membrane.core.cli.util.JwkGenerator.generateJWK; diff --git a/core/src/main/java/com/predic8/membrane/core/exceptions/ProblemDetails.java b/core/src/main/java/com/predic8/membrane/core/exceptions/ProblemDetails.java index 7cae731834..23ec9952b1 100644 --- a/core/src/main/java/com/predic8/membrane/core/exceptions/ProblemDetails.java +++ b/core/src/main/java/com/predic8/membrane/core/exceptions/ProblemDetails.java @@ -11,23 +11,30 @@ package com.predic8.membrane.core.exceptions; -import com.fasterxml.jackson.core.*; -import com.fasterxml.jackson.databind.*; -import com.predic8.membrane.core.exchange.*; -import com.predic8.membrane.core.http.*; -import com.predic8.membrane.core.interceptor.*; -import org.jetbrains.annotations.*; -import org.slf4j.*; - -import java.util.*; - -import static com.predic8.membrane.core.exceptions.ProblemDetailsXML.*; -import static com.predic8.membrane.core.http.MimeType.*; -import static com.predic8.membrane.core.http.Response.*; -import static com.predic8.membrane.core.util.ExceptionUtil.*; -import static java.nio.charset.StandardCharsets.*; -import static java.util.Locale.*; -import static java.util.UUID.*; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.predic8.membrane.core.exchange.Exchange; +import com.predic8.membrane.core.http.Response; +import com.predic8.membrane.core.interceptor.Interceptor; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import static com.predic8.membrane.core.exceptions.ProblemDetailsXML.createXMLContent; +import static com.predic8.membrane.core.http.MimeType.APPLICATION_PROBLEM_JSON; +import static com.predic8.membrane.core.http.MimeType.TEXT_PLAIN_UTF8; +import static com.predic8.membrane.core.http.Response.statusCode; +import static com.predic8.membrane.core.util.ExceptionUtil.concatMessageAndCauseMessages; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Locale.ROOT; +import static java.util.UUID.randomUUID; /** @@ -344,7 +351,7 @@ private String getFullType(String type) { } private Response createContent(Map root, Exchange exchange) { - Response.ResponseBuilder builder = statusCode(status); + var builder = statusCode(correctStatusCodeForResponse(exchange,status)); try { if (exchange != null && (acceptXML(exchange) || exchange.getRequest().isXML())) { createXMLContent(root, builder); @@ -358,6 +365,15 @@ private Response createContent(Map root, Exchange exchange) { return builder.build(); } + /** + * If a user error (4XX) occurs in the response flow, convert error code to 500. + */ + private int correctStatusCodeForResponse(Exchange exc, int status) { + if (exc != null && exc.getResponse() != null && status >= 400 && status < 500) + return 500; + return status; + } + private boolean acceptXML(Exchange exchange) { String accept = exchange.getRequest().getHeader().getAccept(); if (accept == null) diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/xml/Xml2JsonInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/xml/Xml2JsonInterceptor.java index 2efa3bd159..955cf026f0 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/xml/Xml2JsonInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/xml/Xml2JsonInterceptor.java @@ -146,7 +146,7 @@ private void handleException(Exchange exc, Flow flow, Exception e, String msg) { log.info(msg, e); log.debug("", e); } - internal(router.getConfiguration().isProduction(), getDisplayName()).flow(flow).status(flow == REQUEST ? 400 : 500) + internal(router.getConfiguration().isProduction(), getDisplayName()).flow(flow).status(400) .detail(msg) .exception(e) .topLevel("charset-from-header", exc.getMessage(flow).getHeader().getCharset()) diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/xslt/XSLTInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/xslt/XSLTInterceptor.java index 355636896d..2b665fd738 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/xslt/XSLTInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/xslt/XSLTInterceptor.java @@ -13,29 +13,33 @@ limitations under the License. */ package com.predic8.membrane.core.interceptor.xslt; -import com.predic8.membrane.annot.*; -import com.predic8.membrane.core.exceptions.*; -import com.predic8.membrane.core.exchange.*; -import com.predic8.membrane.core.http.*; -import com.predic8.membrane.core.interceptor.*; -import com.predic8.membrane.core.multipart.*; -import com.predic8.membrane.core.util.*; -import org.jetbrains.annotations.*; -import org.slf4j.*; - -import javax.xml.transform.*; -import javax.xml.transform.stream.*; -import java.io.*; -import java.util.*; - -import static com.predic8.membrane.core.exceptions.ProblemDetails.*; +import com.predic8.membrane.annot.MCAttribute; +import com.predic8.membrane.annot.MCElement; +import com.predic8.membrane.core.exchange.Exchange; +import com.predic8.membrane.core.http.Message; +import com.predic8.membrane.core.interceptor.AbstractInterceptor; +import com.predic8.membrane.core.interceptor.Outcome; +import com.predic8.membrane.core.multipart.XOPReconstitutor; +import com.predic8.membrane.core.util.ConfigurationException; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.xml.transform.TransformerException; +import javax.xml.transform.stream.StreamSource; +import java.util.Map; + import static com.predic8.membrane.core.exceptions.ProblemDetails.internal; -import static com.predic8.membrane.core.interceptor.Interceptor.Flow.*; -import static com.predic8.membrane.core.interceptor.Outcome.*; +import static com.predic8.membrane.core.exceptions.ProblemDetails.user; +import static com.predic8.membrane.core.interceptor.Interceptor.Flow.REQUEST; +import static com.predic8.membrane.core.interceptor.Interceptor.Flow.RESPONSE; import static com.predic8.membrane.core.interceptor.Outcome.ABORT; +import static com.predic8.membrane.core.interceptor.Outcome.CONTINUE; import static com.predic8.membrane.core.util.ExceptionUtil.getRootCause; -import static com.predic8.membrane.core.util.text.StringUtil.*; -import static com.predic8.membrane.core.util.text.TextUtil.*; +import static com.predic8.membrane.core.util.text.StringUtil.tail; +import static com.predic8.membrane.core.util.text.StringUtil.truncateAfter; +import static com.predic8.membrane.core.util.text.TextUtil.linkURL; +import static com.predic8.membrane.core.util.text.TextUtil.removeFinalChar; /** * @description

@@ -75,7 +79,8 @@ private Outcome handleInternal(Exchange exc, Flow flow) { } catch (TransformerException e) { log.debug("", e); var cause = getRootCause(e); - if (cause.getMessage() != null && cause.getMessage().contains("not allowed in prolog")) { + // rolog matches Prolog and prolog + if (cause.getMessage() != null && cause.getMessage().contains("rolog")) { user(router.getConfiguration().isProduction(), getDisplayName()) .title("Content not allowed in prolog of XML input.") .detail("Check for extra characters before the XML declaration ") diff --git a/core/src/main/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpression.java b/core/src/main/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpression.java index fa3c6910eb..5cbcc51408 100644 --- a/core/src/main/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpression.java +++ b/core/src/main/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpression.java @@ -14,23 +14,31 @@ package com.predic8.membrane.core.lang.xpath; -import com.predic8.membrane.core.config.xml.*; -import com.predic8.membrane.core.exchange.*; -import com.predic8.membrane.core.http.*; -import com.predic8.membrane.core.interceptor.*; -import com.predic8.membrane.core.lang.*; -import com.predic8.membrane.core.router.*; -import com.predic8.membrane.core.util.xml.*; -import com.predic8.membrane.core.util.xml.parser.*; -import org.jetbrains.annotations.*; -import org.slf4j.*; -import org.w3c.dom.*; - -import javax.xml.namespace.*; -import javax.xml.xpath.*; - -import static com.predic8.membrane.core.util.text.StringUtil.*; -import static javax.xml.xpath.XPathConstants.*; +import com.predic8.membrane.core.config.xml.XmlConfig; +import com.predic8.membrane.core.exchange.Exchange; +import com.predic8.membrane.core.http.Message; +import com.predic8.membrane.core.interceptor.Interceptor; +import com.predic8.membrane.core.interceptor.XMLSupport; +import com.predic8.membrane.core.lang.AbstractExchangeExpression; +import com.predic8.membrane.core.lang.ExchangeExpressionException; +import com.predic8.membrane.core.router.Router; +import com.predic8.membrane.core.util.xml.XMLUtil; +import com.predic8.membrane.core.util.xml.XPathUtil; +import com.predic8.membrane.core.util.xml.parser.HardenedXmlParser; +import com.predic8.membrane.core.util.xml.parser.XmlParser; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.NodeList; + +import javax.xml.namespace.QName; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathEvaluationResult; +import javax.xml.xpath.XPathExpressionException; + +import static com.predic8.membrane.core.util.text.StringUtil.tail; +import static com.predic8.membrane.core.util.text.StringUtil.truncateAfter; +import static javax.xml.xpath.XPathConstants.NODESET; public class XPathExchangeExpression extends AbstractExchangeExpression { @@ -116,14 +124,16 @@ private Object evaluateAndCast(Message msg, QName xmlType) throws XPathExpressio } } catch (RuntimeException e) { // Parser errors may escape as unchecked exceptions. - if (causeMessageContains(e, "not allowed in prolog")) { + // Matches: prolog and Prolog + if (causeMessageContains(e, "rolog")) { throw new ExchangeExpressionException(expression, e, "Content not allowed in prolog of XML input.") .detail("There are extra characters before the XML declaration ") .body(truncateAfter(msg.getBodyAsStringDecoded(), 50)) .excludeException(); } - if (causeMessageContains(e, "is not allowed in trailing section")) { + // Matches: Content and content + if (causeMessageContains(e, "ontent")) { throw new ExchangeExpressionException(expression, e, "Content not allowed in trailing section of XML input.") .detail("There are extra characters after the XML root element (after the final closing tag like ).") .body(tail(msg.getBodyAsStringDecoded(), 50)) diff --git a/core/src/test/java/com/predic8/membrane/core/interceptor/xml/Xml2JsonInterceptorTest.java b/core/src/test/java/com/predic8/membrane/core/interceptor/xml/Xml2JsonInterceptorTest.java index 4c08f979a7..6f62560323 100644 --- a/core/src/test/java/com/predic8/membrane/core/interceptor/xml/Xml2JsonInterceptorTest.java +++ b/core/src/test/java/com/predic8/membrane/core/interceptor/xml/Xml2JsonInterceptorTest.java @@ -14,21 +14,30 @@ package com.predic8.membrane.core.interceptor.xml; -import com.fasterxml.jackson.databind.*; -import com.predic8.membrane.core.exchange.*; -import com.predic8.membrane.core.http.*; -import com.predic8.membrane.core.interceptor.*; -import com.predic8.membrane.core.router.*; -import org.apache.commons.io.*; -import org.jetbrains.annotations.*; -import org.junit.jupiter.api.*; -import org.slf4j.*; - -import java.io.*; -import java.nio.charset.*; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.predic8.membrane.core.exchange.Exchange; +import com.predic8.membrane.core.http.MimeType; +import com.predic8.membrane.core.http.Request; +import com.predic8.membrane.core.http.Response; +import com.predic8.membrane.core.interceptor.Outcome; +import com.predic8.membrane.core.router.DefaultRouter; +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.nio.charset.Charset; import static com.predic8.membrane.core.http.MimeType.*; -import static java.nio.charset.StandardCharsets.*; +import static com.predic8.membrane.core.http.Request.get; +import static java.nio.charset.StandardCharsets.ISO_8859_1; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.*; @@ -95,6 +104,14 @@ void validTestxml2jsonResponse() throws Exception { getJsonRootFromStream(processThroughInterceptorResponse(loadResource("/xml/content-utf-8-encoding-utf-8.xml"))).get("bar").get("futf").asText()); } + @Test + void statusCodeOfResponseError() throws URISyntaxException { + var exc = get("/foo").buildExchange(); + exc.setResponse(Response.ok().contentType(TEXT_XML).body("").build()); + interceptor.handleResponse(exc); + assertEquals(500, exc.getResponse().getStatusCode()); + } + private JsonNode getJsonRootFromStream(InputStream stream) throws IOException { return new ObjectMapper().readTree(stream); } diff --git a/core/src/test/java/com/predic8/membrane/core/interceptor/xslt/XSLTInterceptorTest.java b/core/src/test/java/com/predic8/membrane/core/interceptor/xslt/XSLTInterceptorTest.java index 29f2c1772e..648b3eb76f 100644 --- a/core/src/test/java/com/predic8/membrane/core/interceptor/xslt/XSLTInterceptorTest.java +++ b/core/src/test/java/com/predic8/membrane/core/interceptor/xslt/XSLTInterceptorTest.java @@ -13,22 +13,20 @@ limitations under the License. */ package com.predic8.membrane.core.interceptor.xslt; -import com.predic8.membrane.core.exchange.*; -import com.predic8.membrane.core.http.*; -import com.predic8.membrane.core.interceptor.*; -import com.predic8.membrane.core.router.*; -import org.hamcrest.*; -import org.junit.jupiter.api.*; -import org.xml.sax.*; - -import javax.xml.xpath.*; -import java.io.*; -import java.net.*; +import com.predic8.membrane.core.exchange.Exchange; +import com.predic8.membrane.core.router.DummyTestRouter; +import org.junit.jupiter.api.Test; +import org.xml.sax.InputSource; + +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; import static com.predic8.membrane.core.http.Request.get; -import static com.predic8.membrane.core.http.Response.*; +import static com.predic8.membrane.core.http.Response.ok; import static com.predic8.membrane.core.interceptor.Outcome.ABORT; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class XSLTInterceptorTest { @@ -83,7 +81,7 @@ void noContentInProlog() throws Exception { i.init(new DummyTestRouter()); assertEquals(ABORT, i.handleRequest(exc)); assertEquals(400, exc.getResponse().getStatusCode()); - String body = exc.getResponse().getBodyAsStringDecoded(); + var body = exc.getResponse().getBodyAsStringDecoded(); assertTrue(body.contains("rubbish")); assertTrue(body.contains("not allowed in prolog")); } diff --git a/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIPublisherInterceptorTest.java b/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIPublisherInterceptorTest.java index 0bd8c20331..f1ff07e435 100644 --- a/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIPublisherInterceptorTest.java +++ b/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIPublisherInterceptorTest.java @@ -16,26 +16,31 @@ package com.predic8.membrane.core.openapi.serviceproxy; -import com.fasterxml.jackson.databind.*; -import com.predic8.membrane.core.exchange.*; -import com.predic8.membrane.core.http.*; -import com.predic8.membrane.core.openapi.util.*; -import com.predic8.membrane.core.proxies.*; -import com.predic8.membrane.core.router.*; -import com.predic8.membrane.core.util.*; -import io.swagger.v3.parser.*; -import org.junit.jupiter.api.*; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.predic8.membrane.core.exchange.Exchange; +import com.predic8.membrane.core.http.Header; +import com.predic8.membrane.core.http.Request; +import com.predic8.membrane.core.openapi.util.OpenAPITestUtils; +import com.predic8.membrane.core.proxies.NullProxy; +import com.predic8.membrane.core.router.DefaultRouter; +import com.predic8.membrane.core.util.URIFactory; +import io.swagger.v3.parser.ObjectMapperFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import java.io.*; -import java.util.*; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; -import static com.predic8.membrane.core.http.MimeType.*; -import static com.predic8.membrane.core.interceptor.Outcome.*; +import static com.predic8.membrane.core.http.MimeType.APPLICATION_PROBLEM_JSON; +import static com.predic8.membrane.core.interceptor.Outcome.RETURN; import static org.junit.jupiter.api.Assertions.*; -@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class OpenAPIPublisherInterceptorTest { private final ObjectMapper omYaml = ObjectMapperFactory.createYaml(); @@ -68,7 +73,7 @@ void setUp() { } @Test - public void constructor() { + void constructor() { assertTrue(interceptor.apis.size() >= 27); assertNotNull(interceptor.apis.get("references-test-v1-0")); assertNotNull(interceptor.apis.get("strings-test-api-v1-0")); @@ -78,14 +83,14 @@ public void constructor() { assertNotNull(interceptor.apis.get("references-response-test-v1-0")); } - final List uiParameters() { + final static List uiParameters() { return new ArrayList<>() {{ add(UI_OLD); add(OpenAPIPublisherInterceptor.PATH_UI); }}; } - final List metaParameters() { + final static List metaParameters() { return new ArrayList<>() {{ add(META_OLD); add(OpenAPIPublisherInterceptor.PATH); @@ -94,7 +99,7 @@ final List metaParameters() { @ParameterizedTest @MethodSource("metaParameters") - public void getApiDirectory(String testPath) throws Exception { + void getApiDirectory(String testPath) throws Exception { get.getRequest().setUri(testPath); assertEquals( RETURN, interceptor.handleRequest(get)); assertTrue(OpenAPITestUtils.getMapFromResponse(get).size() >= 27); @@ -102,9 +107,9 @@ public void getApiDirectory(String testPath) throws Exception { @ParameterizedTest @MethodSource("metaParameters") - public void getHTMLOverview(String testPath) { + void getHTMLOverview(String testPath) { get.getRequest().setUri(testPath); - Header header = new Header(); + var header = new Header(); header.setAccept("html"); get.getRequest().setHeader(header); assertEquals( RETURN, interceptor.handleRequest(get)); @@ -113,7 +118,7 @@ public void getHTMLOverview(String testPath) { @ParameterizedTest @MethodSource("uiParameters") - public void getSwaggerUI(String testPath) { + void getSwaggerUI(String testPath) { get.getRequest().setUri(testPath + "/nested-objects-and-arrays-test-api-v1-0"); assertEquals( RETURN, interceptor.handleRequest(get)); assertTrue(get.getResponse().getBodyAsStringDecoded().contains("html")); @@ -121,7 +126,7 @@ public void getSwaggerUI(String testPath) { @ParameterizedTest @MethodSource("uiParameters") - public void getSwaggerUIWrongId(String testPath) throws Exception { + void getSwaggerUIWrongId(String testPath) throws Exception { get.getRequest().setUri(testPath + "/wrong-id-0"); assertEquals( RETURN, interceptor.handleRequest(get)); assertEquals( 404, get.getResponse().getStatusCode()); @@ -130,7 +135,7 @@ public void getSwaggerUIWrongId(String testPath) throws Exception { @ParameterizedTest @MethodSource("uiParameters") - public void getSwaggerUINoId(String testPath) throws Exception { + void getSwaggerUINoId(String testPath) throws Exception { get.getRequest().setUri(testPath); assertEquals( RETURN, interceptor.handleRequest(get)); assertEquals( 404, get.getResponse().getStatusCode()); diff --git a/core/src/test/java/com/predic8/membrane/core/openapi/validators/exceptions/ExceptionInterceptorTest.java b/core/src/test/java/com/predic8/membrane/core/openapi/validators/exceptions/ExceptionInterceptorTest.java index 8077d253ce..6314a4a471 100644 --- a/core/src/test/java/com/predic8/membrane/core/openapi/validators/exceptions/ExceptionInterceptorTest.java +++ b/core/src/test/java/com/predic8/membrane/core/openapi/validators/exceptions/ExceptionInterceptorTest.java @@ -16,20 +16,26 @@ package com.predic8.membrane.core.openapi.validators.exceptions; -import com.fasterxml.jackson.databind.*; -import com.predic8.membrane.core.exchange.*; -import com.predic8.membrane.core.http.*; -import com.predic8.membrane.core.openapi.serviceproxy.*; -import com.predic8.membrane.core.openapi.validators.security.*; -import com.predic8.membrane.core.router.*; -import org.junit.jupiter.api.*; -import org.springframework.http.*; - -import static com.predic8.membrane.core.interceptor.Outcome.*; -import static com.predic8.membrane.core.openapi.serviceproxy.OpenAPISpec.YesNoOpenAPIOption.*; -import static com.predic8.membrane.core.openapi.util.OpenAPITestUtils.*; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.predic8.membrane.core.exchange.Exchange; +import com.predic8.membrane.core.http.Request; +import com.predic8.membrane.core.openapi.serviceproxy.OpenAPIInterceptor; +import com.predic8.membrane.core.openapi.serviceproxy.OpenAPISpec; +import com.predic8.membrane.core.openapi.validators.security.AbstractSecurityValidatorTest; +import com.predic8.membrane.core.router.Router; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static com.predic8.membrane.core.http.Request.get; +import static com.predic8.membrane.core.http.Response.ok; +import static com.predic8.membrane.core.interceptor.Outcome.CONTINUE; +import static com.predic8.membrane.core.interceptor.Outcome.RETURN; +import static com.predic8.membrane.core.openapi.serviceproxy.OpenAPISpec.YesNoOpenAPIOption.YES; +import static com.predic8.membrane.core.openapi.util.OpenAPITestUtils.createProxy; import static com.predic8.membrane.test.TestUtil.getPathFromResource; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.springframework.http.MediaType.APPLICATION_JSON; public class ExceptionInterceptorTest extends AbstractSecurityValidatorTest { @@ -58,7 +64,7 @@ void paramWithNoSchema() throws Exception { @Test void unknownType() throws Exception { Exchange exc = new Request.Builder().get("/unknown-type").buildExchange(); - exc.setResponse(Response.ok().body("{}").contentType(MediaType.APPLICATION_JSON.toString()).build()); + exc.setResponse(ok().body("{}").contentType(APPLICATION_JSON.toString()).build()); exc.setOriginalRequestUri("/unknown-type"); assertEquals(CONTINUE, interceptor.handleRequest(exc)); @@ -68,14 +74,14 @@ void unknownType() throws Exception { @Test void nowhere() throws Exception { - Exchange exc = new Request.Builder().get("/nowhere").buildExchange(); - exc.setResponse(Response.ok().body("{}").contentType(MediaType.APPLICATION_JSON.toString()).build()); + var exc = get("/nowhere").buildExchange(); + exc.setResponse(ok().body("{}").contentType(APPLICATION_JSON.toString()).build()); exc.setOriginalRequestUri("/nowhere"); assertEquals(CONTINUE, interceptor.handleRequest(exc)); assertEquals(RETURN, interceptor.handleResponse(exc)); - assertEquals(400,exc.getResponse().getStatusCode()); + assertEquals(500,exc.getResponse().getStatusCode()); JsonNode json = om.readTree(exc.getResponse().getBodyAsStream()); assertEquals("https://membrane-api.io/problems/user/openapi",json.get("type").asText()); } From 8be0335f4ce7b419e13c6ce6ffda6f77876c73c7 Mon Sep 17 00:00:00 2001 From: thomas Date: Mon, 18 May 2026 17:24:02 +0200 Subject: [PATCH 2/3] Remove redundant `final` modifier from static methods in tests --- .../openapi/serviceproxy/OpenAPIPublisherInterceptorTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIPublisherInterceptorTest.java b/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIPublisherInterceptorTest.java index f1ff07e435..d896d73a5d 100644 --- a/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIPublisherInterceptorTest.java +++ b/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIPublisherInterceptorTest.java @@ -83,14 +83,14 @@ void constructor() { assertNotNull(interceptor.apis.get("references-response-test-v1-0")); } - final static List uiParameters() { + static List uiParameters() { return new ArrayList<>() {{ add(UI_OLD); add(OpenAPIPublisherInterceptor.PATH_UI); }}; } - final static List metaParameters() { + static List metaParameters() { return new ArrayList<>() {{ add(META_OLD); add(OpenAPIPublisherInterceptor.PATH); From 2f08ef587ebcc964ad25483574a3af7366792587 Mon Sep 17 00:00:00 2001 From: thomas Date: Mon, 18 May 2026 20:36:20 +0200 Subject: [PATCH 3/3] Add request-response flow handling to correct status code logic --- .../predic8/membrane/core/exceptions/ProblemDetails.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/src/main/java/com/predic8/membrane/core/exceptions/ProblemDetails.java b/core/src/main/java/com/predic8/membrane/core/exceptions/ProblemDetails.java index 23ec9952b1..47c5e32d58 100644 --- a/core/src/main/java/com/predic8/membrane/core/exceptions/ProblemDetails.java +++ b/core/src/main/java/com/predic8/membrane/core/exceptions/ProblemDetails.java @@ -31,6 +31,8 @@ import static com.predic8.membrane.core.http.MimeType.APPLICATION_PROBLEM_JSON; import static com.predic8.membrane.core.http.MimeType.TEXT_PLAIN_UTF8; import static com.predic8.membrane.core.http.Response.statusCode; +import static com.predic8.membrane.core.interceptor.Interceptor.Flow.REQUEST; +import static com.predic8.membrane.core.interceptor.Interceptor.Flow.RESPONSE; import static com.predic8.membrane.core.util.ExceptionUtil.concatMessageAndCauseMessages; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Locale.ROOT; @@ -369,6 +371,12 @@ private Response createContent(Map root, Exchange exchange) { * If a user error (4XX) occurs in the response flow, convert error code to 500. */ private int correctStatusCodeForResponse(Exchange exc, int status) { + if (flow == REQUEST) + return status; + if (flow == RESPONSE && status >= 400 && status < 500) + return 500; + + // flow is not set. If there is already a response, it is probably the response flow. if (exc != null && exc.getResponse() != null && status >= 400 && status < 500) return 500; return status;