This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new a8905fbd7cc2 CAMEL-26803: camel-jbang - a chaos experiment refuses a 
feed that does not reach the routes of its expectations (#29561)
a8905fbd7cc2 is described below

commit a8905fbd7cc25985d59b09b5f488a4d671cb8b74
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Nov 10 14:01:21 2026 +0100

    CAMEL-26803: camel-jbang - a chaos experiment refuses a feed that does not 
reach the routes of its expectations (#29561)
    
    In a run of camel cmd chaos-explore the model fed a route that did not lead
    to the route of its expectations, so those experiments said nothing and only
    ended INCONCLUSIVE after the budget was spent on them. The experiment is now
    refused before it begins, with a hint about which route to feed.
    
    The CLI passes the route the feed reaches with experiment-begin, and
    ChaosAdvisor.feedMisses refuses the experiment when no route of its
    expectations is fed, either by the route the feed reaches or by a route that
    makes its own messages (timer, scheduler). The CAMEL-26797 walk becomes
    ChaosAdvisor.reach, shared with the lost-without-hand-off warning.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../org/apache/camel/catalog/docs/chaos.adoc       |   7 +
 components/camel-chaos/src/main/docs/chaos.adoc    |   7 +
 .../apache/camel/component/chaos/ChaosAdvisor.java | 144 ++++++++++++++++-----
 .../apache/camel/component/chaos/ChaosManager.java |  17 +++
 .../camel/component/chaos/ChaosFeedReachTest.java  |  99 ++++++++++++++
 .../modules/ROOT/pages/camel-jbang-managing.adoc   |  12 +-
 .../apache/camel/cli/connector/ChaosAction.java    |   7 +-
 .../core/commands/ai/ChaosExplorePlaybook.java     |   6 +-
 .../dsl/jbang/core/common/ChaosExperimentRun.java  |   8 ++
 .../jbang/core/common/ChaosExperimentRunTest.java  |  17 +++
 10 files changed, 289 insertions(+), 35 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/chaos.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/chaos.adoc
index 8d37bfe685d2..3557980aed9a 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/chaos.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/chaos.adoc
@@ -422,6 +422,13 @@ The `chaos` action of the CLI connector begins one 
(`{"action": "chaos", "comman
 within=70s"}`), with several rules separated by `;`, and ends it 
(`experiment-end`, with an optional `note` of what the
 agent makes of it). In Java, these are 
`ChaosManager.beginExperiment(hypothesis, rules)` and `endExperiment(note)`.
 
+An experiment whose messages are fed to a route gives that route too 
(`"feedRoute": "refunds"`, or
+`beginExperiment(hypothesis, rules, feedRoute)`, as `camel cmd 
chaos-experiment --feed` does). It is refused when the
+routes the feed reaches do not include a route of its expectations, and no 
route that makes its own messages (a timer,
+a scheduler) reaches one either: the experiment would say nothing 
(CAMEL-26803). The routes reached are those the static
+sends lead to, over `direct`, `seda`, a broker, or an HTTP call to an HTTP 
server route of the integration; a route with a
+send known only when it sends (`toD`, `wireTap`, `recipientList`) is not 
checked.
+
 The journal has them as `experiments`, each with:
 
 * `number`, `hypothesis`, the `rules` (their ids) and `ruleTexts`
diff --git a/components/camel-chaos/src/main/docs/chaos.adoc 
b/components/camel-chaos/src/main/docs/chaos.adoc
index 8d37bfe685d2..3557980aed9a 100644
--- a/components/camel-chaos/src/main/docs/chaos.adoc
+++ b/components/camel-chaos/src/main/docs/chaos.adoc
@@ -422,6 +422,13 @@ The `chaos` action of the CLI connector begins one 
(`{"action": "chaos", "comman
 within=70s"}`), with several rules separated by `;`, and ends it 
(`experiment-end`, with an optional `note` of what the
 agent makes of it). In Java, these are 
`ChaosManager.beginExperiment(hypothesis, rules)` and `endExperiment(note)`.
 
+An experiment whose messages are fed to a route gives that route too 
(`"feedRoute": "refunds"`, or
+`beginExperiment(hypothesis, rules, feedRoute)`, as `camel cmd 
chaos-experiment --feed` does). It is refused when the
+routes the feed reaches do not include a route of its expectations, and no 
route that makes its own messages (a timer,
+a scheduler) reaches one either: the experiment would say nothing 
(CAMEL-26803). The routes reached are those the static
+sends lead to, over `direct`, `seda`, a broker, or an HTTP call to an HTTP 
server route of the integration; a route with a
+send known only when it sends (`toD`, `wireTap`, `recipientList`) is not 
checked.
+
 The journal has them as `experiments`, each with:
 
 * `number`, `hypothesis`, the `rules` (their ids) and `ruleTexts`
diff --git 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosAdvisor.java
 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosAdvisor.java
index 7ea06df426a9..e93f2da975f1 100644
--- 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosAdvisor.java
+++ 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosAdvisor.java
@@ -299,32 +299,63 @@ final class ChaosAdvisor {
 
     /**
      * The route of the integration that receives a send: the route that 
consumes the endpoint (direct, seda), or for an
-     * HTTP call to this machine the platform-http route of its path; null 
when it is another system.
+     * HTTP call to this machine (http, netty-http, undertow, vertx-http) the 
HTTP server route of its path, on its port
+     * when the server route names one (platform-http serves on the port of 
the integration); null when it is another
+     * system.
      */
     static String receiver(String uri, Map<String, String> consumers) {
         String route = consumers.get(uri);
         if (route != null) {
             return route;
         }
-        String scheme = ChaosPatterns.scheme(uri);
-        if (!"http".equals(scheme) && !"https".equals(scheme)) {
+        String[] call = httpAddress(uri);
+        if (call == null || !LOCAL_HOSTS.contains(call[0])) {
             return null;
         }
-        String rest = uri.substring(uri.indexOf("//") + 2);
-        int slash = rest.indexOf('/');
-        String host = (slash >= 0 ? rest.substring(0, slash) : 
rest).split(":")[0];
-        if (!"localhost".equals(host) && !"127.0.0.1".equals(host) && 
!"0.0.0.0".equals(host)) {
-            return null;
-        }
-        String path = slash >= 0 ? rest.substring(slash) : "/";
         for (Map.Entry<String, String> e : consumers.entrySet()) {
-            if (e.getKey().startsWith("platform-http:") && 
path.equals(httpPath(e.getKey()))) {
-                return e.getValue();
+            String consumer = e.getKey();
+            if (consumer.startsWith("platform-http:")) {
+                if (call[2].equals(httpPath(consumer))) {
+                    return e.getValue();
+                }
+            } else {
+                String[] served = httpAddress(consumer);
+                if (served != null && call[2].equals(served[2]) && 
(served[1].isEmpty() || served[1].equals(call[1]))) {
+                    return e.getValue();
+                }
             }
         }
         return null;
     }
 
+    private static final Set<String> LOCAL_HOSTS = Set.of("localhost", 
"127.0.0.1", "0.0.0.0");
+
+    /**
+     * The host, port (empty when not given) and path of an HTTP endpoint 
(http:, or a component that takes an HTTP URL
+     * such as netty-http:http://host:port/path), or null when it is not one.
+     */
+    private static String[] httpAddress(String uri) {
+        String scheme = ChaosPatterns.scheme(uri);
+        if (scheme == null || !ChaosRuleParser.HTTP_SCHEMES.contains(scheme) 
&& !HTTP_SERVER_SCHEMES.contains(scheme)) {
+            return null;
+        }
+        int at = Math.max(uri.lastIndexOf("http://";), 
uri.lastIndexOf("https://";));
+        String rest = at >= 0 ? uri.substring(uri.indexOf("//", at) + 2) : 
uri.substring(uri.indexOf(':') + 1);
+        while (rest.startsWith("/")) {
+            rest = rest.substring(1);
+        }
+        int slash = rest.indexOf('/');
+        String hostPort = slash >= 0 ? rest.substring(0, slash) : rest;
+        String path = slash >= 0 ? rest.substring(slash) : "/";
+        int colon = hostPort.lastIndexOf(':');
+        String host = colon >= 0 ? hostPort.substring(0, colon) : hostPort;
+        String port = colon >= 0 ? hostPort.substring(colon + 1) : "";
+        return new String[] { host.toLowerCase(Locale.ROOT), port, path };
+    }
+
+    // the HTTP servers that take an HTTP URL to listen on
+    private static final Set<String> HTTP_SERVER_SCHEMES = 
Set.of("netty-http", "undertow", "jetty", "vertx-http");
+
     /** The method of a platform-http or rest input: the one it restricts to, 
else POST. */
     static String httpMethod(String uri) {
         if (uri.startsWith("rest:")) {
@@ -574,16 +605,46 @@ final class ChaosAdvisor {
                 || camelContext.getRoute(expectation.toRoute) == null) {
             return null;
         }
+        Map<String, Boolean> reached = reach(camelContext, 
List.of(expectation.route), false);
+        if (reached == null) {
+            return null;
+        }
+        Boolean handedOff = reached.get(expectation.toRoute);
+        if (handedOff == null) {
+            return "expects messages lost between routes " + expectation.route 
+ " and " + expectation.toRoute
+                   + ", but " + expectation.route + " does not hand messages 
to " + expectation.toRoute
+                   + " (over seda, a broker or a wireTap), so lost counts 
nothing of what fails in " + expectation.route
+                   + ": expect=" + expectation.route + ":failed<=0 counts it";
+        }
+        if (!handedOff) {
+            return "expects messages lost between routes " + expectation.route 
+ " and " + expectation.toRoute
+                   + ", but " + expectation.route + " calls " + 
expectation.toRoute
+                   + " over direct and waits for it, so a failure there fails 
" + expectation.route
+                   + " and nothing is lost between them: expect=" + 
expectation.route + ":failed<=0 counts it";
+        }
+        return null;
+    }
+
+    /**
+     * The routes the given routes reach by the static sends of their steps (a 
send to an endpoint a route consumes, and
+     * with {@code http} an HTTP call to a platform-http route of the 
integration), with whether a hand-off (a send that
+     * is not direct or HTTP: seda, a broker...) was on the way (one that was 
wins). The given routes are in it, without
+     * a hand-off. Null when a route on the way has a send that is known only 
when it sends (toD, wireTap,
+     * recipientList...), as then where it goes is not known.
+     */
+    static Map<String, Boolean> reach(CamelContext camelContext, List<String> 
from, boolean http) {
         Map<String, String> consumers = new HashMap<>();
         for (Route route : camelContext.getRoutes()) {
             if (route.getEndpoint() != null) {
                 consumers.put(uri(route.getEndpoint()), route.getRouteId());
             }
         }
-        // the routes the first one reaches, and whether a hand-off was on the 
way there (one that was wins)
         Map<String, Boolean> reached = new HashMap<>();
-        reached.put(expectation.route, false);
-        Deque<String> todo = new ArrayDeque<>(List.of(expectation.route));
+        Deque<String> todo = new ArrayDeque<>();
+        for (String id : from) {
+            reached.put(id, false);
+            todo.add(id);
+        }
         while (!todo.isEmpty()) {
             String id = todo.poll();
             Route route = camelContext.getRoute(id);
@@ -602,11 +663,12 @@ final class ChaosAdvisor {
                     continue;
                 }
                 String uri = uri(aware.getEndpoint());
-                String next = consumers.get(uri);
+                String next = http ? receiver(uri, consumers) : 
consumers.get(uri);
                 if (next == null) {
                     continue;
                 }
-                boolean handedOff = reached.get(id) || 
!"direct".equals(ChaosPatterns.scheme(uri));
+                String scheme = ChaosPatterns.scheme(uri);
+                boolean handedOff = reached.get(id) || scheme != null && 
!"direct".equals(scheme) && !scheme.startsWith("http");
                 Boolean before = reached.get(next);
                 if (before == null || (!before && handedOff)) {
                     reached.put(next, handedOff);
@@ -614,20 +676,44 @@ final class ChaosAdvisor {
                 }
             }
         }
-        Boolean handedOff = reached.get(expectation.toRoute);
-        if (handedOff == null) {
-            return "expects messages lost between routes " + expectation.route 
+ " and " + expectation.toRoute
-                   + ", but " + expectation.route + " does not hand messages 
to " + expectation.toRoute
-                   + " (over seda, a broker or a wireTap), so lost counts 
nothing of what fails in " + expectation.route
-                   + ": expect=" + expectation.route + ":failed<=0 counts it";
+        return reached;
+    }
+
+    /**
+     * Why an experiment says nothing because its feed does not reach the 
routes of its expectations (CAMEL-26803), or
+     * null when it reaches one, or that is not known. A route of an 
expectation gets messages when the route the feed
+     * reaches sends to it, or a route that makes its own messages (a timer, a 
scheduler) does; a route reached through
+     * a send known only when it sends is not known, so then it says nothing.
+     */
+    static String feedMisses(CamelContext camelContext, String feedRoute, 
List<String> expected) {
+        if (feedRoute == null || camelContext.getRoute(feedRoute) == null || 
expected.isEmpty()) {
+            return null;
         }
-        if (!handedOff) {
-            return "expects messages lost between routes " + expectation.route 
+ " and " + expectation.toRoute
-                   + ", but " + expectation.route + " calls " + 
expectation.toRoute
-                   + " over direct and waits for it, so a failure there fails 
" + expectation.route
-                   + " and nothing is lost between them: expect=" + 
expectation.route + ":failed<=0 counts it";
+        List<String> self = new ArrayList<>();
+        for (Route route : camelContext.getRoutes()) {
+            if (route.getEndpoint() != null
+                    && 
SELF_FEEDING.contains(ChaosPatterns.scheme(route.getEndpoint().getEndpointUri())))
 {
+                self.add(route.getRouteId());
+            }
         }
-        return null;
+        Map<String, Boolean> fed = reach(camelContext, List.of(feedRoute), 
true);
+        Map<String, Boolean> others = reach(camelContext, self, true);
+        if (fed == null || others == null) {
+            return null;
+        }
+        for (String route : expected) {
+            if (fed.containsKey(route) || others.containsKey(route) || 
camelContext.getRoute(route) == null) {
+                return null;
+            }
+        }
+        List<String> reached = fed.keySet().stream().sorted().toList();
+        String missed = String.join(", ", 
expected.stream().distinct().sorted().toList());
+        return "The feed goes to route " + feedRoute + (reached.size() > 1
+                ? ", which sends to " + String.join(", ", 
reached.stream().filter(r -> !r.equals(feedRoute)).toList())
+                : "")
+               + ", but not to " + (expected.stream().distinct().count() == 1 
? "route " : "routes ") + missed
+               + " of the expectations, so the experiment would say nothing: 
feed " + expected.get(0)
+               + " instead (its id as the feed)";
     }
 
     /** The dead letter channel of the route, without its options, or null 
when its error handler has none. */
diff --git 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosManager.java
 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosManager.java
index f26bd9ab3fbe..dae9e0ca2d7f 100644
--- 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosManager.java
+++ 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosManager.java
@@ -26,6 +26,7 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
@@ -769,6 +770,17 @@ public final class ChaosManager {
      * @throws IllegalStateException    when no exploration is running, or an 
experiment is open
      */
     public Map<String, Object> beginExperiment(String hypothesis, String 
rules) {
+        return beginExperiment(hypothesis, rules, null);
+    }
+
+    /**
+     * Begins an experiment whose messages are fed to a route: as {@link 
#beginExperiment(String, String)}, and refused
+     * when the route the feed reaches does not lead to a route of its 
expectations, as the experiment would then say
+     * nothing (CAMEL-26803).
+     *
+     * @param feedRoute the id of the route the feed of the experiment 
reaches, or null when it has no feed
+     */
+    public Map<String, Object> beginExperiment(String hypothesis, String 
rules, String feedRoute) {
         List<String> texts = ChaosRuleParser.splitRules(rules);
         if (texts.isEmpty()) {
             throw new IllegalArgumentException("The chaos rule is empty");
@@ -782,6 +794,11 @@ public final class ChaosManager {
                     "An experiment needs an expectation (expect=) on one of 
its rules to decide its hypothesis, such as"
                                                + " expect=orders:failed<=0 
within=1m");
         }
+        String misses = ChaosAdvisor.feedMisses(camelContext, feedRoute,
+                definitions.stream().map(d -> 
d.expectRoute).filter(Objects::nonNull).toList());
+        if (misses != null) {
+            throw new IllegalArgumentException(misses);
+        }
         synchronized (lock) {
             long now = TimeSource.currentTimeMillis();
             checkDeadline(now);
diff --git 
a/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosFeedReachTest.java
 
b/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosFeedReachTest.java
new file mode 100644
index 000000000000..15f22c65f906
--- /dev/null
+++ 
b/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosFeedReachTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.chaos;
+
+import java.util.Map;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.AvailablePortFinder;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * CAMEL-26803: an experiment whose feed does not reach the routes of its 
expectations is refused before it begins, as
+ * it would say nothing (in a run of chaos-explore the AI fed direct:parked 
for hypotheses about refunds).
+ */
+class ChaosFeedReachTest extends ChaosTestSupport {
+
+    private static final int PORT = AvailablePortFinder.getNextAvailable();
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                // orders come by themselves, are charged over HTTP at a route 
of the same integration, and shipped
+                from("timer:orders?delay=-1&repeatCount=1").routeId("orders")
+                        .to("netty-http:http://localhost:"; + PORT + "/payment")
+                        .to("seda:shipping");
+                from("netty-http:http://0.0.0.0:"; + PORT + 
"/payment").routeId("payment").setBody(constant("paid"));
+                from("seda:shipping").routeId("shipping").to("mock:shipped");
+                // refunds come from outside, and a parked order goes nowhere 
else
+                from("direct:refunds").routeId("refunds").to("direct:payback");
+                from("direct:payback").routeId("payback").to("mock:paidBack");
+                from("direct:parked").routeId("parked").to("mock:parked");
+                // a send known only when it sends
+                
from("direct:router").routeId("router").toD("direct:${header.next}");
+            }
+        };
+    }
+
+    private Map<String, Object> begin(String rules, String feedRoute) {
+        return chaos().beginExperiment("a hypothesis", rules, feedRoute);
+    }
+
+    @Test
+    void aFeedThatMissesTheRoutesOfTheExpectationsIsRefused() {
+        chaos().startExploration("10m", null);
+
+        assertThatThrownBy(() -> begin("endpoint=mock:paidBack error 
expect=refunds:failed<=0 within=1m", "parked"))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessage("The feed goes to route parked, but not to route 
refunds of the expectations, so the"
+                            + " experiment would say nothing: feed refunds 
instead (its id as the feed)");
+        // what it reaches is named
+        assertThatThrownBy(() -> begin("endpoint=mock:parked error 
expect=parked:failed<=0 within=1m", "refunds"))
+                .hasMessageStartingWith("The feed goes to route refunds, which 
sends to payback, but not to route parked");
+        // nothing was added
+        assertThat(chaos().hasRule("r1")).isFalse();
+    }
+
+    @Test
+    void aFeedThatReachesARouteOfTheExpectationsBegins() {
+        chaos().startExploration("10m", null);
+
+        // a direct call
+        assertThat(begin("endpoint=mock:paidBack error 
expect=payback:failed<=0 within=1m", "refunds"))
+                .containsEntry("verdict", "PENDING");
+        chaos().endExperiment(null);
+        // the routes that the timer feeds get messages whatever the feed: 
over HTTP and over seda
+        assertThat(begin("route=shipping error expect=payment:failed<=0 
within=1m", "parked"))
+                .containsEntry("verdict", "PENDING");
+        chaos().endExperiment(null);
+        assertThat(begin("route=shipping error expect=shipping:failed<=0 
within=1m", "refunds"))
+                .containsEntry("verdict", "PENDING");
+        chaos().endExperiment(null);
+        // a send known only when it sends: where it goes is not known, so it 
is not refused
+        assertThat(begin("endpoint=mock:parked error expect=parked:failed<=0 
within=1m", "router"))
+                .containsEntry("verdict", "PENDING");
+        chaos().endExperiment(null);
+        // no feed
+        assertThat(begin("endpoint=mock:parked error expect=parked:failed<=0 
within=1m", null))
+                .containsEntry("verdict", "PENDING");
+    }
+}
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
index ade32cf767bd..625b11255da9 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
@@ -945,7 +945,17 @@ of `chaos-suggest` has it. `camel get chaos --report` 
shows the exploration with
 The messages of `--feed` are sent to the endpoint the route starts from, so 
they reach a route that starts from
 `direct`, `seda`, a stub (`camel run --stub=kafka`) or a broker. An experiment 
refuses a feed that cannot reach its
 route, with the reason: a route that starts from a timer, scheduler, cron or 
quartz makes its own messages (leave
-`--feed` out), and a route that starts from a component that only consumes 
cannot be fed this way.
+`--feed` out), and a route that starts from a component that only consumes 
cannot be fed this way. It also refuses a
+feed whose route does not lead to a route of its expectations, as the 
experiment would say nothing: the route the feed
+reaches, and the routes it sends to (over `direct`, `seda`, a broker, or HTTP 
to the integration's own server), must
+include one, unless a route that makes its own messages, such as a timer, 
sends to it. For example `--feed=parked` for
+`expect=refunds:failed<=0`:
+
+[source,text]
+----
+ERROR: The feed goes to route parked, but not to route refunds of the 
expectations, so the experiment would say
+nothing: feed refunds instead (its id as the feed)
+----
 
 A route that serves HTTP over the server of Camel (`platform-http`, or the 
REST DSL) is fed with HTTP requests to that
 server, on localhost and its port, with the path and method of the route, 
`--body` as the request body and `--header`
diff --git 
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/ChaosAction.java
 
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/ChaosAction.java
index 1d3cbfceac55..eebe24a97ea2 100644
--- 
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/ChaosAction.java
+++ 
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/ChaosAction.java
@@ -157,9 +157,12 @@ final class ChaosAction {
                     return;
                 }
                 // an experiment of the exploration (CAMEL-26774): its rules 
are added together, with an expectation
+                // the route the feed reaches, so an experiment that would say 
nothing is refused (CAMEL-26803)
+                String feedRoute = root.getString("feedRoute");
                 @SuppressWarnings("unchecked")
-                Map<String, Object> begun = (Map<String, Object>) call(type, 
chaos, "beginExperiment",
-                        root.getString("hypothesis"), rule);
+                Map<String, Object> begun = (Map<String, Object>) (feedRoute 
!= null
+                        ? call(type, chaos, "beginExperiment", 
root.getString("hypothesis"), rule, feedRoute)
+                        : call(type, chaos, "beginExperiment", 
root.getString("hypothesis"), rule));
                 answer.putAll(begun);
                 // a rule that matches nothing makes the experiment say 
nothing: as with add, the reason goes along
                 warnings(type, chaos, (List<?>) begun.get("rules"), answer);
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ChaosExplorePlaybook.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ChaosExplorePlaybook.java
index f95c5b51a110..84bc48831510 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ChaosExplorePlaybook.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ChaosExplorePlaybook.java
@@ -94,9 +94,9 @@ public final class ChaosExplorePlaybook {
                 with HTTP requests to the server of the integration: give the 
id of the route as feed, or the path \
                 to call when its path has a parameter (such as POST 
/orders/42); the traffic counts the status codes \
                 the clients got. A route of kind file is fed with its id too: 
each message is written as a file the \
-                route takes. An experiment refuses a feed that cannot reach 
its route, with the reason and \
-                what to do instead, and warns when the feed goes to a real 
system that is not stubbed (a local \
-                broker from camel infra is not one)
+                route takes. An experiment refuses a feed that cannot reach 
its route, or that does not lead to \
+                the routes of its expectations, with the reason and what to do 
instead, and warns when the feed \
+                goes to a real system that is not stubbed (a local broker from 
camel infra is not one)
                    - a rule at=dlq fails the dead letter channel, so it fires 
only when a message goes there: use \
                 the combined suggestions, which add the fault that sends 
messages there too. A message the \
                 dead letter channel did not deliver is handled there, so it 
does not fail in its route: the \
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRun.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRun.java
index 3dfee88cdaf8..ff4c2c91b557 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRun.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRun.java
@@ -202,11 +202,19 @@ public final class ChaosExperimentRun {
         if (within + POLL * 2 > wait) {
             wait = Math.min(within + POLL * 2, MAX_WAIT);
         }
+        // the route the feed reaches: the experiment is refused when it does 
not lead to a route of its expectations
+        // (CAMEL-26803)
+        List<Object> statusRoutes = feed != null && status != null ? 
status.getCollection("routes") : null;
+        JsonObject fedRoute
+                = statusRoutes != null && !feed.endpoint().endsWith("*") ? 
route(statusRoutes, feed.endpoint()) : null;
         JsonObject begun = integration.chaos("experiment-begin", root -> {
             if (hypothesis != null && !hypothesis.isBlank()) {
                 root.put("hypothesis", hypothesis);
             }
             root.put("rule", rules);
+            if (fedRoute != null) {
+                root.put("feedRoute", fedRoute.getString("routeId"));
+            }
         });
         int number = begun.getIntegerOrDefault("number", 0);
         // a rule that matches nothing in the integration now (often a typo), 
and a feed to a real system
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRunTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRunTest.java
index 90816f647e5e..dc3ccf83eca9 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRunTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRunTest.java
@@ -410,6 +410,23 @@ class ChaosExperimentRunTest {
         
assertThat(integration.sent.headers()).isEqualTo("country=DK\nCamelFileName=chaos-1000000.json");
     }
 
+    @Test
+    void theRouteTheFeedReachesGoesWithTheExperiment() throws Exception {
+        JsonObject status = statusWith(experiment("PENDING", true));
+        status.put("routes", new JsonArray(
+                List.of(new JsonObject(Map.of("routeId", "refunds", "from", 
"direct://refunds")))));
+        integration.statuses.add(status);
+        integration.shows("FAIL", true);
+
+        // by the endpoint it starts from: camel-chaos refuses the experiment 
when it leads to no route of its
+        // expectations (CAMEL-26803)
+        ChaosExperimentRun.run(integration, null, RULE,
+                new ChaosExperimentRun.Feed("direct:refunds", "{}", null, 1, 
null), 60_000, null, clock);
+
+        
assertThat(integration.actions.get(0).getString("command")).isEqualTo("experiment-begin");
+        
assertThat(integration.actions.get(0).getString("feedRoute")).isEqualTo("refunds");
+    }
+
     @Test
     void aFeedToARealSystemIsWarned() {
         JsonObject report = new JsonObject(

Reply via email to