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 c271592f64e5 CAMEL-26702: camel-jbang - chaos screen: explain with AI 
what a chaos rule did, its impact, and how to cope with it (#29364)
c271592f64e5 is described below

commit c271592f64e5218e51d6eebe19e2733a72ccd2ab
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Nov 6 23:10:36 2026 +0100

    CAMEL-26702: camel-jbang - chaos screen: explain with AI what a chaos rule 
did, its impact, and how to cope with it (#29364)
    
    Signed-off-by: Claus Ibsen <[email protected]>
    Co-authored-by: Claude Opus 5.5 <[email protected]>
---
 .../dsl/jbang/core/commands/tui/CamelMonitor.java  |   4 +-
 .../dsl/jbang/core/commands/tui/ChaosAiPrompt.java | 163 +++++++++++++++++++++
 .../dsl/jbang/core/commands/tui/ChaosTab.java      |  21 ++-
 .../dsl/jbang/core/commands/tui/ErrorsTab.java     |  14 +-
 .../jbang/core/commands/tui/MonitorContext.java    |   8 +
 .../src/main/resources/tui/help/chaos.md           |   6 +
 .../src/main/resources/tui/help/errors.md          |   2 +-
 .../jbang/core/commands/tui/ChaosAiPromptTest.java | 149 +++++++++++++++++++
 8 files changed, 361 insertions(+), 6 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
index 6482766a6d24..a26335cc6e09 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
@@ -598,8 +598,8 @@ public class CamelMonitor extends CamelCommand {
             }
 
             @Override
-            public void explainLogError(String error) {
-                openAiWithQuestion(dir -> AiFixPrompt.ofLogError(error));
+            public void ask(String question) {
+                openAiWithQuestion(dir -> question);
             }
         };
         ctx.projectOverviewCallback = () -> {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosAiPrompt.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosAiPrompt.java
new file mode 100644
index 000000000000..552ee7baeaaf
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosAiPrompt.java
@@ -0,0 +1,163 @@
+/*
+ * 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.dsl.jbang.core.commands.tui;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The question the AI panel is opened with to explain what a chaos rule did 
(Shift+F8 on the Chaos screen, and on an
+ * error a rule made on the Errors tab), CAMEL-26702: the rule, its live state 
and the counts of the routes, and what to
+ * do with them - say in plain words what the fault simulates and what the 
routes did with it, then suggest how they
+ * should cope, and change the routes only when the user agrees. It is put in 
the input of the panel, for the user to
+ * send with Enter or change first.
+ */
+final class ChaosAiPrompt {
+
+    /** The most recent faults quoted, so a busy rule does not flood the 
question. */
+    static final int MAX_RECENT = 5;
+
+    private ChaosAiPrompt() {
+    }
+
+    /** The question for a rule of the Chaos screen. */
+    static String of(IntegrationInfo info, ChaosInfo.Rule rule) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("Explain what chaos rule ").append(rule.id).append(" did to 
the running integration ")
+                .append(name(info)).append(", and what impact it had. The rule 
(camel-chaos, injected on purpose):\n")
+                .append("  ").append(rule.text).append('\n');
+        state(sb, info, rule);
+        sb.append("Say in plain words what the fault simulates, what the 
routes did with it (which exchanges recovered,")
+                .append(" which the error handler handled, which failed) and 
whether a message was lost. Then suggest how")
+                .append(" the routes should cope with this in production, such 
as redelivery with a delay, an idempotent")
+                .append(" consumer, a circuit breaker or a timeout. 
").append(onlyWhenAgreed());
+        return sb.toString();
+    }
+
+    /** The question for an error a chaos rule made, on the Errors tab: it is 
not a bug to fix. */
+    static String ofError(IntegrationInfo info, ChaosInfo.Rule rule, String 
ruleId, ErrorInfo error) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("This error of the running integration 
").append(name(info)).append(" was injected on purpose by chaos")
+                .append(" rule ").append(ruleId);
+        if (rule != null) {
+            sb.append(" (camel-chaos): ").append(rule.text);
+        }
+        sb.append('\n');
+        sb.append("  ").append(RuntimeFailures.describe(error));
+        if (error.routeId != null) {
+            sb.append(" in route ").append(error.routeId);
+            if (error.nodeId != null) {
+                sb.append(" at ").append(error.nodeId);
+            }
+        }
+        sb.append(error.handled ? " (handled by the error handler)" : " (not 
handled)").append('\n');
+        if (rule != null) {
+            state(sb, info, rule);
+        }
+        sb.append("Do not treat it as a bug to fix. Explain what happened to 
the exchange and how the route coped with")
+                .append(" the fault, and suggest how it should cope in 
production if it did not. ").append(onlyWhenAgreed());
+        return sb.toString();
+    }
+
+    /** The live state of the rule, and the counts of the routes its recent 
faults were in. */
+    private static void state(StringBuilder sb, IntegrationInfo info, 
ChaosInfo.Rule rule) {
+        sb.append("Its state now: ").append(rule.state != null ? rule.state : 
"unknown").append(", fired ")
+                .append(rule.fired).append(" times of 
").append(rule.matches).append(" matches");
+        String rate = ChaosTab.rate(rule);
+        if (!rate.isEmpty() && !"all".equals(rate)) {
+            sb.append(" (rate ").append(rate).append(')');
+        }
+        sb.append(".\n");
+        if (rule.faulted > 0) {
+            sb.append("Effect on ").append(rule.faulted).append(rule.faulted 
== 1 ? " exchange: " : " exchanges: ")
+                    .append(effect(rule)).append(".\n");
+        } else if (rule.firedAt.getOrDefault("poll", 0L) > 0) {
+            sb.append("It fired at 
").append(rule.firedAt.get("poll")).append(" polls of a consumer (no 
exchange).\n");
+        }
+        if (rule.expectRoute != null) {
+            sb.append("Expectation: 
").append(rule.expectRoute).append(':').append(rule.expectKind).append(rule.expectOp)
+                    .append(rule.expectCount).append(", 
").append(rule.expectOutcome != null ? rule.expectOutcome : "pending")
+                    .append(" (").append(rule.expectValue).append(' 
').append(rule.expectKind).append(").\n");
+        }
+        if (rule.lostAtDlq > 0) {
+            sb.append("Messages lost at the dead letter channel: 
").append(rule.lostAtDlq).append(".\n");
+        }
+        ChaosInfo chaos = info.chaos;
+        if (chaos == null) {
+            return;
+        }
+        List<String> routes = new ArrayList<>();
+        int recent = 0;
+        StringBuilder faults = new StringBuilder();
+        for (ChaosInfo.RecentFault f : chaos.recentFaults) {
+            if (rule.id == null || !rule.id.equals(f.rule())) {
+                continue;
+            }
+            if (f.route() != null && !routes.contains(f.route())) {
+                routes.add(f.route());
+            }
+            if (recent++ < MAX_RECENT) {
+                faults.append("  ").append(f.fault() != null ? f.fault() : 
"fault");
+                if (f.hook() != null) {
+                    faults.append(" at ").append(f.hook());
+                    if (f.where() != null) {
+                        faults.append(' ').append(f.where());
+                    }
+                }
+                if (f.route() != null) {
+                    faults.append(" in route ").append(f.route());
+                }
+                faults.append('\n');
+            }
+        }
+        if (!faults.isEmpty()) {
+            sb.append("Its recent faults, the latest first:\n").append(faults);
+        }
+        for (ChaosInfo.RouteCounts counts : chaos.routes) {
+            if (routes.contains(counts.route())) {
+                sb.append("Route ").append(counts.route()).append(" since 
chaos started: ").append(counts.completed())
+                        .append(" completed, 
").append(counts.failed()).append(" failed, ").append(counts.handled())
+                        .append(" handled by the error handler, 
").append(counts.redeliveries()).append(" redeliveries.\n");
+            }
+        }
+    }
+
+    private static String effect(ChaosInfo.Rule rule) {
+        List<String> parts = new ArrayList<>();
+        add(parts, rule.completed, "completed");
+        add(parts, rule.recovered, "recovered by redelivery");
+        add(parts, rule.handled, "handled by the error handler");
+        add(parts, rule.failed, "failed");
+        add(parts, rule.inFlight, "in flight");
+        return parts.isEmpty() ? "none yet" : String.join(", ", parts);
+    }
+
+    private static void add(List<String> parts, long count, String words) {
+        if (count > 0) {
+            parts.add(count + " " + words);
+        }
+    }
+
+    private static String onlyWhenAgreed() {
+        return "The camel_runtime_chaos_report tool has the full report. 
Propose changes to the routes, and make them"
+               + " with camel_edit_file only when I agree.";
+    }
+
+    private static String name(IntegrationInfo info) {
+        return info.name != null ? info.name : info.pid;
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosTab.java
index cadfadf6db76..8cb3a5de72ac 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosTab.java
@@ -33,6 +33,7 @@ import dev.tamboui.text.CharWidth;
 import dev.tamboui.text.Line;
 import dev.tamboui.text.Span;
 import dev.tamboui.text.Text;
+import dev.tamboui.tui.event.KeyCode;
 import dev.tamboui.tui.event.KeyEvent;
 import dev.tamboui.widgets.block.Block;
 import dev.tamboui.widgets.block.BorderType;
@@ -85,7 +86,25 @@ class ChaosTab extends AbstractTableTab {
     @Override
     protected boolean handleTabKeyEvent(KeyEvent ke) {
         IntegrationInfo info = ctx.findSelectedIntegration();
-        return control.handleKey(ke, info, info != null && info.chaos != null 
? selectedRule(info.chaos) : null);
+        ChaosInfo.Rule rule = info != null && info.chaos != null ? 
selectedRule(info.chaos) : null;
+        if (ke.isKey(KeyCode.F8) && ke.hasShift() && control.step() == 
ChaosControl.Step.NONE) {
+            // explain with AI what the selected rule did (CAMEL-26702)
+            if (rule != null && ctx.askAiCallback != null) {
+                ctx.askAiCallback.ask(ChaosAiPrompt.of(info, rule));
+            }
+            return true;
+        }
+        return control.handleKey(ke, info, rule);
+    }
+
+    @Override
+    public void renderFKeyHints(List<Span> spans) {
+        // with the global F-keys, as the AI key of a screen is an F-key of 
every screen that has one
+        IntegrationInfo info = ctx.findSelectedIntegration();
+        if (ctx.askAiCallback != null && control.step() == 
ChaosControl.Step.NONE && info != null
+                && info.chaos != null && selectedRule(info.chaos) != null) {
+            hint(spans, "Shift+F8", "explain with AI");
+        }
     }
 
     @Override
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
index 3510dd8bb11c..9c71e7d03069 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
@@ -491,8 +491,11 @@ class ErrorsTab extends AbstractTableTab {
     @Override
     public void renderFKeyHints(List<Span> spans) {
         // with the global F-keys (after Esc back), as fix with AI is an F-key 
of every screen that has it
-        if (!diagram.isShowDiagram() && ctx.askAiCallback != null && 
selectedError() != null) {
-            hint(spans, "Shift+F8", "fix with AI");
+        ErrorInfo error = selectedError();
+        if (!diagram.isShowDiagram() && ctx.askAiCallback != null && error != 
null) {
+            IntegrationInfo info = ctx.findSelectedIntegration();
+            boolean chaos = info != null && ChaosMarks.ruleOf(ctx, info, 
error.exchangeId, error.properties) != null;
+            hint(spans, "Shift+F8", chaos ? "explain with AI" : "fix with AI");
         }
     }
 
@@ -517,6 +520,13 @@ class ErrorsTab extends AbstractTableTab {
         if (info == null || error == null || ctx.askAiCallback == null) {
             return;
         }
+        String rule = ChaosMarks.ruleOf(ctx, info, error.exchangeId, 
error.properties);
+        if (rule != null) {
+            // made on purpose by a chaos rule: explain it, it is not a bug to 
fix (CAMEL-26702)
+            ctx.askAiCallback.ask(ChaosAiPrompt.ofError(info, info.chaos != 
null ? info.chaos.rule(rule) : null, rule,
+                    error));
+            return;
+        }
         String source = RuntimeFailures.sourceOf(info.routes, error);
         Path file = 
RuntimeFailures.fileOf(FilesBrowser.resolveSourceDirectory(info), source);
         int line = file != null ? LiveRunLines.lineOf(source, 
file.getFileName().toString()) : -1;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorContext.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorContext.java
index 80613a586897..b207871f1fd7 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorContext.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorContext.java
@@ -394,6 +394,14 @@ class MonitorContext {
          * line, the exception and its causes.
          */
         default void explainLogError(String error) {
+            ask(AiFixPrompt.ofLogError(error));
+        }
+
+        /**
+         * Opens the AI panel with a question made by the screen, such as 
explain with AI on the Chaos screen and on an
+         * error a chaos rule made (CAMEL-26702).
+         */
+        default void ask(String question) {
         }
     }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/chaos.md 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/chaos.md
index a830e2ce3931..f35c7eb31129 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/chaos.md
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/chaos.md
@@ -71,6 +71,12 @@ error handler (HAND) and the redeliveries (REDEL).
 - **x** — Remove the selected rule
 - **c** — Clear: remove all rules (the report keeps what they did)
 - **R** — A new experiment: no rules and a new report, with the same seed
+- **Shift+F8** — Explain with AI: the AI panel opens with a question about
+  the selected rule and what it did (its rate, its effect, the
+  expectation, the routes its faults were in, the recent faults). The AI
+  says what the fault simulates and how the routes coped, and suggests how
+  to make them cope better, changing the routes only when you agree.
+  Press Enter to send the question, or change it first
 
 Each asks to confirm with **y**, showing the rule. The integration checks
 the rule as `camel.chaos.rules` is checked: a rule that is not valid comes
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/errors.md 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/errors.md
index 31b4c4edd8d8..a051a615ed11 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/errors.md
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/errors.md
@@ -110,7 +110,7 @@ Press `Esc` to navigate back one route in drill-down mode.
 
 - `Up/Down` — select error (list) / navigate path steps (diagram)
 - `Enter` — view error details
-- `Shift+F8` — fix with AI: ask the AI about the selected error, on the source 
line it happened at
+- `Shift+F8` — fix with AI: ask the AI about the selected error, on the source 
line it happened at. For an error a chaos rule made (marked 🎲), it is explain 
with AI instead: the error was made on purpose, so the AI explains what the 
fault did and how the route coped, and does not chase it as a bug
 - `d` — toggle error diagram (open and close)
 - `Esc` — back to list / back one route in diagram drill-down
 - `i` — cycle info panel size (narrow / wide / full) in diagram
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosAiPromptTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosAiPromptTest.java
new file mode 100644
index 000000000000..b3f5a7f5c355
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosAiPromptTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.dsl.jbang.core.commands.tui;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import dev.tamboui.text.Span;
+import dev.tamboui.tui.event.KeyCode;
+import dev.tamboui.tui.event.KeyEvent;
+import dev.tamboui.tui.event.KeyModifiers;
+import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * CAMEL-26702: explain with AI (Shift+F8) what a chaos rule did, on the Chaos 
screen and on an error a rule made on the
+ * Errors tab, where it takes the place of fix with AI: the error is not a bug.
+ */
+class ChaosAiPromptTest {
+
+    private final List<String> asked = new ArrayList<>();
+    private IntegrationInfo info;
+    private MonitorContext ctx;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        Theme.resetForTesting();
+        info = new IntegrationInfo();
+        info.pid = "1234";
+        info.name = "chaosdemo";
+        info.chaos = ChaosInfo.parse((JsonObject) Jsoner.deserialize("""
+                {"enabled": true,
+                 "rules": [{"id": "r1", "version": 1, "state": "active",
+                            "rule": "endpoint=http://localhost:8765/stock* 
error status=503 rate=0.2 expect=orders:handled<=0",
+                            "matches": 120, "fired": {"send": 24}, "rate": 
0.2, "observedRate": 0.2,
+                            "effect": {"faulted": 22, "completed": 0, 
"recovered": 21, "handled": 1, "failed": 0,
+                                       "inFlight": 0},
+                            "expectation": {"route": "orders", "kind": 
"handled", "op": "<=", "count": 0,
+                                            "value": 1, "outcome": "unmet"}}],
+                 "report": {"verdict": "FAIL", "final": true, "seed": 42,
+                            "routes": [{"route": "orders", "completed": 119, 
"failed": 0, "failedKeptByConsumer": 0,
+                                        "handled": 1, "redeliveries": 25},
+                                       {"route": "shipping", "completed": 118, 
"failed": 0, "failedKeptByConsumer": 0,
+                                        "handled": 0, "redeliveries": 0}],
+                            "recentFaults": [{"ago": 2000, "rule": "r1", 
"fault": "error status 503", "hook": "send",
+                                              "route": "orders", "endpoint": 
"http://localhost:8765/stock";,
+                                              "exchangeId": 
"3F2A85F3D4213-000000000000104E"}]}}
+                """));
+        ctx = new MonitorContext(new AtomicReference<>(List.of(info)), new 
AtomicReference<>(List.of()));
+        ctx.selectedPid = "1234";
+        ctx.askAiCallback = new MonitorContext.AskAi() {
+            @Override
+            public void fixProblem(Path file, int line, String problem, String 
lineText) {
+                asked.add("fix: " + problem);
+            }
+
+            @Override
+            public void ask(String question) {
+                asked.add(question);
+            }
+        };
+    }
+
+    @Test
+    void theQuestionHasTheRuleItsEffectAndTheRoutesItHit() {
+        String question = ChaosAiPrompt.of(info, info.chaos.rule("r1"));
+
+        assertThat(question).startsWith("Explain what chaos rule r1 did to the 
running integration chaosdemo")
+                .contains("endpoint=http://localhost:8765/stock* error 
status=503 rate=0.2")
+                .contains("fired 24 times of 120 matches (rate 20% of 20%)")
+                .contains("Effect on 22 exchanges: 21 recovered by redelivery, 
1 handled by the error handler")
+                .contains("Expectation: orders:handled<=0, unmet (1 handled)")
+                .contains("error status 503 at send 
http://localhost:8765/stock in route orders")
+                .contains("Route orders since chaos started: 119 completed, 0 
failed, 1 handled by the error handler,"
+                          + " 25 redeliveries")
+                .contains("redelivery with a delay, an idempotent consumer, a 
circuit breaker or a timeout")
+                .endsWith("make them with camel_edit_file only when I agree.");
+        // the routes its faults were not in are left out
+        assertThat(question).doesNotContain("Route shipping");
+    }
+
+    @Test
+    void shiftF8OnTheChaosScreenExplainsTheSelectedRule() {
+        ChaosTab tab = new ChaosTab(ctx);
+        // the first rule is selected when the table is rendered
+        TuiTestHelper.renderToString(tab, 160, 40);
+        List<Span> hints = new ArrayList<>();
+        tab.renderFKeyHints(hints);
+        assertThat(text(hints)).contains("explain with AI");
+
+        tab.handleKeyEvent(KeyEvent.ofKey(KeyCode.F8, KeyModifiers.SHIFT));
+
+        assertThat(asked).hasSize(1);
+        assertThat(asked.get(0)).startsWith("Explain what chaos rule r1 did");
+    }
+
+    @Test
+    void anErrorAChaosRuleMadeIsExplainedNotFixed() {
+        ErrorInfo ei = new ErrorInfo();
+        ei.exchangeId = "3F2A85F3D4213-000000000000104E";
+        ei.routeId = "orders";
+        ei.nodeId = "callStock";
+        ei.handled = true;
+        ei.exceptionType = 
"org.apache.camel.http.base.HttpOperationFailedException";
+        ei.exceptionMessage = "HTTP operation failed with statusCode: 503";
+        ei.timestamp = System.currentTimeMillis();
+        ei.properties.put(ChaosMarks.PROPERTY, "r1");
+        info.errors = new ArrayList<>(List.of(ei));
+        ErrorsTab tab = new ErrorsTab(ctx);
+        TuiTestHelper.renderToString(tab, 160, 40);
+        List<Span> hints = new ArrayList<>();
+        tab.renderFKeyHints(hints);
+        assertThat(text(hints)).contains("explain with 
AI").doesNotContain("fix with AI");
+
+        tab.handleKeyEvent(KeyEvent.ofKey(KeyCode.F8, KeyModifiers.SHIFT));
+
+        assertThat(asked).hasSize(1);
+        assertThat(asked.get(0))
+                .startsWith("This error of the running integration chaosdemo 
was injected on purpose by chaos rule r1")
+                .contains("HttpOperationFailedException").contains("in route 
orders at callStock")
+                .contains("(handled by the error handler)").contains("Do not 
treat it as a bug to fix");
+    }
+
+    private static String text(List<Span> spans) {
+        StringBuilder sb = new StringBuilder();
+        spans.forEach(s -> sb.append(s.content()));
+        return sb.toString();
+    }
+}

Reply via email to