This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch mock2 in repository https://gitbox.apache.org/repos/asf/camel.git
commit ea578db07ec0c2fcc181d5037aa9d411bcaecf23 Author: Claus Ibsen <[email protected]> AuthorDate: Sun Nov 3 10:54:44 2024 +0100 CAMEL-21402: camel-mock - Make it easier to use more built-in languages in setting expectations using the existing fluent builder. Added xpath. --- .../camel-mock/src/main/docs/mock-component.adoc | 62 ++++++++++++++++------ .../camel/component/mock/MockValueBuilder.java | 14 +++++ .../camel/issues/MockExpectedHeaderXPathTest.java | 39 +++++++++++++- .../camel/support/builder/ExpressionBuilder.java | 60 +++++++++++++++++++++ .../camel/support/builder/PredicateBuilder.java | 59 ++++++++++++++++++++ 5 files changed, 217 insertions(+), 17 deletions(-) diff --git a/components/camel-mock/src/main/docs/mock-component.adoc b/components/camel-mock/src/main/docs/mock-component.adoc index 8573900f798..00ea34639a3 100644 --- a/components/camel-mock/src/main/docs/mock-component.adoc +++ b/components/camel-mock/src/main/docs/mock-component.adoc @@ -209,7 +209,53 @@ There are some examples of the Mock endpoint in use in the https://github.com/apache/camel/tree/main/core/camel-core/src/test/java/org/apache/camel/processor[`camel-core` processor tests]. -==== Using any language for expectations +==== Using built-in language for expectations + +When you want to check that a given message body or header is as expected, and the format is XML or JSon, +then you can use the Camel languages to perform the validation. + +This section covers the built-in support in the mock component for commonly used languages. +See next section for how to use all the Camel languages using the language builder style. + +You can use regular expressions as expectations, as follows: + +[source,java] +---- +mock.message(1).header("cheese").regex("value[2,3]"); +mock.message(2).header("cheese").regex("value[2,3]"); +// should not match +mock.message(0).header("cheese").not().regex("value[2,3]"); +mock.message(3).header("cheese").not().regex("value[2,3]"); +---- + +Here we use the _built-in_ `regex` function from the mock component, that makes +coding this easier. There are a limited set of functions out of the box. + +You can also use XPath as follows: + +[source,java] +---- +String filter = "/person[@name='James']"; +... +mock.message(0).header("cheese").xpath(filter).isFalse(); +mock.message(1).header("cheese").xpath(filter).isTrue(); +mock.message(2).header("cheese").xpath(filter).isFalse(); +---- + +You can also use xpath to check if it matches a given value such as: + +[source,java] +---- +String name = "/person/@name"; +... +mock.message(0).header("cheese").xpath(name).isEqualTo("Hiram"); +mock.message(1).header("cheese").xpath(name).isEqualTo("James"); +mock.message(2).header("cheese").xpath(name).isEqualTo("Jack"); +---- + +There are a number of built-in languages you can use, see next section for using all the languages. + +==== Using all the language for expectations using language builder When you want to check that a given message body or header is as expected, and the format is XML or JSon, then you can use the Camel languages to perform the validation. @@ -240,20 +286,6 @@ mock.message(1).predicate(expression().xpath("/person[@name='James']").source("h Notice that the xpath language need to use `source` to refer to the value should be from the header with key cheese. By default, the source is the message body, and therefore is only needed when you refer to headers/variables etc. -You can also use regular expressions as expectations, as follows: - -[source,java] ----- -mock.message(1).header("cheese").regex("value[2,3]"); -mock.message(2).header("cheese").regex("value[2,3]"); -// should not match -mock.message(0).header("cheese").not().regex("value[2,3]"); -mock.message(3).header("cheese").not().regex("value[2,3]"); ----- - -Here we use the _built-in_ `regex` function from the mock component, that makes -coding this easier. There are a limited set of functions out of the box. - To use any of the Camel languages then do as shown previously with the XPath example. === Mocking existing endpoints diff --git a/components/camel-mock/src/main/java/org/apache/camel/component/mock/MockValueBuilder.java b/components/camel-mock/src/main/java/org/apache/camel/component/mock/MockValueBuilder.java index c6dce5841d2..f4b56f9f753 100644 --- a/components/camel-mock/src/main/java/org/apache/camel/component/mock/MockValueBuilder.java +++ b/components/camel-mock/src/main/java/org/apache/camel/component/mock/MockValueBuilder.java @@ -110,6 +110,14 @@ public class MockValueBuilder implements Expression, Predicate { return answer; } + public Predicate isTrue() { + return onNewPredicate(PredicateBuilder.isTrue(expression)); + } + + public Predicate isFalse() { + return onNewPredicate(PredicateBuilder.isFalse(expression)); + } + public Predicate isNotEqualTo(Object value) { Expression right = asExpression(value); return onNewPredicate(PredicateBuilder.isNotEqualTo(expression, right)); @@ -211,6 +219,12 @@ public class MockValueBuilder implements Expression, Predicate { // Expression builders // ------------------------------------------------------------------------- + public MockValueBuilder xpath(String xpath) { + // work with string as result as xpath otherwise will use DOM types + Expression newExp = ExpressionBuilder.languageExpression(expression, "xpath", xpath, String.class); + return onNewValueBuilder(newExp); + } + public MockValueBuilder tokenize() { return tokenize("\n"); } diff --git a/core/camel-core/src/test/java/org/apache/camel/issues/MockExpectedHeaderXPathTest.java b/core/camel-core/src/test/java/org/apache/camel/issues/MockExpectedHeaderXPathTest.java index 07d7ade49b0..04c3bf25aff 100644 --- a/core/camel-core/src/test/java/org/apache/camel/issues/MockExpectedHeaderXPathTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/issues/MockExpectedHeaderXPathTest.java @@ -25,17 +25,19 @@ import static org.apache.camel.support.builder.PredicateBuilder.not; public class MockExpectedHeaderXPathTest extends ContextTestSupport { + protected String filter = "/person[@name='James']"; + protected String name = "/person/@name"; protected String matchingBody = "<person name='James' city='London'/>"; protected String notMatchingBody = "<person name='Hiram' city='Tampa'/>"; protected String notMatchingBody2 = "<person name='Jack' city='Houston'/>"; @Test - public void testHeaderXPath() throws Exception { + public void testHeaderXPathBuilderLanguageBuilder() throws Exception { MockEndpoint mock = getMockEndpoint("mock:result"); mock.expectedMessageCount(3); // xpath that takes input from a header - var xpath = expression().xpath("/person[@name='James']").source("header:cheese").end(); + var xpath = expression().xpath(filter).source("header:cheese").end(); // validate that some of the headers match and others do not mock.message(0).predicate(not(xpath)); @@ -49,6 +51,39 @@ public class MockExpectedHeaderXPathTest extends ContextTestSupport { mock.assertIsSatisfied(); } + @Test + public void testHeaderXPathPredicate() throws Exception { + MockEndpoint mock = getMockEndpoint("mock:result"); + mock.expectedMessageCount(3); + + // validate that some of the headers match and others do not + mock.message(0).header("cheese").xpath(filter).isFalse(); + mock.message(1).header("cheese").xpath(filter).isTrue(); + mock.message(2).header("cheese").xpath(filter).isFalse(); + + template.sendBodyAndHeader("direct:test", "message 1", "cheese", notMatchingBody); + template.sendBodyAndHeader("direct:test", "message 2", "cheese", matchingBody); + template.sendBodyAndHeader("direct:test", "message 3", "cheese", notMatchingBody2); + + mock.assertIsSatisfied(); + } + + @Test + public void testHeaderXPathExpressionAttribute() throws Exception { + MockEndpoint mock = getMockEndpoint("mock:result"); + mock.expectedMessageCount(3); + + mock.message(0).header("cheese").xpath(name).isEqualTo("Hiram"); + mock.message(1).header("cheese").xpath(name).isEqualTo("James"); + mock.message(2).header("cheese").xpath(name).isEqualTo("Jack"); + + template.sendBodyAndHeader("direct:test", "message 1", "cheese", notMatchingBody); + template.sendBodyAndHeader("direct:test", "message 2", "cheese", matchingBody); + template.sendBodyAndHeader("direct:test", "message 3", "cheese", notMatchingBody2); + + mock.assertIsSatisfied(); + } + @Override protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { diff --git a/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java b/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java index 26067f8ccc0..7165644a51d 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java @@ -49,12 +49,14 @@ import org.apache.camel.spi.PropertiesComponent; import org.apache.camel.spi.Registry; import org.apache.camel.spi.UnitOfWork; import org.apache.camel.support.ConstantExpressionAdapter; +import org.apache.camel.support.DefaultExchange; import org.apache.camel.support.ExchangeHelper; import org.apache.camel.support.ExpressionAdapter; import org.apache.camel.support.GroupIterator; import org.apache.camel.support.GroupTokenIterator; import org.apache.camel.support.LanguageHelper; import org.apache.camel.support.LanguageSupport; +import org.apache.camel.support.PropertyBindingSupport; import org.apache.camel.support.SingleInputTypedLanguageSupport; import org.apache.camel.util.InetAddressUtil; import org.apache.camel.util.ObjectHelper; @@ -1012,6 +1014,64 @@ public class ExpressionBuilder { }; } + /** + * Returns an expression for evaluating the expression/predicate using the given language + * + * @param expression the expression providing the input + * @param language the language + * @param value the value to use for evaluation + * @return an expression object which will evaluate the expression/predicate using the given language + */ + public static Expression languageExpression( + final Expression expression, final String language, final String value, Class<?> resultType) { + return new ExpressionAdapter() { + private Expression expr; + private Predicate pred; + + @Override + public Object evaluate(Exchange exchange) { + Object result = expression.evaluate(exchange, Object.class); + if (result != null) { + Exchange dummy = new DefaultExchange(exchange); + dummy.getMessage().setBody(result); + return expr.evaluate(dummy, resultType); + } + return null; + } + + @Override + public boolean matches(Exchange exchange) { + Object result = expression.evaluate(exchange, Object.class); + if (result != null) { + Exchange dummy = new DefaultExchange(exchange); + dummy.getMessage().setBody(result); + return pred.matches(dummy); + } + return false; + } + + @Override + public void init(CamelContext context) { + super.init(context); + Language lan = context.resolveLanguage(language); + if (lan != null) { + pred = lan.createPredicate(value); + pred.init(context); + expr = lan.createExpression(value); + expr.init(context); + PropertyBindingSupport.build().bind(context, expr, "resultQName", "string"); + } else { + throw new NoSuchLanguageException(language); + } + } + + @Override + public String toString() { + return language + "(" + expression + ")"; + } + }; + } + /** * Returns an expression for evaluating the expression/predicate using the given language * diff --git a/core/camel-support/src/main/java/org/apache/camel/support/builder/PredicateBuilder.java b/core/camel-support/src/main/java/org/apache/camel/support/builder/PredicateBuilder.java index 48435031045..d87ee46bdc1 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/builder/PredicateBuilder.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/builder/PredicateBuilder.java @@ -25,6 +25,8 @@ import org.apache.camel.CamelContext; import org.apache.camel.Exchange; import org.apache.camel.Expression; import org.apache.camel.Predicate; +import org.apache.camel.spi.Language; +import org.apache.camel.support.ExchangeHelper; import org.apache.camel.support.ExpressionToPredicateAdapter; import org.apache.camel.support.LanguageHelper; import org.apache.camel.support.ObjectHelper; @@ -178,6 +180,20 @@ public class PredicateBuilder { return in(predicates.toArray(new Predicate[0])); } + /** + * Is the predicate true + */ + public static Predicate isTrue(final Expression left) { + return PredicateBuilder.toPredicate(left); + } + + /** + * Is the predicate false + */ + public static Predicate isFalse(final Expression left) { + return PredicateBuilder.not(PredicateBuilder.toPredicate(left)); + } + public static Predicate isEqualTo(final Expression left, final Expression right) { return new BinaryPredicateSupport(left, right) { @@ -546,4 +562,47 @@ public class PredicateBuilder { } }; } + + /** + * Returns a predicate which is true if the expression matches the given language predicate + * + * @param expression the expression to evaluate + * @param language the language such as xpath, jq, groovy, etc. + * @param value the value as expression for the language + * @return a new predicate + */ + public static Predicate language(final Expression expression, final String language, final Object value) { + notNull(expression, "expression"); + notNull(language, "language"); + notNull(value, "value"); + + return new Predicate() { + + private Predicate pred; + + public boolean matches(Exchange exchange) { + Object value = expression.evaluate(exchange, Object.class); + if (value != null) { + Exchange dummy = ExchangeHelper.getDummy(exchange.getContext()); + dummy.getMessage().setBody(value); + return pred.matches(dummy); + } + return false; + } + + @Override + public void init(CamelContext camelContext) { + expression.init(camelContext); + Language lan = camelContext.resolveLanguage(language); + pred = lan.createPredicate(value.toString()); + pred.init(camelContext); + } + + @Override + public String toString() { + return language + "(" + expression + ")"; + } + }; + } + }
