This is an automated email from the ASF dual-hosted git repository. christophd pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit 371e18657eab910f8778b4f5a5132de112245c5c Author: Christoph Deppisch <[email protected]> AuthorDate: Wed Oct 14 22:30:17 2026 +0200 CAMEL-25378: Use MockEndpoint processor infrastructure for Citrus validation Leverage whenExchangeReceived() and whenAnyExchangeReceived() from MockEndpoint to dispatch Citrus validation and response logic via CitrusValidationProcessor. This replaces the previous custom spec resolution and indexing approach. Benefits: - Eliminates custom spec resolution, indexing and state tracking - Reuses MockEndpoint's built-in processor dispatch (indexed and default) for both validation and response handling - Simplifies the Gherkin-style API with a fluent when().receive().body().then().respond().body() chain - Failure reporting uses performAssertions() to capture processor exceptions from the exchange and add them to the failures list; for validate mode the exception is cleared so it does not propagate through the route Co-Authored-By: Claude Opus 4.6 <[email protected]> --- .../apache/camel/catalog/components/citrus.json | 24 +- .../camel/catalog/docs/citrus-component.adoc | 36 +- components/camel-test/camel-test-citrus/pom.xml | 10 +- .../citrus/CitrusComponentConfigurer.java | 39 +- .../component/citrus/CitrusEndpointConfigurer.java | 16 +- .../component/citrus/CitrusEndpointUriFactory.java | 16 +- .../org/apache/camel/component/citrus/citrus.json | 24 +- .../src/main/docs/citrus-component.adoc | 36 +- .../camel/component/citrus/CitrusComponent.java | 4 +- .../camel/component/citrus/CitrusEndpoint.java | 370 +++++----------- ...roducer.java => CitrusValidationProcessor.java} | 154 +++---- .../component/citrus/MessageResponseSpec.java | 59 +++ .../component/citrus/MessageValidationSpec.java | 72 ++++ .../citrus/CitrusMultipleEndpointsTest.java | 8 +- .../camel/component/citrus/CitrusReceiveTest.java | 59 ++- .../citrus/CitrusSequentialValidationTest.java | 38 +- .../component/citrus/CitrusValidateJsonTest.java | 19 +- .../citrus/CitrusValidatePlainTextTest.java | 11 +- .../component/citrus/CitrusValidateXmlTest.java | 8 +- .../component/citrus/CitrusValidateYamlTest.java | 13 +- .../builder/endpoint/StaticEndpointBuilders.java | 10 +- .../endpoint/dsl/CitrusEndpointBuilderFactory.java | 466 ++++++++++++++++++++- 22 files changed, 930 insertions(+), 562 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/citrus.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/citrus.json index ced95b3a80fb..76941d15c7f3 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/citrus.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/citrus.json @@ -20,17 +20,31 @@ "consumerOnly": false, "producerOnly": true, "lenientProperties": false, - "browsable": false, + "browsable": true, "remote": false }, "componentProperties": { "lazyStartProducer": { "index": 0, "kind": "property", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail [...] - "autowiredEnabled": { "index": 1, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching t [...] + "log": { "index": 1, "kind": "property", "displayName": "Log", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "To turn on logging when the mock receives an incoming message. This will log only one time at INFO level for the incoming message. For more detailed logging, then set the logger to DEBUG level for the org.apache.camel.compone [...] + "autowiredEnabled": { "index": 2, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching t [...] + "exchangeFormatter": { "index": 3, "kind": "property", "displayName": "Exchange Formatter", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.ExchangeFormatter", "deprecated": false, "autowired": true, "secret": false, "description": "To use a custom ExchangeFormatter to format the Exchange into a String suitable for logging." } }, "properties": { "operation": { "index": 0, "kind": "path", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "java.lang.String", "enum": [ "validate", "receive" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation to perform. 'validate' validates exchanges without modifying them. 'receive' validates exchanges and returns a simulated response." }, - "name": { "index": 1, "kind": "path", "displayName": "Name", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "A unique name to identify this endpoint. Allows multiple Citrus endpoints with different validation expectations in the same route." }, - "messageType": { "index": 2, "kind": "parameter", "displayName": "Message Type", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The message type for validation. Determines which Citrus validation module is used. Supported values: json, xml, yaml, plaintext." }, - "lazyStartProducer": { "index": 3, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produc [...] + "name": { "index": 1, "kind": "path", "displayName": "Name", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Name of mock endpoint" }, + "assertPeriod": { "index": 2, "kind": "parameter", "displayName": "Assert Period", "group": "producer", "label": "producer", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "description": "Sets a grace period after which the mock will re-assert to ensure the preliminary assertion is still valid. This is used, for example, to assert that exactly a number of messages arrive. For example, if the expected count was set [...] + "expectedCount": { "index": 3, "kind": "parameter", "displayName": "Expected Count", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": -1, "description": "Specifies the expected number of message exchanges that should be received by this mock. Beware: If you want to expect that 0 messages, then take extra care, as 0 matches when the tests starts, so you need to s [...] + "messageType": { "index": 4, "kind": "parameter", "displayName": "Message Type", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The message type for validation. Determines which Citrus validation module is used. Supported values: json, xml, yaml, plaintext." }, + "copyOnExchange": { "index": 5, "kind": "parameter", "displayName": "Copy On Exchange", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Sets whether to make a deep copy of the incoming Exchange when received at this mock endpoint." }, + "failFast": { "index": 6, "kind": "parameter", "displayName": "Fail Fast", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Sets whether assertIsSatisfied() should fail fast at the first detected failed expectation while it may otherwise wait for all expected messages to arrive before performing expectations verifica [...] + "lazyStartProducer": { "index": 7, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produc [...] + "log": { "index": 8, "kind": "parameter", "displayName": "Log", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "To turn on logging when the mock receives an incoming message. This will log only one time at INFO level for the incoming message. For more detailed logging, then set the logger to DEBUG level for the org [...] + "reportGroup": { "index": 9, "kind": "parameter", "displayName": "Report Group", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "A number that is used to turn on throughput logging based on groups of the size." }, + "resultMinimumWaitTime": { "index": 10, "kind": "parameter", "displayName": "Result Minimum Wait Time", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the minimum expected amount of time the assertIsSatisfied() will wait on a latch until it is satisfied" }, + "resultWaitTime": { "index": 11, "kind": "parameter", "displayName": "Result Wait Time", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the maximum amount of time the assertIsSatisfied() will wait on a latch until it is satisfied" }, + "retainFirst": { "index": 12, "kind": "parameter", "displayName": "Retain First", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": -1, "description": "Specifies to only retain the first nth number of received Exchanges. This is used when testing with big data, to reduce memory consumption by not storing copies of every Exchange this mock endp [...] + "retainLast": { "index": 13, "kind": "parameter", "displayName": "Retain Last", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": -1, "description": "Specifies to only retain the last nth number of received Exchanges. This is used when testing with big data, to reduce memory consumption by not storing copies of every Exchange this mock endpoin [...] + "sleepForEmptyTest": { "index": 14, "kind": "parameter", "displayName": "Sleep For Empty Test", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "description": "Allows a sleep to be specified to wait to check that this mock really is empty when expectedMessageCount(int) is called with zero value" }, + "browseLimit": { "index": 15, "kind": "parameter", "displayName": "Browse Limit", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 100, "description": "Maximum number of messages to keep in memory available for browsing. Use 0 for unlimited." } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/citrus-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/citrus-component.adoc index 280ee52e74a4..2d914f66c330 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/citrus-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/citrus-component.adoc @@ -219,14 +219,14 @@ exchange in sequence. ==== Single validation for all exchanges -Use `allMessages()` to define one validation specification that is applied to every exchange. +Use `validateAll()` to define one validation specification that is applied to every exchange. The returned `MessageValidationSpec` provides a fluent API with `body()`, `header()`, and `validate()` methods: [source,java] ---- CitrusEndpoint citrus = context.getEndpoint("citrus:validate?messageType=json", CitrusEndpoint.class); -citrus.allMessages() +citrus.validateAll() .body("{\"status\": \"ok\"}") .header("operation", "test"); citrus.expectedMessageCount(3); @@ -241,17 +241,17 @@ citrus.assertIsSatisfied(); ==== Per-exchange sequential validation -Use `message()` to specify different validation expectations for the 1st, 2nd, 3rd, etc. -exchange. Each call to `message()` auto-increments the index, so specs are applied in order. +Use `validate()` to specify different validation expectations for the 1st, 2nd, 3rd, etc. +exchange. Each call to `validate()` auto-increments the index, so specs are applied in order. Each incoming exchange triggers the next validation spec in sequence: [source,java] ---- CitrusEndpoint citrus = context.getEndpoint("citrus:validate?messageType=json", CitrusEndpoint.class); -citrus.message().body("{\"event\": \"created\"}").header("action", "create"); -citrus.message().body("{\"event\": \"updated\"}").header("action", "update"); -citrus.message().body("{\"event\": \"deleted\"}").header("action", "delete"); +citrus.validate().body("{\"event\": \"created\"}").header("action", "create"); +citrus.validate().body("{\"event\": \"updated\"}").header("action", "update"); +citrus.validate().body("{\"event\": \"deleted\"}").header("action", "delete"); citrus.expectedMessageCount(3); template.sendBodyAndHeader("direct:start", "{\"event\": \"created\"}", "action", "create"); @@ -261,24 +261,24 @@ template.sendBodyAndHeader("direct:start", "{\"event\": \"deleted\"}", "action", citrus.assertIsSatisfied(); ---- -You can also use `message(index)` to target a specific exchange by its 0-based position. +You can also use `validate(index)` to target a specific exchange by its 0-based position. -When both `message(index)` and `allMessages()` are configured, the indexed spec takes priority — -it replaces `allMessages()` for that exchange entirely (the two are never combined). The -`allMessages()` spec acts as a fallback for exchanges that have no matching indexed spec. +When both `validate(index)` and `validateAll()` are configured, the indexed spec takes priority — +it replaces `validateAll()` for that exchange entirely (the two are never combined). The +`validateAll()` spec acts as a fallback for exchanges that have no matching indexed spec. -If an exchange arrives at an index with no matching indexed spec and no `allMessages()` fallback, +If an exchange arrives at an index with no matching indexed spec and no `validateAll()` fallback, the endpoint records a failure. For the `validate` operation, the failure is collected and reported when `assertIsSatisfied()` is called (similar to MockEndpoint). For the `receive` operation, the failure is thrown immediately as an exception. [source,java] ---- -citrus.message(0).body("{\"event\": \"start\"}"); -citrus.allMessages().body("{\"event\": \"progress\"}").header("type", "status"); +citrus.validate(0).body("{\"event\": \"start\"}"); +citrus.validateAll().body("{\"event\": \"progress\"}").header("type", "status"); -// Exchange 0 → validated against message(0) only (allMessages() is NOT applied) -// Exchange 1, 2, ... → validated against allMessages() (body + header) +// Exchange 0 → validated against validate(0) only (validateAll() is NOT applied) +// Exchange 1, 2, ... → validated against validateAll() (body + header) ---- === Receive operation (request/response simulation) @@ -395,7 +395,7 @@ such as JsonPath or XPath expressions. These are provided by the Citrus `Default [source,java] ---- CitrusEndpoint citrus = context.getEndpoint("citrus:validate?messageType=json", CitrusEndpoint.class); -citrus.allMessages().validate(new DefaultValidations().jsonPath() +citrus.validateAll().validate(new DefaultValidations().jsonPath() .expression("$.user.name", "John") .expression("$.user.age", "30")); ---- @@ -403,7 +403,7 @@ citrus.allMessages().validate(new DefaultValidations().jsonPath() [source,java] ---- CitrusEndpoint citrus = context.getEndpoint("citrus:validate?messageType=xml", CitrusEndpoint.class); -citrus.allMessages().validate(new DefaultValidations().xpath() +citrus.validateAll().validate(new DefaultValidations().xpath() .expression("/order/status", "completed") .expression("/order/total", "99.99")); ---- diff --git a/components/camel-test/camel-test-citrus/pom.xml b/components/camel-test/camel-test-citrus/pom.xml index 58ca14a9c68b..278ed3139486 100644 --- a/components/camel-test/camel-test-citrus/pom.xml +++ b/components/camel-test/camel-test-citrus/pom.xml @@ -43,6 +43,10 @@ <groupId>org.apache.camel</groupId> <artifactId>camel-support</artifactId> </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-mock</artifactId> + </dependency> <!-- Citrus core dependencies --> <dependency> @@ -67,12 +71,6 @@ <artifactId>camel-direct</artifactId> <scope>test</scope> </dependency> - <dependency> - <groupId>org.apache.camel</groupId> - <artifactId>camel-mock</artifactId> - <scope>test</scope> - </dependency> - <!-- Citrus validation modules for tests --> <dependency> <groupId>org.citrusframework</groupId> diff --git a/components/camel-test/camel-test-citrus/src/generated/java/org/apache/camel/component/citrus/CitrusComponentConfigurer.java b/components/camel-test/camel-test-citrus/src/generated/java/org/apache/camel/component/citrus/CitrusComponentConfigurer.java index 4042100f8627..74bcd2cc166f 100644 --- a/components/camel-test/camel-test-citrus/src/generated/java/org/apache/camel/component/citrus/CitrusComponentConfigurer.java +++ b/components/camel-test/camel-test-citrus/src/generated/java/org/apache/camel/component/citrus/CitrusComponentConfigurer.java @@ -10,48 +10,13 @@ import org.apache.camel.spi.PropertyConfigurerGetter; import org.apache.camel.spi.ConfigurerStrategy; import org.apache.camel.spi.GeneratedPropertyConfigurer; import org.apache.camel.util.CaseInsensitiveMap; -import org.apache.camel.support.component.PropertyConfigurerSupport; +import org.apache.camel.component.mock.MockComponentConfigurer; /** * Generated by camel build tools - do NOT edit this file! */ @Generated("org.apache.camel.maven.packaging.EndpointSchemaGeneratorMojo") @SuppressWarnings("unchecked") -public class CitrusComponentConfigurer extends PropertyConfigurerSupport implements GeneratedPropertyConfigurer, PropertyConfigurerGetter { - - @Override - public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) { - CitrusComponent target = (CitrusComponent) obj; - switch (ignoreCase ? name.toLowerCase() : name) { - case "autowiredenabled": - case "autowiredEnabled": target.setAutowiredEnabled(property(camelContext, boolean.class, value)); return true; - case "lazystartproducer": - case "lazyStartProducer": target.setLazyStartProducer(property(camelContext, boolean.class, value)); return true; - default: return false; - } - } - - @Override - public Class<?> getOptionType(String name, boolean ignoreCase) { - switch (ignoreCase ? name.toLowerCase() : name) { - case "autowiredenabled": - case "autowiredEnabled": return boolean.class; - case "lazystartproducer": - case "lazyStartProducer": return boolean.class; - default: return null; - } - } - - @Override - public Object getOptionValue(Object obj, String name, boolean ignoreCase) { - CitrusComponent target = (CitrusComponent) obj; - switch (ignoreCase ? name.toLowerCase() : name) { - case "autowiredenabled": - case "autowiredEnabled": return target.isAutowiredEnabled(); - case "lazystartproducer": - case "lazyStartProducer": return target.isLazyStartProducer(); - default: return null; - } - } +public class CitrusComponentConfigurer extends MockComponentConfigurer implements GeneratedPropertyConfigurer, PropertyConfigurerGetter { } diff --git a/components/camel-test/camel-test-citrus/src/generated/java/org/apache/camel/component/citrus/CitrusEndpointConfigurer.java b/components/camel-test/camel-test-citrus/src/generated/java/org/apache/camel/component/citrus/CitrusEndpointConfigurer.java index 28fa0eae3282..bdaf11cc6a38 100644 --- a/components/camel-test/camel-test-citrus/src/generated/java/org/apache/camel/component/citrus/CitrusEndpointConfigurer.java +++ b/components/camel-test/camel-test-citrus/src/generated/java/org/apache/camel/component/citrus/CitrusEndpointConfigurer.java @@ -10,35 +10,31 @@ import org.apache.camel.spi.PropertyConfigurerGetter; import org.apache.camel.spi.ConfigurerStrategy; import org.apache.camel.spi.GeneratedPropertyConfigurer; import org.apache.camel.util.CaseInsensitiveMap; -import org.apache.camel.support.component.PropertyConfigurerSupport; +import org.apache.camel.component.mock.MockEndpointConfigurer; /** * Generated by camel build tools - do NOT edit this file! */ @Generated("org.apache.camel.maven.packaging.EndpointSchemaGeneratorMojo") @SuppressWarnings("unchecked") -public class CitrusEndpointConfigurer extends PropertyConfigurerSupport implements GeneratedPropertyConfigurer, PropertyConfigurerGetter { +public class CitrusEndpointConfigurer extends MockEndpointConfigurer implements GeneratedPropertyConfigurer, PropertyConfigurerGetter { @Override public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) { CitrusEndpoint target = (CitrusEndpoint) obj; switch (ignoreCase ? name.toLowerCase() : name) { - case "lazystartproducer": - case "lazyStartProducer": target.setLazyStartProducer(property(camelContext, boolean.class, value)); return true; case "messagetype": case "messageType": target.setMessageType(property(camelContext, java.lang.String.class, value)); return true; - default: return false; + default: return super.configure(camelContext, obj, name, value, ignoreCase); } } @Override public Class<?> getOptionType(String name, boolean ignoreCase) { switch (ignoreCase ? name.toLowerCase() : name) { - case "lazystartproducer": - case "lazyStartProducer": return boolean.class; case "messagetype": case "messageType": return java.lang.String.class; - default: return null; + default: return super.getOptionType(name, ignoreCase); } } @@ -46,11 +42,9 @@ public class CitrusEndpointConfigurer extends PropertyConfigurerSupport implemen public Object getOptionValue(Object obj, String name, boolean ignoreCase) { CitrusEndpoint target = (CitrusEndpoint) obj; switch (ignoreCase ? name.toLowerCase() : name) { - case "lazystartproducer": - case "lazyStartProducer": return target.isLazyStartProducer(); case "messagetype": case "messageType": return target.getMessageType(); - default: return null; + default: return super.getOptionValue(obj, name, ignoreCase); } } } diff --git a/components/camel-test/camel-test-citrus/src/generated/java/org/apache/camel/component/citrus/CitrusEndpointUriFactory.java b/components/camel-test/camel-test-citrus/src/generated/java/org/apache/camel/component/citrus/CitrusEndpointUriFactory.java index 7255e03cdc6e..64ff55b2ec76 100644 --- a/components/camel-test/camel-test-citrus/src/generated/java/org/apache/camel/component/citrus/CitrusEndpointUriFactory.java +++ b/components/camel-test/camel-test-citrus/src/generated/java/org/apache/camel/component/citrus/CitrusEndpointUriFactory.java @@ -24,11 +24,23 @@ public class CitrusEndpointUriFactory extends org.apache.camel.support.component private static final Set<String> ENDPOINT_IDENTITY_PROPERTY_NAMES; private static final Map<String, String> MULTI_VALUE_PREFIXES; static { - Set<String> props = new HashSet<>(4); + Set<String> props = new HashSet<>(16); + props.add("assertPeriod"); + props.add("browseLimit"); + props.add("copyOnExchange"); + props.add("expectedCount"); + props.add("failFast"); props.add("lazyStartProducer"); + props.add("log"); props.add("messageType"); props.add("name"); props.add("operation"); + props.add("reportGroup"); + props.add("resultMinimumWaitTime"); + props.add("resultWaitTime"); + props.add("retainFirst"); + props.add("retainLast"); + props.add("sleepForEmptyTest"); PROPERTY_NAMES = Collections.unmodifiableSet(props); SECRET_PROPERTY_NAMES = Collections.emptySet(); ENDPOINT_IDENTITY_PROPERTY_NAMES = Collections.emptySet(); @@ -48,7 +60,7 @@ public class CitrusEndpointUriFactory extends org.apache.camel.support.component Map<String, Object> copy = copyParameters(properties); uri = buildPathParameter(syntax, uri, "operation", null, true, copy); - uri = buildPathParameter(syntax, uri, "name", null, false, copy); + uri = buildPathParameter(syntax, uri, "name", null, true, copy); uri = buildQueryParameters(uri, copy, encode); return uri; } diff --git a/components/camel-test/camel-test-citrus/src/generated/resources/META-INF/org/apache/camel/component/citrus/citrus.json b/components/camel-test/camel-test-citrus/src/generated/resources/META-INF/org/apache/camel/component/citrus/citrus.json index ced95b3a80fb..76941d15c7f3 100644 --- a/components/camel-test/camel-test-citrus/src/generated/resources/META-INF/org/apache/camel/component/citrus/citrus.json +++ b/components/camel-test/camel-test-citrus/src/generated/resources/META-INF/org/apache/camel/component/citrus/citrus.json @@ -20,17 +20,31 @@ "consumerOnly": false, "producerOnly": true, "lenientProperties": false, - "browsable": false, + "browsable": true, "remote": false }, "componentProperties": { "lazyStartProducer": { "index": 0, "kind": "property", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail [...] - "autowiredEnabled": { "index": 1, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching t [...] + "log": { "index": 1, "kind": "property", "displayName": "Log", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "To turn on logging when the mock receives an incoming message. This will log only one time at INFO level for the incoming message. For more detailed logging, then set the logger to DEBUG level for the org.apache.camel.compone [...] + "autowiredEnabled": { "index": 2, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching t [...] + "exchangeFormatter": { "index": 3, "kind": "property", "displayName": "Exchange Formatter", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.ExchangeFormatter", "deprecated": false, "autowired": true, "secret": false, "description": "To use a custom ExchangeFormatter to format the Exchange into a String suitable for logging." } }, "properties": { "operation": { "index": 0, "kind": "path", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "java.lang.String", "enum": [ "validate", "receive" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation to perform. 'validate' validates exchanges without modifying them. 'receive' validates exchanges and returns a simulated response." }, - "name": { "index": 1, "kind": "path", "displayName": "Name", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "A unique name to identify this endpoint. Allows multiple Citrus endpoints with different validation expectations in the same route." }, - "messageType": { "index": 2, "kind": "parameter", "displayName": "Message Type", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The message type for validation. Determines which Citrus validation module is used. Supported values: json, xml, yaml, plaintext." }, - "lazyStartProducer": { "index": 3, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produc [...] + "name": { "index": 1, "kind": "path", "displayName": "Name", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Name of mock endpoint" }, + "assertPeriod": { "index": 2, "kind": "parameter", "displayName": "Assert Period", "group": "producer", "label": "producer", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "description": "Sets a grace period after which the mock will re-assert to ensure the preliminary assertion is still valid. This is used, for example, to assert that exactly a number of messages arrive. For example, if the expected count was set [...] + "expectedCount": { "index": 3, "kind": "parameter", "displayName": "Expected Count", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": -1, "description": "Specifies the expected number of message exchanges that should be received by this mock. Beware: If you want to expect that 0 messages, then take extra care, as 0 matches when the tests starts, so you need to s [...] + "messageType": { "index": 4, "kind": "parameter", "displayName": "Message Type", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The message type for validation. Determines which Citrus validation module is used. Supported values: json, xml, yaml, plaintext." }, + "copyOnExchange": { "index": 5, "kind": "parameter", "displayName": "Copy On Exchange", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Sets whether to make a deep copy of the incoming Exchange when received at this mock endpoint." }, + "failFast": { "index": 6, "kind": "parameter", "displayName": "Fail Fast", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Sets whether assertIsSatisfied() should fail fast at the first detected failed expectation while it may otherwise wait for all expected messages to arrive before performing expectations verifica [...] + "lazyStartProducer": { "index": 7, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produc [...] + "log": { "index": 8, "kind": "parameter", "displayName": "Log", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "To turn on logging when the mock receives an incoming message. This will log only one time at INFO level for the incoming message. For more detailed logging, then set the logger to DEBUG level for the org [...] + "reportGroup": { "index": 9, "kind": "parameter", "displayName": "Report Group", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "A number that is used to turn on throughput logging based on groups of the size." }, + "resultMinimumWaitTime": { "index": 10, "kind": "parameter", "displayName": "Result Minimum Wait Time", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the minimum expected amount of time the assertIsSatisfied() will wait on a latch until it is satisfied" }, + "resultWaitTime": { "index": 11, "kind": "parameter", "displayName": "Result Wait Time", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the maximum amount of time the assertIsSatisfied() will wait on a latch until it is satisfied" }, + "retainFirst": { "index": 12, "kind": "parameter", "displayName": "Retain First", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": -1, "description": "Specifies to only retain the first nth number of received Exchanges. This is used when testing with big data, to reduce memory consumption by not storing copies of every Exchange this mock endp [...] + "retainLast": { "index": 13, "kind": "parameter", "displayName": "Retain Last", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": -1, "description": "Specifies to only retain the last nth number of received Exchanges. This is used when testing with big data, to reduce memory consumption by not storing copies of every Exchange this mock endpoin [...] + "sleepForEmptyTest": { "index": 14, "kind": "parameter", "displayName": "Sleep For Empty Test", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "description": "Allows a sleep to be specified to wait to check that this mock really is empty when expectedMessageCount(int) is called with zero value" }, + "browseLimit": { "index": 15, "kind": "parameter", "displayName": "Browse Limit", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 100, "description": "Maximum number of messages to keep in memory available for browsing. Use 0 for unlimited." } } } diff --git a/components/camel-test/camel-test-citrus/src/main/docs/citrus-component.adoc b/components/camel-test/camel-test-citrus/src/main/docs/citrus-component.adoc index 280ee52e74a4..2d914f66c330 100644 --- a/components/camel-test/camel-test-citrus/src/main/docs/citrus-component.adoc +++ b/components/camel-test/camel-test-citrus/src/main/docs/citrus-component.adoc @@ -219,14 +219,14 @@ exchange in sequence. ==== Single validation for all exchanges -Use `allMessages()` to define one validation specification that is applied to every exchange. +Use `validateAll()` to define one validation specification that is applied to every exchange. The returned `MessageValidationSpec` provides a fluent API with `body()`, `header()`, and `validate()` methods: [source,java] ---- CitrusEndpoint citrus = context.getEndpoint("citrus:validate?messageType=json", CitrusEndpoint.class); -citrus.allMessages() +citrus.validateAll() .body("{\"status\": \"ok\"}") .header("operation", "test"); citrus.expectedMessageCount(3); @@ -241,17 +241,17 @@ citrus.assertIsSatisfied(); ==== Per-exchange sequential validation -Use `message()` to specify different validation expectations for the 1st, 2nd, 3rd, etc. -exchange. Each call to `message()` auto-increments the index, so specs are applied in order. +Use `validate()` to specify different validation expectations for the 1st, 2nd, 3rd, etc. +exchange. Each call to `validate()` auto-increments the index, so specs are applied in order. Each incoming exchange triggers the next validation spec in sequence: [source,java] ---- CitrusEndpoint citrus = context.getEndpoint("citrus:validate?messageType=json", CitrusEndpoint.class); -citrus.message().body("{\"event\": \"created\"}").header("action", "create"); -citrus.message().body("{\"event\": \"updated\"}").header("action", "update"); -citrus.message().body("{\"event\": \"deleted\"}").header("action", "delete"); +citrus.validate().body("{\"event\": \"created\"}").header("action", "create"); +citrus.validate().body("{\"event\": \"updated\"}").header("action", "update"); +citrus.validate().body("{\"event\": \"deleted\"}").header("action", "delete"); citrus.expectedMessageCount(3); template.sendBodyAndHeader("direct:start", "{\"event\": \"created\"}", "action", "create"); @@ -261,24 +261,24 @@ template.sendBodyAndHeader("direct:start", "{\"event\": \"deleted\"}", "action", citrus.assertIsSatisfied(); ---- -You can also use `message(index)` to target a specific exchange by its 0-based position. +You can also use `validate(index)` to target a specific exchange by its 0-based position. -When both `message(index)` and `allMessages()` are configured, the indexed spec takes priority — -it replaces `allMessages()` for that exchange entirely (the two are never combined). The -`allMessages()` spec acts as a fallback for exchanges that have no matching indexed spec. +When both `validate(index)` and `validateAll()` are configured, the indexed spec takes priority — +it replaces `validateAll()` for that exchange entirely (the two are never combined). The +`validateAll()` spec acts as a fallback for exchanges that have no matching indexed spec. -If an exchange arrives at an index with no matching indexed spec and no `allMessages()` fallback, +If an exchange arrives at an index with no matching indexed spec and no `validateAll()` fallback, the endpoint records a failure. For the `validate` operation, the failure is collected and reported when `assertIsSatisfied()` is called (similar to MockEndpoint). For the `receive` operation, the failure is thrown immediately as an exception. [source,java] ---- -citrus.message(0).body("{\"event\": \"start\"}"); -citrus.allMessages().body("{\"event\": \"progress\"}").header("type", "status"); +citrus.validate(0).body("{\"event\": \"start\"}"); +citrus.validateAll().body("{\"event\": \"progress\"}").header("type", "status"); -// Exchange 0 → validated against message(0) only (allMessages() is NOT applied) -// Exchange 1, 2, ... → validated against allMessages() (body + header) +// Exchange 0 → validated against validate(0) only (validateAll() is NOT applied) +// Exchange 1, 2, ... → validated against validateAll() (body + header) ---- === Receive operation (request/response simulation) @@ -395,7 +395,7 @@ such as JsonPath or XPath expressions. These are provided by the Citrus `Default [source,java] ---- CitrusEndpoint citrus = context.getEndpoint("citrus:validate?messageType=json", CitrusEndpoint.class); -citrus.allMessages().validate(new DefaultValidations().jsonPath() +citrus.validateAll().validate(new DefaultValidations().jsonPath() .expression("$.user.name", "John") .expression("$.user.age", "30")); ---- @@ -403,7 +403,7 @@ citrus.allMessages().validate(new DefaultValidations().jsonPath() [source,java] ---- CitrusEndpoint citrus = context.getEndpoint("citrus:validate?messageType=xml", CitrusEndpoint.class); -citrus.allMessages().validate(new DefaultValidations().xpath() +citrus.validateAll().validate(new DefaultValidations().xpath() .expression("/order/status", "completed") .expression("/order/total", "99.99")); ---- diff --git a/components/camel-test/camel-test-citrus/src/main/java/org/apache/camel/component/citrus/CitrusComponent.java b/components/camel-test/camel-test-citrus/src/main/java/org/apache/camel/component/citrus/CitrusComponent.java index 57e3d47e1075..f41e7e400a83 100644 --- a/components/camel-test/camel-test-citrus/src/main/java/org/apache/camel/component/citrus/CitrusComponent.java +++ b/components/camel-test/camel-test-citrus/src/main/java/org/apache/camel/component/citrus/CitrusComponent.java @@ -19,11 +19,11 @@ package org.apache.camel.component.citrus; import java.util.Map; import org.apache.camel.Endpoint; +import org.apache.camel.component.mock.MockComponent; import org.apache.camel.spi.annotations.Component; -import org.apache.camel.support.DefaultComponent; @Component("citrus") -public class CitrusComponent extends DefaultComponent { +public class CitrusComponent extends MockComponent { @Override protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception { diff --git a/components/camel-test/camel-test-citrus/src/main/java/org/apache/camel/component/citrus/CitrusEndpoint.java b/components/camel-test/camel-test-citrus/src/main/java/org/apache/camel/component/citrus/CitrusEndpoint.java index b5d5cf59224d..10ef50df6e32 100644 --- a/components/camel-test/camel-test-citrus/src/main/java/org/apache/camel/component/citrus/CitrusEndpoint.java +++ b/components/camel-test/camel-test-citrus/src/main/java/org/apache/camel/component/citrus/CitrusEndpoint.java @@ -16,39 +16,22 @@ */ package org.apache.camel.component.citrus; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.apache.camel.Category; -import org.apache.camel.Consumer; import org.apache.camel.Exchange; -import org.apache.camel.Processor; -import org.apache.camel.Producer; +import org.apache.camel.component.mock.MockEndpoint; import org.apache.camel.spi.Metadata; import org.apache.camel.spi.UriEndpoint; import org.apache.camel.spi.UriParam; import org.apache.camel.spi.UriPath; -import org.apache.camel.support.DefaultEndpoint; -import org.citrusframework.exceptions.ValidationException; +import org.citrusframework.CitrusContext; import org.citrusframework.message.MessagePayloadBuilder; import org.citrusframework.validation.context.ValidationContext; @UriEndpoint(firstVersion = "4.24.0", scheme = "citrus", title = "Citrus", syntax = "citrus:operation/name", producerOnly = true, remote = false, category = { Category.TESTING }) -public class CitrusEndpoint extends DefaultEndpoint { - - private final AtomicInteger exchangeIndex = new AtomicInteger(); - private final AtomicInteger receivedCount = new AtomicInteger(); - private final List<Exchange> receivedExchanges = new CopyOnWriteArrayList<>(); - private final List<Throwable> failures = new CopyOnWriteArrayList<>(); - - private volatile CountDownLatch latch; +public class CitrusEndpoint extends MockEndpoint { @UriPath(description = "The operation to perform. 'validate' validates exchanges without modifying them." + " 'receive' validates exchanges and returns a simulated response.", @@ -56,166 +39,92 @@ public class CitrusEndpoint extends DefaultEndpoint { @Metadata(required = true) private String operation; - @UriPath(description = "A unique name to identify this endpoint. Allows multiple Citrus endpoints" - + " with different validation expectations in the same route.") - private String name; - @UriParam(description = "The message type for validation. Determines which Citrus validation module is used." + " Supported values: json, xml, yaml, plaintext.") private String messageType; - private volatile MessageValidationSpec allMessagesSpec; - private final Map<Integer, MessageValidationSpec> indexedSpecs = new LinkedHashMap<>(); - private volatile int nextSpecIndex; - - private volatile MessageResponseSpec allMessagesResponseSpec; - private final Map<Integer, MessageResponseSpec> indexedResponseSpecs = new LinkedHashMap<>(); - private volatile int nextResponseSpecIndex; - - private volatile int expectedCount = -1; + private volatile CitrusContext citrusContext; + private volatile AtomicInteger nextSpecIndex = new AtomicInteger(0); public CitrusEndpoint(String endpointUri, CitrusComponent component) { super(endpointUri, component); } - @Override - public Consumer createConsumer(Processor processor) throws Exception { - throw new UnsupportedOperationException("You cannot consume from this endpoint"); - } + // --- Lifecycle --- @Override - public Producer createProducer() throws Exception { - return new CitrusProducer(this); + protected void doStart() throws Exception { + super.doStart(); + citrusContext = CitrusContext.create(); } - // --- Fluent validation specification API (for 'validate' operation) --- - - public MessageValidationSpec allMessages() { - if (allMessagesSpec == null) { - allMessagesSpec = new MessageValidationSpec(); + @Override + protected void doStop() throws Exception { + if (citrusContext != null) { + citrusContext.close(); + citrusContext = null; } - return allMessagesSpec; - } - - public MessageValidationSpec message() { - return message(nextSpecIndex++); - } - - public MessageValidationSpec message(int index) { - return indexedSpecs.computeIfAbsent(index, k -> new MessageValidationSpec()); + super.doStop(); } - // --- Gherkin-style API (for 'receive' operation) --- + // --- Fluent validation specification API --- - public WhenSpec when() { - return new WhenSpec(); + public MessageValidationSpec validateAll() { + MessageValidationSpec messageValidationSpec = new MessageValidationSpec(); + whenAnyExchangeReceived(createProcessor(messageValidationSpec)); + return messageValidationSpec; } - public ThenSpec then() { - return new ThenSpec(); + public MessageValidationSpec validate() { + return validate(nextSpecIndex.incrementAndGet()); } - public CitrusEndpoint expectedMessageCount(int count) { - this.expectedCount = count; - this.receivedCount.set(0); - this.receivedExchanges.clear(); - this.latch = new CountDownLatch(count); - return this; + public MessageValidationSpec validate(int index) { + MessageValidationSpec messageValidationSpec = new MessageValidationSpec(); + whenExchangeReceived(index, createProcessor(messageValidationSpec)); + return messageValidationSpec; } - // --- Spec resolution --- - - int nextExchangeIndex() { - return exchangeIndex.getAndIncrement(); + private CitrusValidationProcessor createProcessor(MessageValidationSpec validationSpec) { + return new CitrusValidationProcessor(citrusContext, messageType, validationSpec); } - MessageValidationSpec resolveValidationSpec(int index) { - MessageValidationSpec indexed = indexedSpecs.get(index); - if (indexed != null) { - return indexed; - } - if (allMessagesSpec != null) { - return allMessagesSpec; - } - if (!indexedSpecs.isEmpty()) { - throw new IllegalStateException( - "No validation specification for exchange at index " + index - + " on endpoint: " + getEndpointUri() - + ". Validation specs are configured for indexes " + indexedSpecs.keySet() - + ". Use allMessages() to provide a default" - + " or message(" + index + ") for this specific exchange."); - } - return null; + private CitrusValidationProcessor createProcessor( + MessageValidationSpec validationSpec, + MessageResponseSpec responseSpec) { + return new CitrusValidationProcessor(citrusContext, messageType, validationSpec, responseSpec); } - MessageResponseSpec resolveResponseSpec(int index) { - MessageResponseSpec indexed = indexedResponseSpecs.get(index); - if (indexed != null) { - return indexed; - } - return allMessagesResponseSpec; - } - - // --- Assertion API --- + // --- Gherkin-style API (for 'receive' operation) --- - public void assertIsSatisfied() throws InterruptedException { - assertIsSatisfied(10, TimeUnit.SECONDS); + public WhenSpec when() { + return new WhenSpec(); } - public void assertIsSatisfied(long timeout, TimeUnit unit) throws InterruptedException { - int effectiveExpected = expectedCount; - if (effectiveExpected < 0 && !indexedSpecs.isEmpty()) { - effectiveExpected = indexedSpecs.size(); - } - - if (latch != null && !latch.await(timeout, unit)) { - throw new ValidationException( - "Timed out waiting for %d messages on endpoint: %s" - .formatted(effectiveExpected, getEndpointUri())); - } - - if (effectiveExpected >= 0 && receivedCount.get() != effectiveExpected) { - throw new ValidationException( - "Expected %d messages but received %d on endpoint: %s" - .formatted(effectiveExpected, receivedCount.get(), getEndpointUri())); - } - - if (!failures.isEmpty()) { - ValidationException validationException = new ValidationException( - "Citrus validation failed on endpoint: %s. Has %d failure(s)." - .formatted(getEndpointUri(), failures.size())); - failures.forEach(validationException::addSuppressed); - throw validationException; - } + @Override + public void reset() { + super.reset(); + nextSpecIndex = new AtomicInteger(0); } - void onExchangeReceived(Exchange exchange) { - receivedCount.incrementAndGet(); - receivedExchanges.add(exchange); - if (latch != null) { - latch.countDown(); + @Override + protected void performAssertions(Exchange exchange, Exchange copy) throws Exception { + super.performAssertions(exchange, copy); + + // tryProcessing() in the superclass swallows processor exceptions and sets them on the + // exchange instead of adding them to the failures list. Capture them here so + // assertIsSatisfied() can report Citrus validation errors properly. + Exception e = exchange.getException(); + if (e != null && !getFailures().contains(e)) { + getFailures().add(e); + if ("validate".equals(operation)) { + // For validate-only mode, clear the exception so it does not propagate + // through the route — validation errors are collected, not thrown. + exchange.setException(null); + } } } - void addFailure(Throwable failure) { - failures.add(failure); - } - - public void reset() { - exchangeIndex.set(0); - receivedCount.set(0); - receivedExchanges.clear(); - failures.clear(); - allMessagesSpec = null; - indexedSpecs.clear(); - nextSpecIndex = 0; - allMessagesResponseSpec = null; - indexedResponseSpecs.clear(); - nextResponseSpecIndex = 0; - expectedCount = -1; - latch = null; - } - // --- Getters and setters --- public String getOperation() { @@ -226,14 +135,6 @@ public class CitrusEndpoint extends DefaultEndpoint { this.operation = operation; } - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - public String getMessageType() { return messageType; } @@ -242,82 +143,82 @@ public class CitrusEndpoint extends DefaultEndpoint { this.messageType = messageType; } - public int getReceivedCount() { - return receivedCount.get(); - } - - public List<Exchange> getReceivedExchanges() { - return new ArrayList<>(receivedExchanges); - } - // --- Gherkin-style inner classes --- public class WhenSpec { - public WhenMessageSelector message() { - return message(nextSpecIndex++); + public WhenMessageValidationSpec receive() { + return receive(nextSpecIndex.incrementAndGet()); } - public WhenMessageSelector message(int index) { - return new WhenMessageSelector(index, false); + public WhenMessageValidationSpec receive(int index) { + WhenMessageValidationSpec messageValidationSpec = new WhenMessageValidationSpec(index); + whenExchangeReceived(index, createProcessor(messageValidationSpec)); + return messageValidationSpec; } - public WhenMessageSelector allMessages() { - return new WhenMessageSelector(-1, true); + public WhenMessageValidationSpec receiveAll() { + WhenMessageValidationSpec messageValidationSpec = new WhenMessageValidationSpec(-1); + whenAnyExchangeReceived(createProcessor(messageValidationSpec)); + return messageValidationSpec; } } - public class WhenMessageSelector { + public class WhenMessageValidationSpec extends MessageValidationSpec { private final int index; - private final boolean all; - WhenMessageSelector(int index, boolean all) { + WhenMessageValidationSpec(int index) { this.index = index; - this.all = all; } - public MessageValidationSpec receive() { - if (all) { - return CitrusEndpoint.this.allMessages(); - } - return CitrusEndpoint.this.message(index); + @Override + public WhenMessageValidationSpec body(MessagePayloadBuilder bodyBuilder) { + super.body(bodyBuilder); + return this; } - } - public class ThenSpec { + @Override + public WhenMessageValidationSpec body(String body) { + super.body(body); + return this; + } - public ThenMessageSelector message() { - return message(nextResponseSpecIndex++); + @Override + public WhenMessageValidationSpec header(String headerName, Object value) { + super.header(headerName, value); + return this; } - public ThenMessageSelector message(int index) { - return new ThenMessageSelector(index, false); + @Override + public MessageValidationSpec validate(ValidationContext.Builder<?, ?>... validationContexts) { + super.validate(validationContexts); + return this; } - public ThenMessageSelector allMessages() { - return new ThenMessageSelector(-1, true); + public ThenSpec then() { + return new ThenSpec(index, this); } } - public class ThenMessageSelector { + public class ThenSpec { private final int index; - private final boolean all; + private final WhenMessageValidationSpec messageValidationSpec; - ThenMessageSelector(int index, boolean all) { + public ThenSpec(int index, WhenMessageValidationSpec messageValidationSpec) { this.index = index; - this.all = all; + this.messageValidationSpec = messageValidationSpec; } public MessageResponseSpec respond() { - if (all) { - if (allMessagesResponseSpec == null) { - allMessagesResponseSpec = new MessageResponseSpec(); - } - return allMessagesResponseSpec; + MessageResponseSpec messageResponseSpec = new MessageResponseSpec(); + if (index < 0) { + whenAnyExchangeReceived(createProcessor(messageValidationSpec, messageResponseSpec)); + } else { + whenExchangeReceived(index, createProcessor(messageValidationSpec, messageResponseSpec)); } - return indexedResponseSpecs.computeIfAbsent(index, k -> new MessageResponseSpec()); + return messageResponseSpec; } public MessageResponseSpec send() { @@ -327,87 +228,6 @@ public class CitrusEndpoint extends DefaultEndpoint { // --- Validation spec for expected messages --- - public static class MessageValidationSpec { - - private String body; - private MessagePayloadBuilder bodyBuilder; - private final Map<String, Object> headers = new LinkedHashMap<>(); - private final List<ValidationContext.Builder<?, ?>> validationContextBuilders = new ArrayList<>(); - - public MessageValidationSpec body(String body) { - this.body = body; - this.bodyBuilder = null; - return this; - } - - public MessageValidationSpec body(MessagePayloadBuilder bodyBuilder) { - this.bodyBuilder = bodyBuilder; - this.body = null; - return this; - } - - public MessageValidationSpec header(String headerName, Object value) { - this.headers.put(headerName, value); - return this; - } - - public MessageValidationSpec validate(ValidationContext.Builder<?, ?>... validationContexts) { - this.validationContextBuilders.addAll(List.of(validationContexts)); - return this; - } - - public String getBody() { - return body; - } - - public MessagePayloadBuilder getBodyBuilder() { - return bodyBuilder; - } - - public Map<String, Object> getHeaders() { - return headers; - } - - public List<ValidationContext.Builder<?, ?>> getValidationContextBuilders() { - return validationContextBuilders; - } - } - // --- Response spec for simulated responses --- - public static class MessageResponseSpec { - - private String body; - private MessagePayloadBuilder bodyBuilder; - private final Map<String, Object> headers = new LinkedHashMap<>(); - - public MessageResponseSpec body(String body) { - this.body = body; - this.bodyBuilder = null; - return this; - } - - public MessageResponseSpec body(MessagePayloadBuilder bodyBuilder) { - this.bodyBuilder = bodyBuilder; - this.body = null; - return this; - } - - public MessageResponseSpec header(String headerName, Object value) { - this.headers.put(headerName, value); - return this; - } - - public String getBody() { - return body; - } - - public MessagePayloadBuilder getBodyBuilder() { - return bodyBuilder; - } - - public Map<String, Object> getHeaders() { - return headers; - } - } } diff --git a/components/camel-test/camel-test-citrus/src/main/java/org/apache/camel/component/citrus/CitrusProducer.java b/components/camel-test/camel-test-citrus/src/main/java/org/apache/camel/component/citrus/CitrusValidationProcessor.java similarity index 72% rename from components/camel-test/camel-test-citrus/src/main/java/org/apache/camel/component/citrus/CitrusProducer.java rename to components/camel-test/camel-test-citrus/src/main/java/org/apache/camel/component/citrus/CitrusValidationProcessor.java index c565db25cba7..6b76f5d1966f 100644 --- a/components/camel-test/camel-test-citrus/src/main/java/org/apache/camel/component/citrus/CitrusProducer.java +++ b/components/camel-test/camel-test-citrus/src/main/java/org/apache/camel/component/citrus/CitrusValidationProcessor.java @@ -23,9 +23,7 @@ import java.util.Locale; import java.util.Map; import org.apache.camel.Exchange; -import org.apache.camel.component.citrus.CitrusEndpoint.MessageResponseSpec; -import org.apache.camel.component.citrus.CitrusEndpoint.MessageValidationSpec; -import org.apache.camel.support.DefaultProducer; +import org.apache.camel.Processor; import org.citrusframework.CitrusContext; import org.citrusframework.context.TestContext; import org.citrusframework.message.DefaultMessage; @@ -40,96 +38,55 @@ import org.citrusframework.validation.context.ValidationContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class CitrusProducer extends DefaultProducer { +class CitrusValidationProcessor implements Processor { - private static final Logger LOG = LoggerFactory.getLogger(CitrusProducer.class); + private static final Logger LOG = LoggerFactory.getLogger(CitrusValidationProcessor.class); - private volatile CitrusContext citrusContext; + private final CitrusContext citrusContext; + private final String messageType; + private final MessageValidationSpec spec; + private final MessageResponseSpec responseSpec; - public CitrusProducer(CitrusEndpoint endpoint) { - super(endpoint); + CitrusValidationProcessor(CitrusContext citrusContext, String messageType, + MessageValidationSpec validationSpec) { + this(citrusContext, messageType, validationSpec, null); } - @Override - public CitrusEndpoint getEndpoint() { - return (CitrusEndpoint) super.getEndpoint(); - } - - @Override - protected void doStart() throws Exception { - super.doStart(); - citrusContext = CitrusContext.create(); - } - - @Override - protected void doStop() throws Exception { - super.doStop(); - if (citrusContext != null) { - citrusContext.close(); - citrusContext = null; - } + CitrusValidationProcessor(CitrusContext citrusContext, String messageType, + MessageValidationSpec validationSpec, + MessageResponseSpec responseSpec) { + this.citrusContext = citrusContext; + this.messageType = messageType; + this.spec = validationSpec; + this.responseSpec = responseSpec; } @Override public void process(Exchange exchange) throws Exception { - CitrusEndpoint endpoint = getEndpoint(); - - int index = endpoint.nextExchangeIndex(); TestContext testContext = citrusContext.createTestContext(); propagateExchangeVariables(exchange, testContext); try { - MessageValidationSpec spec = endpoint.resolveValidationSpec(index); if (spec != null) { DefaultMessage receivedMessage = buildReceivedMessage(exchange); DefaultMessage controlMessage = buildControlMessage(spec, testContext); - String messageType = resolveMessageType(endpoint, spec); - controlMessage.setType(messageType); + String resolvedType = resolveMessageType(spec); + controlMessage.setType(resolvedType); List<ValidationContext> validationContexts = buildValidationContexts(spec); - - performValidation(receivedMessage, controlMessage, testContext, validationContexts, messageType); + performCitrusValidation(receivedMessage, controlMessage, testContext, validationContexts, resolvedType); } - if ("receive".equals(endpoint.getOperation())) { - MessageResponseSpec responseSpec = endpoint.resolveResponseSpec(index); - if (responseSpec != null) { - applyResponse(exchange, responseSpec, testContext); - } - } - } catch (Exception e) { - endpoint.addFailure(e); - if ("receive".equals(endpoint.getOperation())) { - throw e; + if (responseSpec != null) { + applyResponse(exchange, responseSpec, testContext); } } finally { writeBackExtractedVariables(exchange, testContext); - endpoint.onExchangeReceived(exchange); } } - private void propagateExchangeVariables(Exchange exchange, TestContext testContext) { - for (Map.Entry<String, Object> entry : exchange.getProperties().entrySet()) { - if (entry.getValue() != null) { - testContext.setVariable(entry.getKey(), entry.getValue()); - } - } - for (Map.Entry<String, Object> entry : exchange.getVariables().entrySet()) { - if (entry.getValue() != null) { - testContext.setVariable(entry.getKey(), entry.getValue()); - } - } - } - - private void writeBackExtractedVariables(Exchange exchange, TestContext testContext) { - for (Map.Entry<String, Object> entry : testContext.getVariables().entrySet()) { - String key = entry.getKey(); - if (!exchange.getProperties().containsKey(key) && !exchange.getVariables().containsKey(key)) { - exchange.setVariable(key, entry.getValue()); - } - } - } + // --- Citrus validation helpers --- private DefaultMessage buildReceivedMessage(Exchange exchange) { Object body = exchange.getIn().getBody(); @@ -138,34 +95,27 @@ public class CitrusProducer extends DefaultProducer { } private DefaultMessage buildControlMessage(MessageValidationSpec spec, TestContext testContext) { - DefaultMessage controlMessage; Object resolvedBody = resolveBody(spec.getBody(), spec.getBodyBuilder(), testContext); + DefaultMessage controlMessage = resolvedBody != null + ? new DefaultMessage(resolvedBody) + : new DefaultMessage(); - if (resolvedBody != null) { - controlMessage = new DefaultMessage(resolvedBody); - } else { - controlMessage = new DefaultMessage(); - } - - Map<String, Object> expectedHeaders = spec.getHeaders(); - for (Map.Entry<String, Object> entry : expectedHeaders.entrySet()) { + for (Map.Entry<String, Object> entry : spec.getHeaders().entrySet()) { Object value = entry.getValue(); if (value instanceof String s) { value = testContext.replaceDynamicContentInString(s); } controlMessage.setHeader(entry.getKey(), value); } - return controlMessage; } - private String resolveMessageType(CitrusEndpoint endpoint, MessageValidationSpec spec) { - String type = endpoint.getMessageType(); - if (type != null) { - if (MessageType.knows(type)) { - return MessageType.valueOf(type.toUpperCase(Locale.ROOT)).name(); + private String resolveMessageType(MessageValidationSpec spec) { + if (messageType != null) { + if (MessageType.knows(messageType)) { + return MessageType.valueOf(messageType.toUpperCase(Locale.ROOT)).name(); } else { - return type.toUpperCase(Locale.ROOT); + return messageType.toUpperCase(Locale.ROOT); } } @@ -179,46 +129,41 @@ public class CitrusProducer extends DefaultProducer { return MessageType.YAML.name(); } } - return MessageType.PLAINTEXT.name(); } private List<ValidationContext> buildValidationContexts(MessageValidationSpec spec) { List<ValidationContext> contexts = new ArrayList<>(); - contexts.add(new DefaultMessageValidationContext()); - if (!spec.getHeaders().isEmpty()) { contexts.add(new HeaderValidationContext()); } - for (ValidationContext.Builder<?, ?> builder : spec.getValidationContextBuilders()) { contexts.add(builder.build()); } - return contexts; } @SuppressWarnings({ "unchecked", "rawtypes" }) - private void performValidation( + private void performCitrusValidation( DefaultMessage receivedMessage, DefaultMessage controlMessage, TestContext testContext, List<ValidationContext> validationContexts, - String messageType) { + String resolvedType) { MessageValidatorRegistry registry = testContext.getMessageValidatorRegistry(); List<MessageValidator<? extends ValidationContext>> validators - = registry.findMessageValidators(messageType, receivedMessage); + = registry.findMessageValidators(resolvedType, receivedMessage); if (validators.isEmpty()) { throw new IllegalStateException( - "No Citrus message validator found for message type '" + messageType + "No Citrus message validator found for message type '" + resolvedType + "'. Make sure to add the required Citrus validation module " + "to the test classpath (e.g. citrus-validation-json, " + "citrus-validation-xml, citrus-validation-text)."); } LOG.debug("Validating exchange with {} Citrus validator(s) for message type '{}'", - validators.size(), messageType); + validators.size(), resolvedType); for (MessageValidator validator : validators) { validator.validateMessage(receivedMessage, controlMessage, testContext, validationContexts); @@ -230,7 +175,6 @@ public class CitrusProducer extends DefaultProducer { if (resolvedBody != null) { exchange.getIn().setBody(resolvedBody); } - Map<String, Object> resolvedHeaders = testContext.resolveDynamicValuesInMap(responseSpec.getHeaders()); resolvedHeaders.forEach((key, value) -> exchange.getIn().setHeader(key, value)); } @@ -244,4 +188,28 @@ public class CitrusProducer extends DefaultProducer { } return null; } + + // --- Variable propagation --- + + private void propagateExchangeVariables(Exchange exchange, TestContext testContext) { + for (Map.Entry<String, Object> entry : exchange.getProperties().entrySet()) { + if (entry.getValue() != null) { + testContext.setVariable(entry.getKey(), entry.getValue()); + } + } + for (Map.Entry<String, Object> entry : exchange.getVariables().entrySet()) { + if (entry.getValue() != null) { + testContext.setVariable(entry.getKey(), entry.getValue()); + } + } + } + + private void writeBackExtractedVariables(Exchange exchange, TestContext testContext) { + for (Map.Entry<String, Object> entry : testContext.getVariables().entrySet()) { + String key = entry.getKey(); + if (!exchange.getProperties().containsKey(key) && !exchange.getVariables().containsKey(key)) { + exchange.setVariable(key, entry.getValue()); + } + } + } } diff --git a/components/camel-test/camel-test-citrus/src/main/java/org/apache/camel/component/citrus/MessageResponseSpec.java b/components/camel-test/camel-test-citrus/src/main/java/org/apache/camel/component/citrus/MessageResponseSpec.java new file mode 100644 index 000000000000..2e1ce576bfc5 --- /dev/null +++ b/components/camel-test/camel-test-citrus/src/main/java/org/apache/camel/component/citrus/MessageResponseSpec.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.camel.component.citrus; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.citrusframework.message.MessagePayloadBuilder; + +public class MessageResponseSpec { + + private String body; + private MessagePayloadBuilder bodyBuilder; + private final Map<String, Object> headers = new LinkedHashMap<>(); + + public MessageResponseSpec body(String body) { + this.body = body; + this.bodyBuilder = null; + return this; + } + + public MessageResponseSpec body(MessagePayloadBuilder bodyBuilder) { + this.bodyBuilder = bodyBuilder; + this.body = null; + return this; + } + + public MessageResponseSpec header(String headerName, Object value) { + this.headers.put(headerName, value); + return this; + } + + public String getBody() { + return body; + } + + public MessagePayloadBuilder getBodyBuilder() { + return bodyBuilder; + } + + public Map<String, Object> getHeaders() { + return headers; + } +} diff --git a/components/camel-test/camel-test-citrus/src/main/java/org/apache/camel/component/citrus/MessageValidationSpec.java b/components/camel-test/camel-test-citrus/src/main/java/org/apache/camel/component/citrus/MessageValidationSpec.java new file mode 100644 index 000000000000..5e276576c05e --- /dev/null +++ b/components/camel-test/camel-test-citrus/src/main/java/org/apache/camel/component/citrus/MessageValidationSpec.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.camel.component.citrus; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.citrusframework.message.MessagePayloadBuilder; +import org.citrusframework.validation.context.ValidationContext; + +public class MessageValidationSpec { + + private String body; + private MessagePayloadBuilder bodyBuilder; + private final Map<String, Object> headers = new LinkedHashMap<>(); + private final List<ValidationContext.Builder<?, ?>> validationContextBuilders = new ArrayList<>(); + + public MessageValidationSpec body(String body) { + this.body = body; + this.bodyBuilder = null; + return this; + } + + public MessageValidationSpec body(MessagePayloadBuilder bodyBuilder) { + this.bodyBuilder = bodyBuilder; + this.body = null; + return this; + } + + public MessageValidationSpec header(String headerName, Object value) { + this.headers.put(headerName, value); + return this; + } + + public MessageValidationSpec validate(ValidationContext.Builder<?, ?>... validationContexts) { + this.validationContextBuilders.addAll(List.of(validationContexts)); + return this; + } + + public String getBody() { + return body; + } + + public MessagePayloadBuilder getBodyBuilder() { + return bodyBuilder; + } + + public Map<String, Object> getHeaders() { + return headers; + } + + public List<ValidationContext.Builder<?, ?>> getValidationContextBuilders() { + return validationContextBuilders; + } +} diff --git a/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusMultipleEndpointsTest.java b/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusMultipleEndpointsTest.java index dbac357254af..8e2524b52248 100644 --- a/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusMultipleEndpointsTest.java +++ b/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusMultipleEndpointsTest.java @@ -29,7 +29,7 @@ class CitrusMultipleEndpointsTest extends CamelTestSupport { void shouldValidateMultipleEndpointsInRoute() throws Exception { CitrusEndpoint orderCheck = context.getEndpoint("citrus:validate/orderCheck", CitrusEndpoint.class); - orderCheck.allMessages().body(""" + orderCheck.validateAll().body(""" { "orderId": "${orderId}", "status": "created" @@ -39,7 +39,7 @@ class CitrusMultipleEndpointsTest extends CamelTestSupport { CitrusEndpoint responseCheck = context.getEndpoint("citrus:validate/responseCheck", CitrusEndpoint.class); - responseCheck.allMessages().body(""" + responseCheck.validateAll().body(""" { "orderId": "${orderId}", "status": "processed" @@ -60,8 +60,8 @@ class CitrusMultipleEndpointsTest extends CamelTestSupport { orderCheck.assertIsSatisfied(); responseCheck.assertIsSatisfied(); - assertThat(orderCheck.getReceivedCount()).isEqualTo(1); - assertThat(responseCheck.getReceivedCount()).isEqualTo(1); + assertThat(orderCheck.getReceivedCounter()).isEqualTo(1); + assertThat(responseCheck.getReceivedCounter()).isEqualTo(1); } @Override diff --git a/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusReceiveTest.java b/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusReceiveTest.java index 3c0e9a3b2f68..a89579f7d841 100644 --- a/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusReceiveTest.java +++ b/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusReceiveTest.java @@ -30,9 +30,9 @@ class CitrusReceiveTest extends CamelTestSupport { void shouldValidateAndRespond() throws Exception { CitrusEndpoint mock = context.getEndpoint("citrus:receive/service?messageType=json", CitrusEndpoint.class); - mock.when().allMessages().receive() - .body("{\"action\": \"greet\"}"); - mock.then().allMessages().respond() + mock.when().receiveAll() + .body("{\"action\": \"greet\"}") + .then().respond() .body("{\"greeting\": \"Hello!\"}"); mock.expectedMessageCount(1); @@ -48,9 +48,9 @@ class CitrusReceiveTest extends CamelTestSupport { void shouldRespondWithHeaders() throws Exception { CitrusEndpoint mock = context.getEndpoint("citrus:receive/service?messageType=json", CitrusEndpoint.class); - mock.when().allMessages().receive() - .body("{\"action\": \"greet\"}"); - mock.then().allMessages().respond() + mock.when().receiveAll() + .body("{\"action\": \"greet\"}") + .then().respond() .body("{\"greeting\": \"Hello!\"}") .header("Content-Type", "application/json") .header("X-Status", "ok"); @@ -70,9 +70,9 @@ class CitrusReceiveTest extends CamelTestSupport { void shouldRespondWithCitrusVariables() throws Exception { CitrusEndpoint mock = context.getEndpoint("citrus:receive/service?messageType=json", CitrusEndpoint.class); - mock.when().allMessages().receive() - .body("{\"action\": \"greet\", \"name\": \"@ignore@\"}"); - mock.then().allMessages().respond() + mock.when().receiveAll() + .body("{\"action\": \"greet\", \"name\": \"@ignore@\"}") + .then().respond() .body("{\"greeting\": \"Hello ${userName}!\"}"); mock.expectedMessageCount(1); @@ -89,9 +89,9 @@ class CitrusReceiveTest extends CamelTestSupport { void shouldRespondWithVariablesInHeaders() throws Exception { CitrusEndpoint mock = context.getEndpoint("citrus:receive/service?messageType=json", CitrusEndpoint.class); - mock.when().allMessages().receive() - .body("{\"action\": \"check\"}"); - mock.then().allMessages().respond() + mock.when().receiveAll() + .body("{\"action\": \"check\"}") + .then().respond() .body("{\"result\": \"ok\"}") .header("X-Correlation-Id", "${correlationId}"); mock.expectedMessageCount(1); @@ -110,11 +110,11 @@ class CitrusReceiveTest extends CamelTestSupport { void shouldHandleSequentialReceiveAndRespond() throws Exception { CitrusEndpoint mock = context.getEndpoint("citrus:receive/service?messageType=json", CitrusEndpoint.class); - mock.when().message(0).receive().body("{\"action\": \"create\"}"); - mock.then().message(0).respond().body("{\"id\": \"1\", \"status\": \"created\"}"); + mock.when().receive(1).body("{\"action\": \"create\"}") + .then().respond().body("{\"id\": \"1\", \"status\": \"created\"}"); - mock.when().message(1).receive().body("{\"action\": \"update\"}"); - mock.then().message(1).respond().body("{\"id\": \"1\", \"status\": \"updated\"}"); + mock.when().receive(2).body("{\"action\": \"update\"}") + .then().respond().body("{\"id\": \"1\", \"status\": \"updated\"}"); mock.expectedMessageCount(2); @@ -135,11 +135,11 @@ class CitrusReceiveTest extends CamelTestSupport { void shouldFallBackToAllMessagesResponse() throws Exception { CitrusEndpoint mock = context.getEndpoint("citrus:receive/service?messageType=json", CitrusEndpoint.class); - mock.when().message(0).receive().body("{\"action\": \"first\"}"); - mock.then().message(0).respond().body("{\"result\": \"special\"}"); + mock.when().receive(1).body("{\"action\": \"first\"}") + .then().respond().body("{\"result\": \"special\"}"); - mock.when().allMessages().receive().body("{\"action\": \"@ignore@\"}"); - mock.then().allMessages().respond().body("{\"result\": \"default\"}"); + mock.when().receiveAll().body("{\"action\": \"@ignore@\"}") + .then().respond().body("{\"result\": \"default\"}"); mock.expectedMessageCount(3); @@ -163,8 +163,8 @@ class CitrusReceiveTest extends CamelTestSupport { void shouldUseSendAliasForRespond() throws Exception { CitrusEndpoint mock = context.getEndpoint("citrus:receive/service?messageType=json", CitrusEndpoint.class); - mock.when().allMessages().receive().body("{\"ping\": true}"); - mock.then().allMessages().send().body("{\"pong\": true}"); + mock.when().receive().body("{\"ping\": true}") + .then().send().body("{\"pong\": true}"); mock.expectedMessageCount(1); Exchange result = template.send("direct:start", exchange -> { @@ -178,8 +178,7 @@ class CitrusReceiveTest extends CamelTestSupport { @Test void shouldRespondWithoutValidation() throws Exception { CitrusEndpoint mock = context.getEndpoint("citrus:receive/responder", CitrusEndpoint.class); - - mock.then().allMessages().respond() + mock.when().receive().then().respond() .body("OK") .header("status", "200"); mock.expectedMessageCount(1); @@ -197,9 +196,9 @@ class CitrusReceiveTest extends CamelTestSupport { void shouldRespondWithPayloadBuilder() throws Exception { CitrusEndpoint mock = context.getEndpoint("citrus:receive/service?messageType=json", CitrusEndpoint.class); - mock.when().allMessages().receive() - .body("{\"action\": \"greet\"}"); - mock.then().allMessages().respond() + mock.when().receiveAll() + .body("{\"action\": \"greet\"}") + .then().respond() .body(testContext -> "{\"greeting\": \"Hello from builder!\"}"); mock.expectedMessageCount(1); @@ -215,9 +214,9 @@ class CitrusReceiveTest extends CamelTestSupport { void shouldFailOnValidationMismatch() { CitrusEndpoint mock = context.getEndpoint("citrus:receive/service?messageType=json", CitrusEndpoint.class); - mock.when().allMessages().receive() - .body("{\"action\": \"greet\"}"); - mock.then().allMessages().respond() + mock.when().receiveAll() + .body("{\"action\": \"greet\"}") + .then().respond() .body("{\"greeting\": \"Hello!\"}"); Exchange result = template.send("direct:start", exchange -> { diff --git a/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusSequentialValidationTest.java b/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusSequentialValidationTest.java index 1ce218d5c3c1..09f98f7e8ae9 100644 --- a/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusSequentialValidationTest.java +++ b/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusSequentialValidationTest.java @@ -31,13 +31,13 @@ class CitrusSequentialValidationTest extends CamelTestSupport { void shouldValidateSequentialExchanges() throws Exception { CitrusEndpoint citrus = context.getEndpoint("citrus:validate/test?messageType=json", CitrusEndpoint.class); - citrus.message().body(""" + citrus.validate().body(""" {"event": "created"} """); - citrus.message().body(""" + citrus.validate().body(""" {"event": "updated"} """); - citrus.message().body(""" + citrus.validate().body(""" {"event": "deleted"} """); citrus.expectedMessageCount(3); @@ -47,17 +47,17 @@ class CitrusSequentialValidationTest extends CamelTestSupport { template.sendBody("direct:start", "{\"event\": \"deleted\"}"); citrus.assertIsSatisfied(); - assertThat(citrus.getReceivedCount()).isEqualTo(3); + assertThat(citrus.getReceivedCounter()).isEqualTo(3); } @Test void shouldValidateSequentialExchangesWithHeaders() throws Exception { CitrusEndpoint citrus = context.getEndpoint("citrus:validate/test?messageType=json", CitrusEndpoint.class); - citrus.message() + citrus.validate() .body("{\"status\": \"pending\"}") .header("action", "create"); - citrus.message() + citrus.validate() .body("{\"status\": \"done\"}") .header("action", "complete"); citrus.expectedMessageCount(2); @@ -69,28 +69,25 @@ class CitrusSequentialValidationTest extends CamelTestSupport { } @Test - void shouldFailWhenExchangeExceedsIndexedSpecs() { + void shouldPassThroughExchangeWithoutMatchingSpec() throws Exception { CitrusEndpoint citrus = context.getEndpoint("citrus:validate/test?messageType=json", CitrusEndpoint.class); - citrus.message(0).body("{\"event\": \"first\"}"); + citrus.validate(1).body("{\"event\": \"first\"}"); citrus.expectedMessageCount(2); template.sendBody("direct:start", "{\"event\": \"first\"}"); template.sendBody("direct:start", "{\"event\": \"second\"}"); - assertThatThrownBy(citrus::assertIsSatisfied) - .isInstanceOf(ValidationException.class) - .satisfies(e -> assertThat(e.getSuppressed()[0]) - .isInstanceOf(IllegalStateException.class) - .hasMessageContaining("No validation specification for exchange at index 1")); + citrus.assertIsSatisfied(); + assertThat(citrus.getReceivedCounter()).isEqualTo(2); } @Test void shouldFallBackToAllMessagesSpec() throws Exception { CitrusEndpoint citrus = context.getEndpoint("citrus:validate/test?messageType=json", CitrusEndpoint.class); - citrus.message(0).body("{\"event\": \"first\"}"); - citrus.allMessages().body("{\"event\": \"default\"}"); + citrus.validate(1).body("{\"event\": \"first\"}"); + citrus.validateAll().body("{\"event\": \"default\"}"); citrus.expectedMessageCount(3); template.sendBody("direct:start", "{\"event\": \"first\"}"); @@ -98,14 +95,14 @@ class CitrusSequentialValidationTest extends CamelTestSupport { template.sendBody("direct:start", "{\"event\": \"default\"}"); citrus.assertIsSatisfied(); - assertThat(citrus.getReceivedCount()).isEqualTo(3); + assertThat(citrus.getReceivedCounter()).isEqualTo(3); } @Test void shouldValidateAllMessagesWithSameSpec() throws Exception { CitrusEndpoint citrus = context.getEndpoint("citrus:validate/test?messageType=json", CitrusEndpoint.class); - citrus.allMessages() + citrus.validateAll() .body("{\"status\": \"ok\"}") .header("type", "health"); citrus.expectedMessageCount(3); @@ -121,15 +118,16 @@ class CitrusSequentialValidationTest extends CamelTestSupport { void shouldFailSequentialValidationOnMismatch() { CitrusEndpoint citrus = context.getEndpoint("citrus:validate/test?messageType=json", CitrusEndpoint.class); - citrus.message(0).body("{\"event\": \"created\"}"); - citrus.message(1).body("{\"event\": \"updated\"}"); + citrus.validate(1).body("{\"event\": \"created\"}"); + citrus.validate(2).body("{\"event\": \"updated\"}"); citrus.expectedMessageCount(2); template.sendBody("direct:start", "{\"event\": \"created\"}"); template.sendBody("direct:start", "{\"event\": \"wrong\"}"); assertThatThrownBy(citrus::assertIsSatisfied) - .isInstanceOf(ValidationException.class); + .isInstanceOf(AssertionError.class) + .hasCauseInstanceOf(ValidationException.class); } @Override diff --git a/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusValidateJsonTest.java b/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusValidateJsonTest.java index 30241c3e5e81..c9e6543c694a 100644 --- a/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusValidateJsonTest.java +++ b/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusValidateJsonTest.java @@ -31,7 +31,7 @@ class CitrusValidateJsonTest extends CamelTestSupport { @Test void shouldValidateJsonBody() throws Exception { CitrusEndpoint citrus = context.getEndpoint("citrus:validate?messageType=json", CitrusEndpoint.class); - citrus.allMessages().body(""" + citrus.validateAll().body(""" { "greeting": "Hello World!" } @@ -45,13 +45,13 @@ class CitrusValidateJsonTest extends CamelTestSupport { """); citrus.assertIsSatisfied(); - assertThat(citrus.getReceivedCount()).isEqualTo(1); + assertThat(citrus.getReceivedCounter()).isEqualTo(1); } @Test void shouldFailOnJsonBodyMismatch() { CitrusEndpoint citrus = context.getEndpoint("citrus:validate?messageType=json", CitrusEndpoint.class); - citrus.allMessages().body(""" + citrus.validateAll().body(""" { "greeting": "Hello World!" } @@ -65,13 +65,14 @@ class CitrusValidateJsonTest extends CamelTestSupport { """); assertThatThrownBy(citrus::assertIsSatisfied) - .isInstanceOf(ValidationException.class); + .isInstanceOf(AssertionError.class) + .hasCauseInstanceOf(ValidationException.class); } @Test void shouldValidateJsonPathExpressions() throws Exception { CitrusEndpoint citrus = context.getEndpoint("citrus:validate?messageType=json", CitrusEndpoint.class); - citrus.allMessages().validate(new DefaultValidations().jsonPath() + citrus.validateAll().validate(new DefaultValidations().jsonPath() .expression("$.user.name", "John") .expression("$.user.age", "30")); citrus.expectedMessageCount(1); @@ -91,7 +92,7 @@ class CitrusValidateJsonTest extends CamelTestSupport { @Test void shouldAutoDetectJsonMessageType() throws Exception { CitrusEndpoint citrus = context.getEndpoint("citrus:validate", CitrusEndpoint.class); - citrus.allMessages().body(""" + citrus.validateAll().body(""" { "greeting": "Hello World!" } @@ -110,7 +111,7 @@ class CitrusValidateJsonTest extends CamelTestSupport { @Test void shouldValidateHeaders() throws Exception { CitrusEndpoint citrus = context.getEndpoint("citrus:validate?messageType=json", CitrusEndpoint.class); - citrus.allMessages() + citrus.validateAll() .body(""" { "result": "ok" @@ -128,7 +129,7 @@ class CitrusValidateJsonTest extends CamelTestSupport { @Test void shouldPropagateExchangePropertiesAsVariables() throws Exception { CitrusEndpoint citrus = context.getEndpoint("citrus:validate?messageType=json", CitrusEndpoint.class); - citrus.allMessages().body(""" + citrus.validateAll().body(""" { "message": "Hello ${userName}!" } @@ -150,7 +151,7 @@ class CitrusValidateJsonTest extends CamelTestSupport { @Test void shouldValidateWithPayloadBuilder() throws Exception { CitrusEndpoint citrus = context.getEndpoint("citrus:validate?messageType=json", CitrusEndpoint.class); - citrus.allMessages().body(testContext -> """ + citrus.validateAll().body(testContext -> """ { "greeting": "Hello World!" } diff --git a/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusValidatePlainTextTest.java b/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusValidatePlainTextTest.java index f030a5061a85..04818d849ccf 100644 --- a/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusValidatePlainTextTest.java +++ b/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusValidatePlainTextTest.java @@ -30,31 +30,32 @@ class CitrusValidatePlainTextTest extends CamelTestSupport { @Test void shouldValidatePlainTextBody() throws Exception { CitrusEndpoint citrus = context.getEndpoint("citrus:validate?messageType=plaintext", CitrusEndpoint.class); - citrus.allMessages().body("Hello Camel!"); + citrus.validateAll().body("Hello Camel!"); citrus.expectedMessageCount(1); template.sendBody("direct:start", "Hello Camel!"); citrus.assertIsSatisfied(); - assertThat(citrus.getReceivedCount()).isEqualTo(1); + assertThat(citrus.getReceivedCounter()).isEqualTo(1); } @Test void shouldFailOnPlainTextMismatch() { CitrusEndpoint citrus = context.getEndpoint("citrus:validate?messageType=plaintext", CitrusEndpoint.class); - citrus.allMessages().body("Hello Camel!"); + citrus.validateAll().body("Hello Camel!"); citrus.expectedMessageCount(1); template.sendBody("direct:start", "Goodbye Camel!"); assertThatThrownBy(citrus::assertIsSatisfied) - .isInstanceOf(ValidationException.class); + .isInstanceOf(AssertionError.class) + .hasCauseInstanceOf(ValidationException.class); } @Test void shouldValidateDefaultMessageType() throws Exception { CitrusEndpoint citrus = context.getEndpoint("citrus:validate", CitrusEndpoint.class); - citrus.allMessages().body("Default text"); + citrus.validateAll().body("Default text"); citrus.expectedMessageCount(1); template.sendBody("direct:default", "Default text"); diff --git a/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusValidateXmlTest.java b/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusValidateXmlTest.java index 4b734fc3b65a..bb82ea3bb7f9 100644 --- a/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusValidateXmlTest.java +++ b/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusValidateXmlTest.java @@ -29,7 +29,7 @@ class CitrusValidateXmlTest extends CamelTestSupport { @Test void shouldValidateXmlBody() throws Exception { CitrusEndpoint citrus = context.getEndpoint("citrus:validate?messageType=xml", CitrusEndpoint.class); - citrus.allMessages().body(""" + citrus.validateAll().body(""" <greeting> <text>Hello World!</text> </greeting> @@ -43,13 +43,13 @@ class CitrusValidateXmlTest extends CamelTestSupport { """); citrus.assertIsSatisfied(); - assertThat(citrus.getReceivedCount()).isEqualTo(1); + assertThat(citrus.getReceivedCounter()).isEqualTo(1); } @Test void shouldValidateXpathExpressions() throws Exception { CitrusEndpoint citrus = context.getEndpoint("citrus:validate?messageType=xml", CitrusEndpoint.class); - citrus.allMessages().validate(new DefaultValidations().xpath() + citrus.validateAll().validate(new DefaultValidations().xpath() .expression("/greeting/text", "Hello World!") .expression("/greeting/author", "Camel")); citrus.expectedMessageCount(1); @@ -67,7 +67,7 @@ class CitrusValidateXmlTest extends CamelTestSupport { @Test void shouldAutoDetectXmlMessageType() throws Exception { CitrusEndpoint citrus = context.getEndpoint("citrus:validate", CitrusEndpoint.class); - citrus.allMessages().body(""" + citrus.validateAll().body(""" <greeting> <text>Hello World!</text> </greeting> diff --git a/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusValidateYamlTest.java b/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusValidateYamlTest.java index 58680624f7ce..3520b85d3eb8 100644 --- a/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusValidateYamlTest.java +++ b/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusValidateYamlTest.java @@ -30,7 +30,7 @@ class CitrusValidateYamlTest extends CamelTestSupport { @Test void shouldValidateYamlBody() throws Exception { CitrusEndpoint citrus = context.getEndpoint("citrus:validate?messageType=yaml", CitrusEndpoint.class); - citrus.allMessages().body(""" + citrus.validateAll().body(""" greeting: Hello World! """); citrus.expectedMessageCount(1); @@ -40,13 +40,13 @@ class CitrusValidateYamlTest extends CamelTestSupport { """); citrus.assertIsSatisfied(); - assertThat(citrus.getReceivedCount()).isEqualTo(1); + assertThat(citrus.getReceivedCounter()).isEqualTo(1); } @Test void shouldAutoDetectYamlMessageType() throws Exception { CitrusEndpoint citrus = context.getEndpoint("citrus:validate", CitrusEndpoint.class); - citrus.allMessages().body(""" + citrus.validateAll().body(""" greeting: Hello World! items: - name: first @@ -67,7 +67,7 @@ class CitrusValidateYamlTest extends CamelTestSupport { @Test void shouldPropagateExchangePropertiesAsVariables() throws Exception { CitrusEndpoint citrus = context.getEndpoint("citrus:validate?messageType=yaml", CitrusEndpoint.class); - citrus.allMessages().body(""" + citrus.validateAll().body(""" greeting: Hello ${userName}! """); citrus.expectedMessageCount(1); @@ -85,7 +85,7 @@ class CitrusValidateYamlTest extends CamelTestSupport { @Test void shouldFailOnYamlBodyMismatch() { CitrusEndpoint citrus = context.getEndpoint("citrus:validate?messageType=yaml", CitrusEndpoint.class); - citrus.allMessages().body(""" + citrus.validateAll().body(""" greeting: Hello World! """); citrus.expectedMessageCount(1); @@ -95,7 +95,8 @@ class CitrusValidateYamlTest extends CamelTestSupport { """); assertThatThrownBy(citrus::assertIsSatisfied) - .isInstanceOf(ValidationException.class); + .isInstanceOf(AssertionError.class) + .hasCauseInstanceOf(ValidationException.class); } @Override diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java index bef527f62ada..9d1bf697ccc6 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java @@ -3390,9 +3390,8 @@ public class StaticEndpointBuilders { * response. * There are 2 enums and the value can be one of: validate, receive * - * Path parameter: name - * A unique name to identify this endpoint. Allows multiple Citrus endpoints - * with different validation expectations in the same route. + * Path parameter: name (required) + * Name of mock endpoint * * @param path operation/name * @return the dsl builder @@ -3416,9 +3415,8 @@ public class StaticEndpointBuilders { * response. * There are 2 enums and the value can be one of: validate, receive * - * Path parameter: name - * A unique name to identify this endpoint. Allows multiple Citrus endpoints - * with different validation expectations in the same route. + * Path parameter: name (required) + * Name of mock endpoint * * @param componentName to use a custom component name for the endpoint * instead of the default name diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/CitrusEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/CitrusEndpointBuilderFactory.java index 8deac9e3c7a7..5c1bcbe5036b 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/CitrusEndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/CitrusEndpointBuilderFactory.java @@ -44,6 +44,92 @@ public interface CitrusEndpointBuilderFactory { return (AdvancedCitrusEndpointBuilder) this; } + /** + * Sets a grace period after which the mock will re-assert to ensure the + * preliminary assertion is still valid. This is used, for example, to + * assert that exactly a number of messages arrive. For example, if the + * expected count was set to 5, then the assertion is satisfied when + * five or more messages arrive. To ensure that exactly 5 messages + * arrive, then you would need to wait a little period to ensure no + * further message arrives. This is what you can use this method for. By + * default, this period is disabled. + * + * The option is a: <code>long</code> type. + * + * Group: producer + * + * @param assertPeriod the value to set + * @return the dsl builder + */ + default CitrusEndpointBuilder assertPeriod(long assertPeriod) { + doSetProperty("assertPeriod", assertPeriod); + return this; + } + /** + * Sets a grace period after which the mock will re-assert to ensure the + * preliminary assertion is still valid. This is used, for example, to + * assert that exactly a number of messages arrive. For example, if the + * expected count was set to 5, then the assertion is satisfied when + * five or more messages arrive. To ensure that exactly 5 messages + * arrive, then you would need to wait a little period to ensure no + * further message arrives. This is what you can use this method for. By + * default, this period is disabled. + * + * The option will be converted to a <code>long</code> type. + * + * Group: producer + * + * @param assertPeriod the value to set + * @return the dsl builder + */ + default CitrusEndpointBuilder assertPeriod(String assertPeriod) { + doSetProperty("assertPeriod", assertPeriod); + return this; + } + /** + * Specifies the expected number of message exchanges that should be + * received by this mock. Beware: If you want to expect that 0 messages, + * then take extra care, as 0 matches when the tests starts, so you need + * to set a assert period time to let the test run for a while to make + * sure there are still no messages arrived; for that use the + * assertPeriod option. If you want to assert that exactly nth message + * arrives to this mock, then see also the assertPeriod option for + * further details. + * + * The option is a: <code>int</code> type. + * + * Default: -1 + * Group: producer + * + * @param expectedCount the value to set + * @return the dsl builder + */ + default CitrusEndpointBuilder expectedCount(int expectedCount) { + doSetProperty("expectedCount", expectedCount); + return this; + } + /** + * Specifies the expected number of message exchanges that should be + * received by this mock. Beware: If you want to expect that 0 messages, + * then take extra care, as 0 matches when the tests starts, so you need + * to set a assert period time to let the test run for a while to make + * sure there are still no messages arrived; for that use the + * assertPeriod option. If you want to assert that exactly nth message + * arrives to this mock, then see also the assertPeriod option for + * further details. + * + * The option will be converted to a <code>int</code> type. + * + * Default: -1 + * Group: producer + * + * @param expectedCount the value to set + * @return the dsl builder + */ + default CitrusEndpointBuilder expectedCount(String expectedCount) { + doSetProperty("expectedCount", expectedCount); + return this; + } /** * The message type for validation. Determines which Citrus validation * module is used. Supported values: json, xml, yaml, plaintext. @@ -71,6 +157,76 @@ public interface CitrusEndpointBuilderFactory { return (CitrusEndpointBuilder) this; } + /** + * Sets whether to make a deep copy of the incoming Exchange when + * received at this mock endpoint. + * + * The option is a: <code>boolean</code> type. + * + * Default: true + * Group: producer (advanced) + * + * @param copyOnExchange the value to set + * @return the dsl builder + */ + default AdvancedCitrusEndpointBuilder copyOnExchange(boolean copyOnExchange) { + doSetProperty("copyOnExchange", copyOnExchange); + return this; + } + /** + * Sets whether to make a deep copy of the incoming Exchange when + * received at this mock endpoint. + * + * The option will be converted to a <code>boolean</code> type. + * + * Default: true + * Group: producer (advanced) + * + * @param copyOnExchange the value to set + * @return the dsl builder + */ + default AdvancedCitrusEndpointBuilder copyOnExchange(String copyOnExchange) { + doSetProperty("copyOnExchange", copyOnExchange); + return this; + } + /** + * Sets whether assertIsSatisfied() should fail fast at the first + * detected failed expectation while it may otherwise wait for all + * expected messages to arrive before performing expectations + * verifications. Is by default true. Set to false to use behavior as in + * Camel 2.x. + * + * The option is a: <code>boolean</code> type. + * + * Default: true + * Group: producer (advanced) + * + * @param failFast the value to set + * @return the dsl builder + */ + default AdvancedCitrusEndpointBuilder failFast(boolean failFast) { + doSetProperty("failFast", failFast); + return this; + } + /** + * Sets whether assertIsSatisfied() should fail fast at the first + * detected failed expectation while it may otherwise wait for all + * expected messages to arrive before performing expectations + * verifications. Is by default true. Set to false to use behavior as in + * Camel 2.x. + * + * The option will be converted to a <code>boolean</code> type. + * + * Default: true + * Group: producer (advanced) + * + * @param failFast the value to set + * @return the dsl builder + */ + default AdvancedCitrusEndpointBuilder failFast(String failFast) { + doSetProperty("failFast", failFast); + return this; + } /** * Whether the producer should be started lazy (on the first message). * By starting lazy you can use this to allow CamelContext and routes to @@ -117,6 +273,306 @@ public interface CitrusEndpointBuilderFactory { doSetProperty("lazyStartProducer", lazyStartProducer); return this; } + /** + * To turn on logging when the mock receives an incoming message. This + * will log only one time at INFO level for the incoming message. For + * more detailed logging, then set the logger to DEBUG level for the + * org.apache.camel.component.mock.MockEndpoint class. + * + * The option is a: <code>boolean</code> type. + * + * Default: false + * Group: producer (advanced) + * + * @param log the value to set + * @return the dsl builder + */ + default AdvancedCitrusEndpointBuilder log(boolean log) { + doSetProperty("log", log); + return this; + } + /** + * To turn on logging when the mock receives an incoming message. This + * will log only one time at INFO level for the incoming message. For + * more detailed logging, then set the logger to DEBUG level for the + * org.apache.camel.component.mock.MockEndpoint class. + * + * The option will be converted to a <code>boolean</code> type. + * + * Default: false + * Group: producer (advanced) + * + * @param log the value to set + * @return the dsl builder + */ + default AdvancedCitrusEndpointBuilder log(String log) { + doSetProperty("log", log); + return this; + } + /** + * A number that is used to turn on throughput logging based on groups + * of the size. + * + * The option is a: <code>int</code> type. + * + * Group: producer (advanced) + * + * @param reportGroup the value to set + * @return the dsl builder + */ + default AdvancedCitrusEndpointBuilder reportGroup(int reportGroup) { + doSetProperty("reportGroup", reportGroup); + return this; + } + /** + * A number that is used to turn on throughput logging based on groups + * of the size. + * + * The option will be converted to a <code>int</code> type. + * + * Group: producer (advanced) + * + * @param reportGroup the value to set + * @return the dsl builder + */ + default AdvancedCitrusEndpointBuilder reportGroup(String reportGroup) { + doSetProperty("reportGroup", reportGroup); + return this; + } + /** + * Sets the minimum expected amount of time the assertIsSatisfied() will + * wait on a latch until it is satisfied. + * + * The option is a: <code>long</code> type. + * + * Group: producer (advanced) + * + * @param resultMinimumWaitTime the value to set + * @return the dsl builder + */ + default AdvancedCitrusEndpointBuilder resultMinimumWaitTime(long resultMinimumWaitTime) { + doSetProperty("resultMinimumWaitTime", resultMinimumWaitTime); + return this; + } + /** + * Sets the minimum expected amount of time the assertIsSatisfied() will + * wait on a latch until it is satisfied. + * + * The option will be converted to a <code>long</code> type. + * + * Group: producer (advanced) + * + * @param resultMinimumWaitTime the value to set + * @return the dsl builder + */ + default AdvancedCitrusEndpointBuilder resultMinimumWaitTime(String resultMinimumWaitTime) { + doSetProperty("resultMinimumWaitTime", resultMinimumWaitTime); + return this; + } + /** + * Sets the maximum amount of time the assertIsSatisfied() will wait on + * a latch until it is satisfied. + * + * The option is a: <code>long</code> type. + * + * Group: producer (advanced) + * + * @param resultWaitTime the value to set + * @return the dsl builder + */ + default AdvancedCitrusEndpointBuilder resultWaitTime(long resultWaitTime) { + doSetProperty("resultWaitTime", resultWaitTime); + return this; + } + /** + * Sets the maximum amount of time the assertIsSatisfied() will wait on + * a latch until it is satisfied. + * + * The option will be converted to a <code>long</code> type. + * + * Group: producer (advanced) + * + * @param resultWaitTime the value to set + * @return the dsl builder + */ + default AdvancedCitrusEndpointBuilder resultWaitTime(String resultWaitTime) { + doSetProperty("resultWaitTime", resultWaitTime); + return this; + } + /** + * Specifies to only retain the first nth number of received Exchanges. + * This is used when testing with big data, to reduce memory consumption + * by not storing copies of every Exchange this mock endpoint receives. + * Important: When using this limitation, then the getReceivedCounter() + * will still return the actual number of received message. For example + * if we have received 5000 messages and have configured to only retain + * the first 10 Exchanges, then the getReceivedCounter() will still + * return 5000 but there is only the first 10 Exchanges in the + * getExchanges() and getReceivedExchanges() methods. When using this + * method, then some of the other expectation methods is not supported, + * for example the expectedBodiesReceived(Object...) sets a expectation + * on the first number of bodies received. You can configure both + * retainFirst and retainLast options, to limit both the first and last + * received. + * + * The option is a: <code>int</code> type. + * + * Default: -1 + * Group: producer (advanced) + * + * @param retainFirst the value to set + * @return the dsl builder + */ + default AdvancedCitrusEndpointBuilder retainFirst(int retainFirst) { + doSetProperty("retainFirst", retainFirst); + return this; + } + /** + * Specifies to only retain the first nth number of received Exchanges. + * This is used when testing with big data, to reduce memory consumption + * by not storing copies of every Exchange this mock endpoint receives. + * Important: When using this limitation, then the getReceivedCounter() + * will still return the actual number of received message. For example + * if we have received 5000 messages and have configured to only retain + * the first 10 Exchanges, then the getReceivedCounter() will still + * return 5000 but there is only the first 10 Exchanges in the + * getExchanges() and getReceivedExchanges() methods. When using this + * method, then some of the other expectation methods is not supported, + * for example the expectedBodiesReceived(Object...) sets a expectation + * on the first number of bodies received. You can configure both + * retainFirst and retainLast options, to limit both the first and last + * received. + * + * The option will be converted to a <code>int</code> type. + * + * Default: -1 + * Group: producer (advanced) + * + * @param retainFirst the value to set + * @return the dsl builder + */ + default AdvancedCitrusEndpointBuilder retainFirst(String retainFirst) { + doSetProperty("retainFirst", retainFirst); + return this; + } + /** + * Specifies to only retain the last nth number of received Exchanges. + * This is used when testing with big data, to reduce memory consumption + * by not storing copies of every Exchange this mock endpoint receives. + * Important: When using this limitation, then the getReceivedCounter() + * will still return the actual number of received message. For example + * if we have received 5000 messages and have configured to only retain + * the last 20 Exchanges, then the getReceivedCounter() will still + * return 5000 but there is only the last 20 Exchanges in the + * getExchanges() and getReceivedExchanges() methods. When using this + * method, then some of the other expectation methods is not supported, + * for example the expectedBodiesReceived(Object...) sets a expectation + * on the first number of bodies received. You can configure both + * retainFirst and retainLast options, to limit both the first and last + * received. + * + * The option is a: <code>int</code> type. + * + * Default: -1 + * Group: producer (advanced) + * + * @param retainLast the value to set + * @return the dsl builder + */ + default AdvancedCitrusEndpointBuilder retainLast(int retainLast) { + doSetProperty("retainLast", retainLast); + return this; + } + /** + * Specifies to only retain the last nth number of received Exchanges. + * This is used when testing with big data, to reduce memory consumption + * by not storing copies of every Exchange this mock endpoint receives. + * Important: When using this limitation, then the getReceivedCounter() + * will still return the actual number of received message. For example + * if we have received 5000 messages and have configured to only retain + * the last 20 Exchanges, then the getReceivedCounter() will still + * return 5000 but there is only the last 20 Exchanges in the + * getExchanges() and getReceivedExchanges() methods. When using this + * method, then some of the other expectation methods is not supported, + * for example the expectedBodiesReceived(Object...) sets a expectation + * on the first number of bodies received. You can configure both + * retainFirst and retainLast options, to limit both the first and last + * received. + * + * The option will be converted to a <code>int</code> type. + * + * Default: -1 + * Group: producer (advanced) + * + * @param retainLast the value to set + * @return the dsl builder + */ + default AdvancedCitrusEndpointBuilder retainLast(String retainLast) { + doSetProperty("retainLast", retainLast); + return this; + } + /** + * Allows a sleep to be specified to wait to check that this mock really + * is empty when expectedMessageCount(int) is called with zero value. + * + * The option is a: <code>long</code> type. + * + * Group: producer (advanced) + * + * @param sleepForEmptyTest the value to set + * @return the dsl builder + */ + default AdvancedCitrusEndpointBuilder sleepForEmptyTest(long sleepForEmptyTest) { + doSetProperty("sleepForEmptyTest", sleepForEmptyTest); + return this; + } + /** + * Allows a sleep to be specified to wait to check that this mock really + * is empty when expectedMessageCount(int) is called with zero value. + * + * The option will be converted to a <code>long</code> type. + * + * Group: producer (advanced) + * + * @param sleepForEmptyTest the value to set + * @return the dsl builder + */ + default AdvancedCitrusEndpointBuilder sleepForEmptyTest(String sleepForEmptyTest) { + doSetProperty("sleepForEmptyTest", sleepForEmptyTest); + return this; + } + /** + * Maximum number of messages to keep in memory available for browsing. + * Use 0 for unlimited. + * + * The option is a: <code>int</code> type. + * + * Default: 100 + * Group: advanced + * + * @param browseLimit the value to set + * @return the dsl builder + */ + default AdvancedCitrusEndpointBuilder browseLimit(int browseLimit) { + doSetProperty("browseLimit", browseLimit); + return this; + } + /** + * Maximum number of messages to keep in memory available for browsing. + * Use 0 for unlimited. + * + * The option will be converted to a <code>int</code> type. + * + * Default: 100 + * Group: advanced + * + * @param browseLimit the value to set + * @return the dsl builder + */ + default AdvancedCitrusEndpointBuilder browseLimit(String browseLimit) { + doSetProperty("browseLimit", browseLimit); + return this; + } } public interface CitrusBuilders { @@ -136,9 +592,8 @@ public interface CitrusEndpointBuilderFactory { * response. * There are 2 enums and the value can be one of: validate, receive * - * Path parameter: name - * A unique name to identify this endpoint. Allows multiple Citrus - * endpoints with different validation expectations in the same route. + * Path parameter: name (required) + * Name of mock endpoint * * @param path operation/name * @return the dsl builder @@ -162,9 +617,8 @@ public interface CitrusEndpointBuilderFactory { * response. * There are 2 enums and the value can be one of: validate, receive * - * Path parameter: name - * A unique name to identify this endpoint. Allows multiple Citrus - * endpoints with different validation expectations in the same route. + * Path parameter: name (required) + * Name of mock endpoint * * @param componentName to use a custom component name for the endpoint * instead of the default name
