Skip to content

Commit 934c76d

Browse files
Escaping For setBody (#2810)
* fix: compute URL expressions in the target after request flow in the http client interceptor * refactor(core): use `getAuthority` instead of `getHost` for improved URL handling and modify target configurations - Introduced `computeDestinationExpressions` to streamline target URL modification logic. - Improved `DispatchingInterceptor` handling of base path resolution with illegal characters. - Enhanced `urlEncode` and `pathSeg` utility functions for safety and clarity. - Refactored `HTTPClientInterceptor` to adjust the target flow and error messages. - Added extensive URI tests, including custom-parsed and relative path resolution scenarios. - Updated `applyTargetModifications` for streamlined behavior during request processing. * feat(core): add URL encoding support for template evaluation in target URLs - Introduced `TemplateUtil` with a method to check for template markers. - Enhanced `TemplateExchangeExpression` to support URL encoding for dynamic templates. - Updated `Target` to skip unnecessary evaluation for URLs without template markers. - Added extensive tests to verify correct URL encoding across various languages (Groovy, SpEL, XPath, JSONPath). * docs(roadmap): update URL encoding details for template evaluation in target URLs - Documented the change to URL encode expressions inside `${}` in target URLs. - Updated tests to improve validation coverage and logging for clearer output. * feat(core): optimize expression handling for dynamic URLs with caching - Introduced a `templateExpressionCache` in `Target` to avoid redundant parsing and compilation of frequently used expressions. - Improved token parsing and evaluation logic in `TemplateExchangeExpression`, enhancing clarity and performance. - Renamed test helper method `extracted` to `testExpression` for better readability. - Updated test cases across multiple classes to cover new enhancements and ensure consistent behavior. * refactor(core): remove template expression caching from `Target` and simplify logic - Eliminated `templateExpressionCache` to reduce complexity and improve maintainability. - Updated `computeDestinationExpressions` to use `Collectors.toList()` for a mutable list. - Simplified `evaluateTemplate` by removing cache-related logic and comments on minor performance differences. * feat(core): introduce `Escaping` enum for customizable URL encoding in `Target` - Added `Escaping` enum with options `NONE`, `URL`, and `SEGMENT` to enhance placeholder escaping in URLs. - Updated `Target` to include configurable escaping behavior and a helper method `getEscapingFunction()`. - Extended test cases to validate new escaping options in various scenarios. * refactor(core): enhance URI handling and extend test coverage - Updated `URI` to leverage `var` for better readability and added JavaDoc for `resolve` method. - Improved `ResolverMap` handling of URI schemes, including `classpath:` and `internal:`. - Added new tests in `HTTPClientInterceptorTest` for complete path computation, including URL encoding. * feat(core): improve handling of illegal characters in URLs and enhance template evaluation - Added `UriIllegalCharacterDetector` for validating URI components with RFC 3986 compliance and optional extensions. - Updated `Target` to skip template evaluation for URLs without placeholders and log warnings for insecure configurations. - Enhanced `TemplateUtil.containsTemplateMarker` with null safety. - Improved `RewriteInterceptor` to handle query parsing errors with more informative logging. - Refactored tests and added new ones to validate changes. * refactor(core): simplify `URI` logic and enhance test readability - Refactored `removeLastSegment` method for concise logic using `Math.max()` to avoid redundant checks. - Replaced `assertEquals(false, ...)` with `assertFalse(...)` for improved readability in tests. - Cleaned up unused imports in `ResolverMapCombineTest`. * feat(core): enhance URI handling and replace `pathSeg` with `pathEncode` - Replaced `pathSeg` method with `pathEncode` for improved naming clarity and functionality in line with RFC 3986. - Introduced `IPv6Util` for robust validation of IPv6 address text, ensuring only allowed characters are accepted. - Refactored `Target` to include URL encoding strategy through a dedicated `escapingFunction` and removed redundant logic. - Streamlined URI combination logic in `ResolverMap` with utility methods for handling slashes and file URIs. - Added extensive tests covering URL path encoding, IPv6 address validation, and robustification of URI logic for edge cases. * refactor(core): improve code readability and consistency in URI tests and utilities - Adjusted visibility modifiers of constants to `final` for better immutability (`URITest`). - Reordered equality assertion arguments for better readability (`TargetTest`). - Replaced redundant comments with concise `<p/>` tags in JavaDocs for uniformity. - Refactored tests to leverage `assertNull` for null comparisons (`FileUtilTest`). - Removed unused method `stripIpv6BracketsIfNeeded` and redundant imports for cleaner code. - Enhanced URI combination logic and streamlined utility imports (`DispatchingInterceptor`). * feat(core): introduce `URIFactory` for enhanced URI creation and add comprehensive RFC 3986 compliance tests - Added `URIFactory` to support flexible URI creation with options for illegal character handling and backslash escaping. - Implemented extensive * test(core): extend URI test cases for edge scenarios in IPv4 and IPv6 parsing - Added edge case assertions for parsing IPv6 with empty ports. - Introduced additional checks for IPv4 host and port parsing scenarios. - Renamed test methods to reflect broader case coverage. * feat(core): enhance template marker detection and URL evaluation in `Target` and `CallInterceptor` - Replaced `evaluateExpressions` with `urlIsTemplate` for better semantics and clarity. - Improved `Target` and `CallInterceptor` to skip URL evaluation when no template markers are present. - Introduced stricter validation for configurations involving template markers and illegal characters. - Updated relevant tests to validate changes and added new test cases for URL template evaluation. * feat(core): add dynamic escaping to `setBody` and improve expression evaluation - Introduced JSON escaping support for `setBody` based on `contentType`. - Enhanced `ExchangeExpression` to centralize router retrieval logic. - Updated tests to include validation logging for transformation scenarios. - Fixed minor formatting inconsistencies and improved code readability. * feat(core): add XML escaping support and enhance `getEscapingFunction` - Introduced XML escaping using `StringEscapeUtils::escapeXml11`. - Updated `getEscapingFunction` to handle XML content type. - Added `XML` escaping type to `Escaping` enum. - Enhanced utility imports and updated dependencies for XML handling. * test(core): add JSON and Groovy null value handling tests in `SetBodyInterceptorTest` - Added tests to verify behavior for null values in JSONPath and Groovy expressions. - Enhanced `SetBodyInterceptorTest` with additional scenarios for content escaping. - Updated imports for improved code readability and consistency. * test(core): improve null value handling tests for JSONPath and Groovy in `SetBodyInterceptorTest` - Refactored tests to consolidate null value handling for JSONPath and Groovy. - Enhanced `SetBodyInterceptor` to handle `Object` evaluation results, ensuring compatibility and proper type handling. - Updated helper methods to restructure and reuse test setup across scenarios. * test(tutorials): log validation errors in `SoapArrayToJsonTutorialTest` - Added `.log().ifValidationFails()` to improve debugging of failing test scenarios. --------- Co-authored-by: Christian Gördes <118011644+christiangoerdes@users.noreply.github.com>
1 parent eb88166 commit 934c76d

9 files changed

Lines changed: 159 additions & 29 deletions

File tree

core/src/main/java/com/predic8/membrane/core/interceptor/lang/SetBodyInterceptor.java

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,26 @@
1616

1717
import com.predic8.membrane.annot.*;
1818
import com.predic8.membrane.core.exchange.*;
19+
import com.predic8.membrane.core.http.*;
1920
import com.predic8.membrane.core.interceptor.*;
21+
import com.predic8.membrane.core.lang.*;
2022
import com.predic8.membrane.core.util.*;
23+
import com.predic8.membrane.core.util.uri.*;
24+
import org.jetbrains.annotations.*;
2125
import org.slf4j.*;
2226

27+
import java.util.function.*;
28+
2329
import static com.predic8.membrane.core.exceptions.ProblemDetails.*;
2430
import static com.predic8.membrane.core.interceptor.Interceptor.Flow.*;
2531
import static com.predic8.membrane.core.interceptor.Outcome.*;
2632
import static com.predic8.membrane.core.interceptor.Outcome.ABORT;
33+
import static com.predic8.membrane.core.util.uri.EscapingUtil.Escaping.NONE;
34+
import static com.predic8.membrane.core.util.uri.EscapingUtil.getEscapingFunction;
2735
import static java.nio.charset.StandardCharsets.*;
2836

2937
/**
30-
* @description
31-
* sets the content of the HTTP message body to the specified value. The value
38+
* @description sets the content of the HTTP message body to the specified value. The value
3239
* can be a static string, or it can be dynamically generated by an expression.
3340
* Different languages such as SpEL, Groovy, XMLPath or JsonPath are supported.
3441
* setBody does not support conditional processing or loops. When you need these features,
@@ -58,16 +65,19 @@ private Outcome handleInternal(Exchange exchange, Flow flow) {
5865
var msg = exchange.getMessage(flow);
5966
try {
6067
// The value is typically set from YAML there we can assume UTF-8
61-
var result = exchangeExpression.evaluate(exchange, flow, String.class);
68+
var result = exchangeExpression.evaluate(exchange, flow, Object.class);
6269
if (result == null) {
6370
result = "null";
6471
}
65-
msg.setBodyContent(result.getBytes(UTF_8));
66-
67-
if (contentType!=null) {
72+
if (result instanceof String s) {
73+
msg.setBodyContent(s.getBytes(UTF_8));
74+
} else {
75+
// Hope that all possibles return types are covered
76+
msg.setBodyContent(result.toString().getBytes(UTF_8));
77+
}
78+
if (contentType != null) {
6879
msg.getHeader().setContentType(contentType);
6980
}
70-
7181
return CONTINUE;
7282
} catch (Exception e) {
7383
var root = ExceptionUtil.getRootCause(e);
@@ -84,6 +94,14 @@ private Outcome handleInternal(Exchange exchange, Flow flow) {
8494
}
8595
}
8696

97+
protected ExchangeExpression getExchangeExpression() {
98+
return TemplateExchangeExpression.newInstance(this, language, expression, router,
99+
getEscapingFunction(contentType).orElseGet(() -> {
100+
log.warn("Turning off escaping for 'setBody'. No escaping found for content type {}. To enable escaping set 'contentType' on setBody.", contentType);
101+
return getEscapingFunction(NONE);
102+
}));
103+
}
104+
87105
/**
88106
* Sets the expression to be evaluated for modifying the HTTP message body.
89107
* The provided string value can represent a static text or a dynamic expression

core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.predic8.membrane.core.lang.spel.*;
2424
import com.predic8.membrane.core.lang.xpath.*;
2525
import com.predic8.membrane.core.router.*;
26+
import org.jetbrains.annotations.*;
2627

2728
/**
2829
* Language expression that takes an exchange as input
@@ -59,15 +60,18 @@ enum Language {GROOVY, SPEL, XPATH, JSONPATH}
5960
* @return
6061
*/
6162
static ExchangeExpression expression(Interceptor interceptor, Language language, String expression) {
62-
var router = interceptor != null ? interceptor.getRouter() : null;
6363
return switch (language) {
64-
case GROOVY -> new GroovyExchangeExpression(expression, router);
65-
case SPEL -> new SpELExchangeExpression(expression,null, router); // parserContext is null on purpose ${} or #{} are not needed here
66-
case XPATH -> new XPathExchangeExpression(interceptor,expression, router);
67-
case JSONPATH -> new JsonpathExchangeExpression(expression, router);
64+
case GROOVY -> new GroovyExchangeExpression(expression, getRouter(interceptor));
65+
case SPEL -> new SpELExchangeExpression(expression,null, getRouter(interceptor)); // parserContext is null on purpose ${} or #{} are not needed here
66+
case XPATH -> new XPathExchangeExpression(interceptor,expression, getRouter(interceptor));
67+
case JSONPATH -> new JsonpathExchangeExpression(expression, getRouter(interceptor));
6868
};
6969
}
7070

71+
private static @Nullable Router getRouter(Interceptor interceptor) {
72+
return interceptor != null ? interceptor.getRouter() : null;
73+
}
74+
7175
/**
7276
* Allows to pass an Interceptor as an argument where there is no interceptor e.g. Target
7377
*/

core/src/main/java/com/predic8/membrane/core/util/uri/EscapingUtil.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,19 @@
1414

1515
package com.predic8.membrane.core.util.uri;
1616

17+
import com.predic8.membrane.core.http.*;
18+
import com.predic8.membrane.core.lang.*;
19+
import groovy.json.*;
20+
import org.apache.commons.text.StringEscapeUtils;
21+
import org.jetbrains.annotations.*;
22+
1723
import java.net.*;
24+
import java.util.*;
1825
import java.util.function.*;
1926

27+
import static com.predic8.membrane.core.http.MimeType.isJson;
28+
import static com.predic8.membrane.core.http.MimeType.isXML;
29+
import static com.predic8.membrane.core.util.uri.EscapingUtil.Escaping.*;
2030
import static java.lang.Character.*;
2131
import static java.nio.charset.StandardCharsets.*;
2232

@@ -30,20 +40,36 @@ public class EscapingUtil {
3040
* - {@code NONE}: No escaping is applied. Strings are returned as-is.
3141
* - {@code URL}: Encodes strings for safe inclusion in a URL, replacing spaces and
3242
* other special characters with their percent-encoded counterparts (e.g., SPACE -> +).
43+
* - {@code JSON}: Escapes strings for safe inclusion in a JSON context.
44+
* - {@code XML}: Escapes strings for safe inclusion in an XML context using XML 1.1 rules.
3345
* - {@code SEGMENT}: Encodes strings as safe URI path segments, ensuring they do not introduce
3446
* path separators, query delimiters, or other unsafe characters, as per RFC 3986.
3547
*/
3648
public enum Escaping {
3749
NONE,
3850
URL,
39-
SEGMENT
51+
SEGMENT,
52+
JSON,
53+
XML
54+
}
55+
56+
public static Optional<Function<String, String>> getEscapingFunction(String mimeType) {
57+
if (isJson(mimeType)) {
58+
return Optional.of(getEscapingFunction(JSON));
59+
}
60+
if (isXML(mimeType)) {
61+
return Optional.of(getEscapingFunction(XML));
62+
}
63+
return Optional.empty();
4064
}
4165

4266
public static Function<String, String> getEscapingFunction(Escaping escaping) {
4367
return switch (escaping) {
4468
case NONE -> Function.identity();
4569
case URL -> s -> URLEncoder.encode(s, UTF_8);
4670
case SEGMENT -> EscapingUtil::pathEncode;
71+
case JSON -> CommonBuiltInFunctions::toJSON; // Alternative: StringEscapeUtils::escapeJson ?
72+
case XML -> StringEscapeUtils::escapeXml11;
4773
};
4874
}
4975

core/src/test/java/com/predic8/membrane/core/interceptor/lang/SetBodyInterceptorTest.java

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@
1515
package com.predic8.membrane.core.interceptor.lang;
1616

1717
import com.predic8.membrane.core.exchange.*;
18-
import com.predic8.membrane.core.http.*;
18+
import com.predic8.membrane.core.lang.*;
1919
import com.predic8.membrane.core.router.*;
2020
import org.junit.jupiter.api.*;
2121

2222
import java.net.*;
2323

24-
import static com.predic8.membrane.core.http.Response.notImplemented;
24+
import static com.predic8.membrane.core.http.MimeType.*;
25+
import static com.predic8.membrane.core.http.Request.*;
26+
import static com.predic8.membrane.core.http.Response.*;
27+
import static com.predic8.membrane.core.lang.ExchangeExpression.Language.*;
2528
import static org.junit.jupiter.api.Assertions.*;
2629

2730
/**
@@ -37,7 +40,7 @@ class SetBodyInterceptorTest {
3740
void setup() throws URISyntaxException {
3841
sbi = new SetBodyInterceptor();
3942

40-
exc = Request.get("/foo").buildExchange();
43+
exc = get("/foo").buildExchange();
4144
exc.setResponse(notImplemented().body("bar").build());
4245
}
4346

@@ -57,7 +60,43 @@ void evalOfSimpleExpression() {
5760
assertEquals("/foo", exc.getRequest().getBodyAsStringDecoded());
5861
}
5962

63+
/**
64+
* When inserting a value from JSONPath into a JSON document like:
65+
* { "a": ${.a} }
66+
* and the value is null, the document should be:
67+
* { "a": null }
68+
*/
69+
@Nested
70+
class Null {
71+
72+
@Test
73+
void escapeNullJsonPath() throws URISyntaxException {
74+
callSetBody(JSONPATH, "${$.a}");
75+
}
76+
6077
@Test
78+
void escapeNullGroovy() throws URISyntaxException {
79+
callSetBody(GROOVY, "${fn.jsonPath('$.a')}");
80+
}
81+
82+
private void callSetBody(ExchangeExpression.Language language, String expression) throws URISyntaxException {
83+
exc = setJsonSample();
84+
sbi.setLanguage(language);
85+
sbi.setContentType(APPLICATION_JSON_UTF8);
86+
sbi.setValue(expression);
87+
sbi.init(new DefaultRouter());
88+
sbi.handleRequest(exc);
89+
assertEquals("null", exc.getRequest().getBodyAsStringDecoded());
90+
}
91+
92+
private Exchange setJsonSample() throws URISyntaxException {
93+
return post("/foo").json("""
94+
{"a":null}
95+
""").buildExchange();
96+
}
97+
}
98+
99+
@Test
61100
void response() {
62101
sbi.setValue("SC: ${statusCode}");
63102
sbi.init(new DefaultRouter());

core/src/test/java/com/predic8/membrane/core/interceptor/templating/TemplateInterceptorTest.java

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import com.fasterxml.jackson.databind.*;
1818
import com.predic8.membrane.core.exchange.*;
1919
import com.predic8.membrane.core.http.*;
20-
import com.predic8.membrane.core.resolver.*;
2120
import com.predic8.membrane.core.router.*;
2221
import com.predic8.membrane.core.security.*;
2322
import com.predic8.membrane.core.util.*;
@@ -29,6 +28,7 @@
2928
import javax.xml.parsers.*;
3029
import javax.xml.xpath.*;
3130
import java.io.*;
31+
import java.net.*;
3232
import java.nio.file.*;
3333
import java.util.*;
3434

@@ -51,7 +51,7 @@ public class TemplateInterceptorTest {
5151
Router router;
5252

5353
@BeforeEach
54-
void setUp(){
54+
void setUp() {
5555
router = new DummyTestRouter();
5656
ti = new TemplateInterceptor();
5757
exc = new Exchange(null);
@@ -84,23 +84,23 @@ void accessJson() throws Exception {
8484
@SuppressWarnings("unchecked")
8585
@Test
8686
void createJson() throws Exception {
87-
Exchange exchange = Request.put("/foo").contentType(APPLICATION_JSON).buildExchange();
87+
var exchange = Request.put("/foo").contentType(APPLICATION_JSON).buildExchange();
8888

8989
invokeInterceptor(exchange, """
9090
{"foo":7,"bar":"baz"}
9191
""", APPLICATION_JSON);
9292

9393
assertEquals(APPLICATION_JSON, exchange.getRequest().getHeader().getContentType());
9494

95-
Map<String,Object> m = om.readValue(exchange.getRequest().getBodyAsStringDecoded(),Map.class);
96-
assertEquals(7,m.get("foo"));
97-
assertEquals("baz",m.get("bar"));
95+
Map<String, Object> m = om.readValue(exchange.getRequest().getBodyAsStringDecoded(), Map.class);
96+
assertEquals(7, m.get("foo"));
97+
assertEquals("baz", m.get("bar"));
9898
}
9999

100100
@Test
101101
void accessBindings() throws Exception {
102102
Exchange exchange = post("/foo?a=1&b=2").contentType(TEXT_PLAIN).body("vlinder").buildExchange();
103-
exchange.setProperty("baz",7);
103+
exchange.setProperty("baz", 7);
104104

105105
invokeInterceptor(exchange, """
106106
<% for(h in header.allHeaderFields) { %>
@@ -123,7 +123,7 @@ void accessBindings() throws Exception {
123123
<%= p.key %> : <%= p.value %>
124124
<% } %>
125125
""", APPLICATION_JSON);
126-
126+
127127
String body = exchange.getRequest().getBodyAsStringDecoded();
128128
assertTrue(body.contains("/foo"));
129129
assertTrue(body.contains("Flow: \"REQUEST\""));
@@ -201,15 +201,15 @@ void contentTypeTestOther() {
201201
@Test
202202
void contentTypeTestJson() {
203203
setAndHandleRequest("json/template_test.json");
204-
assertEquals(APPLICATION_JSON,exc.getRequest().getHeader().getContentType());
204+
assertEquals(APPLICATION_JSON, exc.getRequest().getHeader().getContentType());
205205
}
206206

207207
@Test
208208
void contentTypeTestNoXml() {
209209
ti.setSrc("normal text");
210210
ti.init(router);
211211
ti.handleRequest(exc);
212-
assertEquals(TEXT_PLAIN,exc.getRequest().getHeader().getContentType());
212+
assertEquals(TEXT_PLAIN, exc.getRequest().getHeader().getContentType());
213213
}
214214

215215
@Test
@@ -223,7 +223,7 @@ void testPrettify() {
223223

224224
ti.setContentType(APPLICATION_JSON);
225225
ti.setSrc(inputJson);
226-
ti.setPretty( TRUE);
226+
ti.setPretty(TRUE);
227227
ti.init();
228228
assertArrayEquals(expectedPrettyJson.getBytes(UTF_8), ti.prettify(inputJson.getBytes(UTF_8)));
229229
}
@@ -246,14 +246,40 @@ void builtInFunctions() {
246246
exc.setProperty(SECURITY_SCHEMES, List.of(new BasicHttpSecurityScheme().username("alice")));
247247
ti.setContentType(APPLICATION_JSON);
248248
ti.setSrc("""
249-
{ "foo": ${user()} }
250-
""");
249+
{ "foo": ${user()} }
250+
""");
251251
ti.init(router);
252252
ti.handleRequest(exc);
253253

254254
assertTrue(exc.getRequest().getBodyAsStringDecoded().contains("alice"));
255255
}
256256

257+
/**
258+
* When inserting a value from JSONPath into a JSON document like:
259+
* { "a": ${.a} }
260+
* and the value is null, the document should be:
261+
* { "a": null }
262+
*/
263+
@Nested
264+
class Null {
265+
266+
@Test
267+
void escapeNull() throws URISyntaxException {
268+
exc = setJsonSample();
269+
ti.setContentType(APPLICATION_JSON_UTF8);
270+
ti.setSrc("${fn.jsonPath('$.a')}");
271+
ti.init(new DefaultRouter());
272+
ti.handleRequest(exc);
273+
assertEquals("null", exc.getRequest().getBodyAsStringDecoded());
274+
}
275+
276+
private Exchange setJsonSample() throws URISyntaxException {
277+
return post("/foo").json("""
278+
{"a":null}
279+
""").buildExchange();
280+
}
281+
}
282+
257283
private void setAndHandleRequest(String location) {
258284
ti.setLocation(Paths.get("src/test/resources/" + location).toString());
259285
ti.init(router);

core/src/test/java/com/predic8/membrane/core/util/xml/NormalizeXMLForJsonUtilTest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
/* Copyright 2026 predic8 GmbH, www.predic8.com
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License. */
14+
115
package com.predic8.membrane.core.util.xml;
216

317
import com.fasterxml.jackson.databind.*;

distribution/src/test/java/com/predic8/membrane/tutorials/soap/WSDLRewriterTutorialTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ void soapCall() throws IOException {
5959
.when()
6060
.post("http://localhost:%d/my-service".formatted(getPort()))
6161
.then()
62+
.log().ifValidationFails()
6263
.body("Envelope.Body.getCityResponse.population", equalTo("34665600"));
6364
// @formatter:on
6465
}

distribution/src/test/java/com/predic8/membrane/tutorials/transformation/RestGetToSoapTutorialTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ void restGetIsConvertedToSoapAndBackToJson() {
3333
.when()
3434
.get("http://localhost:2000/cities/Bielefeld")
3535
.then()
36+
.log().ifValidationFails()
3637
.statusCode(200)
3738
.body("country", equalTo("Germany"))
3839
.body("population", greaterThan(0));

0 commit comments

Comments
 (0)