This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch CAMEL-26723-chaos-from-error in repository https://gitbox.apache.org/repos/asf/camel.git
commit 938ca9c06c55fc6d864986069f8f0e4e5c9de8d0 Author: Claus Ibsen <[email protected]> AuthorDate: Sat Nov 7 22:52:23 2026 +0100 CAMEL-26723: camel-jbang - make a chaos rule of a real error, to prove a fix against the same failure On the Errors tab, ! on a real error (not one a chaos rule made) 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. The rule is typed in, to change before Enter, and is added after the confirmation. Only when chaos is on. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Claus Ibsen <[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(); + } +}
