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 fc5c0e3a84d0 CAMEL-26723: camel-jbang - make a chaos rule of a real
error, to prove a fix against the same failure (#29393)
fc5c0e3a84d0 is described below
commit fc5c0e3a84d0eef000f190508fe869fff5217568
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Nov 8 00:41:29 2026 +0100
CAMEL-26723: camel-jbang - make a chaos rule of a real error, to prove a
fix against the same failure (#29393)
On the Errors tab, ! on a real error opens the Chaos screen with a rule
that makes the same error again ten times: the same exception and message at
the step it happened at (else its endpoint, else its route), or the same HTTP
status at the same endpoint in the same route.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../org/apache/camel/catalog/docs/chaos.adoc | 4 +-
components/camel-chaos/src/main/docs/chaos.adoc | 4 +-
.../ROOT/pages/camel-jbang-tui-observe.adoc | 10 +-
.../dsl/jbang/core/commands/tui/ChaosControl.java | 11 ++
.../jbang/core/commands/tui/ChaosFromError.java | 113 +++++++++++++++++
.../dsl/jbang/core/commands/tui/ChaosTab.java | 5 +
.../dsl/jbang/core/commands/tui/ErrorsTab.java | 37 +++++-
.../jbang/core/commands/tui/MonitorContext.java | 2 +
.../dsl/jbang/core/commands/tui/TabRegistry.java | 4 +
.../src/main/resources/tui/help/errors.md | 1 +
.../jbang/core/commands/tui/ChaosControlTest.java | 14 +++
.../core/commands/tui/ChaosFromErrorTest.java | 138 +++++++++++++++++++++
12 files changed, 337 insertions(+), 6 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 2d6a8b709e41..7479bbdd7d16 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
@@ -255,7 +255,9 @@ there are stay. The ids of the rules go on from the last
one (`r4` above, after
of a scenario says which one it came from (`(scenario outage.chaos)` in the
text, `scenario` in the JSON).
The chaos screen of the Camel TUI saves the rules there are as a scenario (key
`s`), with the seed: rules tried by hand
-become a file of the project, to run again.
+become a file of the project, to run again. On its Errors tab, `!` makes a
rule of a real error, which fails the same
+way again (the same exception and message at the same step, or the same HTTP
status at the same endpoint, ten times):
+fix the bug, then prove the fix against the same failure, and keep the rule in
a scenario.
== Rules
diff --git a/components/camel-chaos/src/main/docs/chaos.adoc
b/components/camel-chaos/src/main/docs/chaos.adoc
index 2d6a8b709e41..7479bbdd7d16 100644
--- a/components/camel-chaos/src/main/docs/chaos.adoc
+++ b/components/camel-chaos/src/main/docs/chaos.adoc
@@ -255,7 +255,9 @@ there are stay. The ids of the rules go on from the last
one (`r4` above, after
of a scenario says which one it came from (`(scenario outage.chaos)` in the
text, `scenario` in the JSON).
The chaos screen of the Camel TUI saves the rules there are as a scenario (key
`s`), with the seed: rules tried by hand
-become a file of the project, to run again.
+become a file of the project, to run again. On its Errors tab, `!` makes a
rule of a real error, which fails the same
+way again (the same exception and message at the same step, or the same HTTP
status at the same endpoint, ten times):
+fix the bug, then prove the fix against the same failure, and keep the rule in
a scenario.
== Rules
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-observe.adoc
b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-observe.adoc
index 25710d8a894f..3ce16ed356b6 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-observe.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-observe.adoc
@@ -173,6 +173,13 @@ Inspect tab):
* Compare the message body and headers before and after the failing step
to understand what triggered the error
+With chaos on in the integration (`camel run --chaos`), press *!* on a real
error to make a chaos rule of it: the Chaos
+screen opens with a rule that makes the same error again ten times, at the
same step with the same exception and
+message, or at the same endpoint with the same HTTP status, such as
+`step=callStock error exception=java.net.ConnectException message="Connection
refused" until=10`. Change it if needed,
+and Enter adds it after you confirm. Fix the bug, then prove the fix against
the same failure without waiting for it
+to happen again.
+
This combination of error details + visual diagram replay gives you a complete
picture of
what went wrong and why -- without leaving the terminal.
@@ -325,7 +332,8 @@ image::jbang/camel-tui-chaos-recipes.png[Adding a chaos
rule from the known rule
* *!* on the Endpoint tab, the Consumers tab or a node of the Diagram opens
the known rules for what is selected, with
it filled in: for a `to` or an endpoint the routes send to, the faults of that
endpoint (an HTTP status only for an
HTTP endpoint) and of the step; for the `from` of a route, a consumer or an
endpoint a route consumes, the faults of
-the route, and of its polls when it polls.
+the route, and of its polls when it polls. On the Errors tab, *!* makes a rule
of the selected error instead, which
+fails the same way again (see <<_troubleshooting_errors,Troubleshooting
Errors>>).
+
image::jbang/camel-tui-chaos-here.png[! on the to callStock of the Diagram:
the known rules for its HTTP endpoint and its step, with the endpoint filled in]
* *x* removes the selected rule, *c* removes all rules (the report keeps what
they did), and *R* starts a new experiment:
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosControl.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosControl.java
index bcd40dd904b5..c9d794598c3b 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosControl.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosControl.java
@@ -252,6 +252,17 @@ final class ChaosControl {
}
}
+ /** Opens a rule typed as one line, to change before Enter: a rule that
makes a real error again (CAMEL-26723). */
+ void openRule(IntegrationInfo info, String text) {
+ if (!ChaosHere.available(info)) {
+ return;
+ }
+ pid = info.pid;
+ name = info.name;
+ id = null;
+ openInput(Command.ADD, text);
+ }
+
private ChaosRecipes knownRules() {
if (!recipesLoaded) {
recipes = recipeSource.get();
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosFromError.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosFromError.java
new file mode 100644
index 000000000000..0c175ffd0659
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosFromError.java
@@ -0,0 +1,113 @@
+/*
+ * 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.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A real error as a chaos rule (CAMEL-26723): {@code !} on the Errors tab
makes the same failure again, so a fix can be
+ * proved against it without waiting for it to happen again. The rule fails
the step the error happened at (else its
+ * endpoint, else its route) with the same exception and message ten times; an
HTTP failure fails the sends to the same
+ * endpoint in the same route with the same status.
+ */
+final class ChaosFromError {
+
+ // how often the rule fails: enough to see the error handling at work, and
it stops by itself
+ static final int TIMES = 10;
+
+ private static final int MAX_MESSAGE = 120;
+ private static final Pattern HTTP_STATUS = Pattern.compile("statusCode:
(\\d{3})");
+ private static final Pattern HTTP_URI = Pattern.compile("invoking (\\S+)
with statusCode");
+
+ private ChaosFromError() {
+ }
+
+ /**
+ * The rule that makes the error again, or null when the error says too
little (no step, endpoint or route, or no
+ * exception).
+ */
+ static String rule(ErrorInfo error) {
+ if (error == null || error.exceptionType == null ||
error.exceptionType.isBlank()) {
+ return null;
+ }
+ Integer status = httpStatus(error);
+ String httpTarget = status != null ? httpTarget(error) : null;
+ String target = httpTarget != null ? httpTarget : target(error);
+ if (target == null) {
+ return null;
+ }
+ StringBuilder sb = new StringBuilder(target).append(" error");
+ if (httpTarget != null) {
+ // what the HTTP endpoint answered: chaos raises the failure of
the component for it, at its sends
+ sb.append(" status=").append(status);
+ } else {
+ sb.append(" exception=").append(error.exceptionType.strip());
+ String message = message(error.exceptionMessage);
+ if (message != null) {
+ sb.append(" message=\"").append(message).append('"');
+ }
+ }
+ return sb.append(" until=").append(TIMES).toString();
+ }
+
+ private static String target(ErrorInfo error) {
+ if (error.nodeId != null && !error.nodeId.isBlank()) {
+ return "step=" + error.nodeId;
+ }
+ if (error.endpointUri != null && !error.endpointUri.isBlank()) {
+ int query = error.endpointUri.indexOf('?');
+ return "endpoint=" + (query > 0 ? error.endpointUri.substring(0,
query) : error.endpointUri);
+ }
+ if (error.routeId != null && !error.routeId.isBlank()) {
+ return "route=" + error.routeId;
+ }
+ return null;
+ }
+
+ /** The HTTP endpoint that answered with the status, in the route of the
error: status= fires at its sends. */
+ private static String httpTarget(ErrorInfo error) {
+ String uri = error.endpointUri;
+ if (uri == null || !uri.startsWith("http")) {
+ Matcher m = HTTP_URI.matcher(error.exceptionMessage);
+ uri = m.find() ? m.group(1) : null;
+ }
+ if (uri == null) {
+ return null;
+ }
+ int query = uri.indexOf('?');
+ String target = "endpoint=" + (query > 0 ? uri.substring(0, query) :
uri);
+ return error.routeId != null && !error.routeId.isBlank() ? target + "
in=" + error.routeId : target;
+ }
+
+ private static Integer httpStatus(ErrorInfo error) {
+ if (!error.exceptionType.endsWith("HttpOperationFailedException") ||
error.exceptionMessage == null) {
+ return null;
+ }
+ Matcher m = HTTP_STATUS.matcher(error.exceptionMessage);
+ return m.find() ? Integer.valueOf(m.group(1)) : null;
+ }
+
+ /** The message on one line, without double quotes (the rule quotes it),
and not too long. */
+ private static String message(String text) {
+ if (text == null || text.isBlank()) {
+ return null;
+ }
+ String line = text.strip().replaceAll("\\s+", " ").replace('"',
'\'').replace("\\", "/");
+ return line.length() > MAX_MESSAGE ? line.substring(0, MAX_MESSAGE -
3) + "..." : line;
+ }
+}
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 1949295904ee..de3f9b8e3d3c 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
@@ -83,6 +83,11 @@ class ChaosTab extends AbstractTableTab {
control.openAt(ctx.findSelectedIntegration(), target);
}
+ /** Opens the rule typed as one line, such as a rule that makes a real
error again. */
+ void openRule(String rule) {
+ control.openRule(ctx.findSelectedIntegration(), rule);
+ }
+
@Override
protected boolean handleTabKeyEvent(KeyEvent ke) {
IntegrationInfo info = ctx.findSelectedIntegration();
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 05dcf148098a..6c71f5d8c199 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
@@ -106,9 +106,7 @@ class ErrorsTab extends AbstractTableTab {
@Override
protected boolean handleTabKeyEvent(KeyEvent ke) {
- if (ke.isKey(KeyCode.F8) && ke.hasShift()) {
- // fix with AI: the selected error, on the source line it happened
at
- askAiToFixSelectedError();
+ if (handleErrorActionKey(ke)) {
return true;
}
if (diagram.isShowDiagram() && diagram.isHistoryMode() &&
diagram.hasHistoryData()) {
@@ -485,9 +483,42 @@ class ErrorsTab extends AbstractTableTab {
}
hint(spans, "Home/End", "top/end");
hint(spans, "s", "sort");
+ if (chaosRuleOfSelectedError() != null) {
+ hint(spans, "!", "chaos rule of it");
+ }
hintShowBhpv(spans, showBody, showHeaders, showProperties,
showVariables);
}
+ /** The keys that act on the selected error: fix with AI (Shift+F8), and a
chaos rule of it (!). */
+ private boolean handleErrorActionKey(KeyEvent ke) {
+ if (ke.isKey(KeyCode.F8) && ke.hasShift()) {
+ // fix with AI: the selected error, on the source line it happened
at
+ askAiToFixSelectedError();
+ return true;
+ }
+ String rule = ke.isChar('!') && !diagram.isShowDiagram() ?
chaosRuleOfSelectedError() : null;
+ if (rule != null) {
+ // the same failure again as a chaos rule, to prove a fix against
it (CAMEL-26723)
+ ctx.chaosRule.accept(rule);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * The chaos rule that makes the selected error again, or null: when chaos
is on in the integration, and the error
+ * is a real one (not made by a chaos rule).
+ */
+ String chaosRuleOfSelectedError() {
+ ErrorInfo error = selectedError();
+ IntegrationInfo info = ctx.findSelectedIntegration();
+ if (error == null || ctx.chaosRule == null ||
!ChaosHere.available(info)
+ || ChaosMarks.ruleOf(ctx, info, error.exchangeId,
error.properties) != null) {
+ return null;
+ }
+ return ChaosFromError.rule(error);
+ }
+
@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
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 82fe1c37e685..ec72b5b31007 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
@@ -94,6 +94,8 @@ class MonitorContext {
BiConsumer<String, Boolean> notificationCallback;
// opens the Chaos screen with the known rules for a target ({@code !} on
an endpoint, a consumer or a diagram node)
Consumer<ChaosHere.Target> chaosHere;
+ // ! on the Errors tab: opens the Chaos screen with a rule that makes the
selected error again (CAMEL-26723)
+ Consumer<String> chaosRule;
// opens what a line of About is about (Enter): a route on the Diagram, an
endpoint on the Endpoint tab
Consumer<AboutView.Target> openFromAbout;
// the exchanges chaos rules faulted, for the marks on the Activity,
Errors and Inspect tabs
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java
index 7484b3d5d2f9..a020b299286f 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java
@@ -335,6 +335,10 @@ class TabRegistry {
selectMoreTab(moreTabIndex("Chaos"));
chaosTab.openAt(target);
};
+ ctx.chaosRule = rule -> {
+ selectMoreTab(moreTabIndex("Chaos"));
+ chaosTab.openRule(rule);
+ };
ctx.openFromAbout = target -> {
if (target.endpoint() != null) {
handleTabKey(TAB_ENDPOINTS);
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 a051a615ed11..48b84e796c02 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
@@ -111,6 +111,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. 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
+- `!` — a chaos rule of the selected error, when chaos is on: the Chaos screen
opens with a rule that makes the same error again (the same exception and
message at the same step, or the same HTTP status at the same endpoint), ten
times, such as `step=callStock error exception=java.net.ConnectException
message="Connection refused" until=10`. Change it if you like, and Enter adds
it after you confirm. Fix the bug, then prove the fix against the same failure
without waiting for it to happen again
- `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/ChaosControlTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosControlTest.java
index f335bba5321d..dbca4cfb092a 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosControlTest.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosControlTest.java
@@ -273,6 +273,20 @@ class ChaosControlTest {
assertThat(ChaosScenarioFile.defaultPath(info)).isEqualTo("chaos/my-app.chaos");
}
+ @Test
+ void aRuleOpenedTypedInIsAddedAfterTheConfirmation() {
+ tab.openRule("step=callStock error exception=java.net.ConnectException
until=10");
+ // the input shows the end of the line, where the cursor is
+ assertThat(TuiTestHelper.renderToString(tab, 160, 40)).contains("Add a
chaos rule to chaosdemo")
+ .contains("ConnectException until=10");
+ enter();
+ assertThat(tab.isConfirming()).isTrue();
+ reply.put("id", "r2");
+ key('y');
+ assertThat(requests.get(0).getString("rule"))
+ .isEqualTo("step=callStock error
exception=java.net.ConnectException until=10");
+ }
+
@Test
void theFooterHasTheKeys() {
List<Span> footer = new ArrayList<>();
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosFromErrorTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosFromErrorTest.java
new file mode 100644
index 000000000000..f4c7f1e1070c
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ChaosFromErrorTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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;
+import java.util.concurrent.atomic.AtomicReference;
+
+import dev.tamboui.text.Span;
+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;
+
+/**
+ * A real error as a chaos rule ({@link ChaosFromError}): {@code !} on the
Errors tab opens the Chaos screen with a rule
+ * that makes the same failure again.
+ */
+class ChaosFromErrorTest {
+
+ private IntegrationInfo info;
+ private MonitorContext ctx;
+ private final List<String> opened = new ArrayList<>();
+
+ @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": [], "report": {"verdict": "PASS",
"final": true, "seed": 42}}
+ """));
+ ctx = new MonitorContext(new AtomicReference<>(List.of(info)), new
AtomicReference<>(List.of()));
+ ctx.selectedPid = "1234";
+ ctx.chaosRule = opened::add;
+ }
+
+ private static ErrorInfo error(String nodeId, String type, String message)
{
+ ErrorInfo ei = new ErrorInfo();
+ ei.exchangeId = "ID-1";
+ ei.routeId = "orders";
+ ei.nodeId = nodeId;
+ ei.exceptionType = type;
+ ei.exceptionMessage = message;
+ ei.timestamp = System.currentTimeMillis();
+ return ei;
+ }
+
+ @Test
+ void theSameExceptionAtTheSameStep() {
+ assertThat(ChaosFromError.rule(error("callStock",
"java.net.ConnectException", "Connection refused")))
+ .isEqualTo("step=callStock error
exception=java.net.ConnectException message=\"Connection refused\" until=10");
+ // the message on one line, without the quotes of the rule, and not
too long
+ String rule = ChaosFromError.rule(error("callStock",
"java.lang.IllegalStateException",
+ "the \"stock\" is\n gone " + "x".repeat(200)));
+ assertThat(rule)
+ .startsWith("step=callStock error
exception=java.lang.IllegalStateException message=\"the 'stock' is gone x")
+ .endsWith("...\" until=10");
+ // without a message
+ assertThat(ChaosFromError.rule(error("callStock",
"java.io.IOException", null)))
+ .isEqualTo("step=callStock error exception=java.io.IOException
until=10");
+ }
+
+ @Test
+ void anHttpFailureIsTheSameStatusAtTheSameEndpoint() {
+ ErrorInfo http = error("callStock",
"org.apache.camel.http.base.HttpOperationFailedException",
+ "HTTP operation failed invoking http://localhost:8765/stock
with statusCode: 503");
+ assertThat(ChaosFromError.rule(http))
+ .isEqualTo("endpoint=http://localhost:8765/stock in=orders
error status=503 until=10");
+ // the endpoint of the error when it has one
+ http.endpointUri = "https://inventory/stock?bridgeEndpoint=true";
+
assertThat(ChaosFromError.rule(http)).isEqualTo("endpoint=https://inventory/stock
in=orders error status=503 until=10");
+ }
+
+ @Test
+ void withoutAStepTheEndpointOrTheRoute() {
+ ErrorInfo ei = error(null, "java.io.IOException", "boom");
+ ei.endpointUri = "kafka:orders?brokers=localhost:9092";
+ assertThat(ChaosFromError.rule(ei))
+ .isEqualTo("endpoint=kafka:orders error
exception=java.io.IOException message=\"boom\" until=10");
+ ei.endpointUri = null;
+ assertThat(ChaosFromError.rule(ei))
+ .isEqualTo("route=orders error exception=java.io.IOException
message=\"boom\" until=10");
+ ei.routeId = null;
+ assertThat(ChaosFromError.rule(ei)).isNull();
+ assertThat(ChaosFromError.rule(error("callStock", null,
"boom"))).isNull();
+ }
+
+ @Test
+ void theErrorsTabOpensTheChaosScreenWithTheRule() {
+ info.errors = new ArrayList<>(List.of(error("callStock",
"java.net.ConnectException", "Connection refused")));
+ ErrorsTab tab = new ErrorsTab(ctx);
+ TuiTestHelper.renderToString(tab, 160, 40);
+ List<Span> footer = new ArrayList<>();
+ tab.renderFooter(footer);
+ assertThat(footer.stream().map(Span::content).reduce("",
String::concat)).contains("chaos rule of it");
+
+ tab.handleKeyEvent(KeyEvent.ofChar('!', KeyModifiers.NONE));
+ assertThat(opened).containsExactly(
+ "step=callStock error exception=java.net.ConnectException
message=\"Connection refused\" until=10");
+ }
+
+ @Test
+ void notForAnErrorMadeByChaosNorWhenChaosIsOff() {
+ ErrorInfo made = error("callStock", "java.net.ConnectException",
"Connection refused");
+ made.properties.put(ChaosMarks.PROPERTY, "r1");
+ info.errors = new ArrayList<>(List.of(made));
+ ErrorsTab tab = new ErrorsTab(ctx);
+ TuiTestHelper.renderToString(tab, 160, 40);
+ tab.handleKeyEvent(KeyEvent.ofChar('!', KeyModifiers.NONE));
+ assertThat(opened).isEmpty();
+
+ made.properties.clear();
+ info.chaos.enabled = false;
+ tab.handleKeyEvent(KeyEvent.ofChar('!', KeyModifiers.NONE));
+ assertThat(opened).isEmpty();
+ assertThat(tab.chaosRuleOfSelectedError()).isNull();
+ }
+}