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 30589ef8d6eeb6d4d01e2c93bb4a196d8b069ac7 Author: Christoph Deppisch <[email protected]> AuthorDate: Thu Oct 15 17:34:12 2026 +0200 CAMEL-25378: Fix receive mode failure recording and document edge cases Co-Authored-By: Claude Opus 4.6 <[email protected]> --- .../src/main/docs/citrus-component.adoc | 12 +++++++++++- .../apache/camel/component/citrus/CitrusEndpoint.java | 16 +++++++++++++--- .../component/citrus/CitrusReceiveErrorHandlingTest.java | 3 +++ 3 files changed, 27 insertions(+), 4 deletions(-) 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 3b223533a49f..f004bb3dcff8 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 @@ -39,8 +39,10 @@ citrus:operation/name[?options] Where `operation` is `validate` or `receive`, and `name` is an optional identifier to distinguish multiple Citrus endpoints in the same route. -* `validate` — validates exchanges without modifying them (pass-through validation). +* `validate` — validates exchanges without modifying them (pass-through validation). Validation + failures are collected silently and only surface when `assertIsSatisfied()` is called. * `receive` — validates exchanges and returns a simulated response (request/response simulation). + Validation failures remain on the exchange and trigger Camel's `onException` handlers. // component options: START include::partial$component-configure-options.adoc[] @@ -53,6 +55,10 @@ include::partial$component-endpoint-headers.adoc[] The Citrus endpoint acts as a producer-only endpoint. Routes send exchanges *to* `citrus:validate`, and the component validates each exchange using Citrus message validation. +NOTE: Spec setup (`validate()`, `validateAll()`, `when().receive()`) must complete before exchanges +start arriving. For async consumers (SEDA, timer-based routes), ensure all validation chains are +fully configured before starting the `CamelContext`. + === Validation modules The component does not bundle Citrus validation modules. You must add the required validation @@ -394,6 +400,10 @@ The two operations handle validation failures differently with respect to Camel' `onException` handlers and redelivery policies *do* trigger, which matches the expected behavior for a request/response endpoint where callers need to see errors. +NOTE: Each redelivery attempt counts as a new exchange in the mock counter. So if a +`receive(1)` spec fails and Camel redelivers, the retry is exchange 2 and +`receive(2)` applies to it — not `receive(1)` again. + === Exchange properties and variables as Citrus variables All exchange properties and exchange variables are automatically propagated to the Citrus test 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 bf58be0ad4df..efcdfc354e20 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 @@ -80,6 +80,10 @@ public class CitrusEndpoint extends MockEndpoint { return messageValidationSpec; } + /** + * Auto-incrementing variant. Do not mix with explicit {@code validate(index)} calls — the auto-index is unaware of + * explicit registrations and may overwrite them. + */ public MessageValidationSpec validate() { return validate(nextSpecIndex.incrementAndGet()); } @@ -139,12 +143,15 @@ public class CitrusEndpoint extends MockEndpoint { // 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. + // For validate-only mode, collect the error and clear the exception so it does not + // propagate through the route — validation errors are collected, not thrown. + getFailures().add(e); exchange.setException(null); } + // For receive mode, leave the exception on the exchange so Camel's onException + // handlers trigger. Do NOT add to failures — the error is handled by the route, + // not by assertIsSatisfied(). } } @@ -181,6 +188,9 @@ public class CitrusEndpoint extends MockEndpoint { public class WhenSpec { + /** + * Auto-incrementing variant. Do not mix with explicit {@code receive(index)} calls. + */ public WhenMessageValidationSpec receive() { return receive(nextSpecIndex.incrementAndGet()); } diff --git a/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusReceiveErrorHandlingTest.java b/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusReceiveErrorHandlingTest.java index 11192d5aa55b..3b52c39aab95 100644 --- a/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusReceiveErrorHandlingTest.java +++ b/components/camel-test/camel-test-citrus/src/test/java/org/apache/camel/component/citrus/CitrusReceiveErrorHandlingTest.java @@ -63,6 +63,9 @@ class CitrusReceiveErrorHandlingTest extends CamelTestSupport { handled.assertIsSatisfied(); assertThat(citrus.getReceivedCounter()).isEqualTo(1); + // receive mode does not add to failures — assertIsSatisfied passes because + // the error was handled by onException, not collected by the endpoint + citrus.assertIsSatisfied(); } @Override
