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 85b924c1859f CAMEL-26804: camel-jbang - a chaos experiment that feeds
a broker run by camel infra does not warn about a real system (#29557)
85b924c1859f is described below
commit 85b924c1859f122251493909e71f250cb5419c96
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Nov 10 13:10:24 2026 +0100
CAMEL-26804: camel-jbang - a chaos experiment that feeds a broker run by
camel infra does not warn about a real system (#29557)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../modules/ROOT/pages/camel-jbang-managing.adoc | 11 +-
.../action/CamelChaosExperimentAction.java | 7 +-
.../core/commands/ai/ChaosExplorePlaybook.java | 3 +-
.../dsl/jbang/core/common/ChaosExperimentRun.java | 39 +++++-
.../camel/dsl/jbang/core/common/InfraServices.java | 150 +++++++++++++++++++++
.../jbang/core/common/ChaosExperimentRunTest.java | 41 +++++-
.../dsl/jbang/core/common/InfraServicesTest.java | 67 +++++++++
7 files changed, 305 insertions(+), 13 deletions(-)
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 8a1fbac0b265..ade32cf767bd 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
@@ -973,7 +973,16 @@ polls, named so the route takes it: `chaos-<time>.json`,
numbered as `chaos-<tim
repeated, with the extension of its `include` or `antInclude` filter, else of
the body. A `CamelFileName` in `--header` names the files instead.
A feed to a remote system that is not stubbed, such as a Kafka broker, has a
warning, as the messages go to that system:
-use a local one (`camel infra run kafka`), or stub it.
+use a local one (`camel infra run kafka`), or stub it. A broker that `camel
infra run` started on this machine is such a
+local one: when the input of the route is on it (its address, as the
integration reports it, is on a port of a service
+that `camel infra ps` lists), there is no warning, and the traffic says so:
+
+[source,text]
+----
+ traffic: 6 messages sent to
kafka://refunds?brokers=localhost:9092&groupId=shop (the local kafka from camel
infra) in 199ms
+----
+
+A real broker tests what a stub cannot, such as a consumer that reads a failed
message again or goes on without it.
`camel cmd chaos-explore` lets the AI of `camel ask` do it all: let there be
chaos. It starts the exploration with the
budget, and the AI reads the suggestions, runs one experiment per call, reads
each verdict, and goes on while there is
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosExperimentAction.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosExperimentAction.java
index 08fe673bce9b..0167a45180e0 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosExperimentAction.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosExperimentAction.java
@@ -235,6 +235,8 @@ public class CamelChaosExperimentAction extends
CamelChaosAction {
long failed = traffic.getLongOrDefault("failed", 0);
StringBuilder sb = new StringBuilder();
sb.append(sent).append(sent == 1 ? " message" : "
messages").append(" sent to ").append(endpoint)
+ .append(traffic.containsKey("localInfra")
+ ? " (the local " + traffic.getString("localInfra")
+ " from camel infra)" : "")
.append(failed > 0 ? " (" + failed + " failed)" :
"").append(" in ")
.append(time(traffic.getLongOrDefault("elapsed", 0)));
// the status codes the clients of an HTTP feed got (CAMEL-26793)
@@ -250,7 +252,10 @@ public class CamelChaosExperimentAction extends
CamelChaosAction {
return sb.toString();
}
// a single message: the answer of the send
- return "1 message sent to " + endpoint +
("success".equals(traffic.getString("status")) ? "" : " (failed)");
+ return "1 message sent to " + endpoint
+ + (traffic.containsKey("localInfra")
+ ? " (the local " + traffic.getString("localInfra") + "
from camel infra)" : "")
+ + ("success".equals(traffic.getString("status")) ? "" : "
(failed)");
}
// a time under a second in millis, as a send of a few messages takes
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 e4f809384651..f95c5b51a110 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
@@ -95,7 +95,8 @@ public final class ChaosExplorePlaybook {
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
+ 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 f7e9b75a7bdd..3dfee88cdaf8 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
@@ -215,8 +215,11 @@ public final class ChaosExperimentRun {
if (ruleWarnings != null) {
warnings.addAll(ruleWarnings);
}
+ // a broker that camel infra runs on this machine is a local one, not
a real system (CAMEL-26804)
+ List<InfraServices.Service> infra = feed != null ?
InfraServices.running() : List.of();
+ InfraServices.Service local = feed != null ? localInfra(status,
feed.endpoint(), infra) : null;
if (feed != null) {
- String real = feedWarning(status, feed.endpoint());
+ String real = feedWarning(status, feed.endpoint(), infra);
if (real != null) {
warnings.add(real);
}
@@ -264,6 +267,9 @@ public final class ChaosExperimentRun {
}
JsonObject answer = new JsonObject(begun);
if (traffic != null) {
+ if (local != null) {
+ traffic.put("localInfra", local.name());
+ }
answer.put("traffic", traffic);
}
answer.put("waited", waited);
@@ -426,7 +432,7 @@ public final class ChaosExperimentRun {
* A warning when the feed goes to a real remote system, as the route it
reaches starts from one that is not stubbed
* (a Kafka broker, a JMS queue), or null. Such a feed is fine for a local
broker (camel infra run).
*/
- public static String feedWarning(JsonObject status, String feed) {
+ public static String feedWarning(JsonObject status, String feed,
List<InfraServices.Service> infra) {
List<Object> routes = status != null ? status.getCollection("routes")
: null;
JsonObject route = routes != null && !feed.endsWith("*") ?
route(routes, feed) : null;
String from = route != null ? route.getString("from") : null;
@@ -438,7 +444,7 @@ public final class ChaosExperimentRun {
}
String id = route.getString("routeId");
Boolean stubbed = stubbed(status, id);
- if (stubbed == null || stubbed) {
+ if (stubbed == null || stubbed || localInfra(status, feed, infra) !=
null) {
return null;
}
String scheme = model.getScheme();
@@ -447,6 +453,33 @@ public final class ChaosExperimentRun {
+ " with --stub=" + scheme + " so no real system gets them";
}
+ /**
+ * The service camel infra runs on this machine that the input of the
route the feed reaches is on (CAMEL-26804):
+ * the addresses of its input (the services of the status, such as the
brokers of Kafka, and the URI it starts from)
+ * are all local, on ports of that service. Null when the route is not
known, or its input is elsewhere.
+ */
+ static InfraServices.Service localInfra(JsonObject status, String feed,
List<InfraServices.Service> infra) {
+ List<Object> routes = status != null ? status.getCollection("routes")
: null;
+ JsonObject route = routes != null && !infra.isEmpty() &&
!feed.endsWith("*") ? route(routes, feed) : null;
+ if (route == null) {
+ return null;
+ }
+ String id = route.getString("routeId");
+ List<String> addresses = new ArrayList<>();
+ if (route.getString("from") != null) {
+ addresses.add(route.getString("from"));
+ }
+ if (status.get("services") instanceof JsonObject services &&
services.getCollection("services") != null) {
+ for (Object o : services.getCollection("services")) {
+ if (o instanceof JsonObject service &&
id.equals(service.getString("routeId"))
+ && service.getString("serviceUrl") != null) {
+ addresses.add(service.getString("serviceUrl"));
+ }
+ }
+ }
+ return InfraServices.serving(addresses, infra);
+ }
+
/** The route the feed reaches: by its id, or by the endpoint it starts
from; null when none. */
private static JsonObject route(List<Object> routes, String feed) {
String endpoint = normalize(feed);
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/InfraServices.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/InfraServices.java
new file mode 100644
index 000000000000..a9a7322609b6
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/InfraServices.java
@@ -0,0 +1,150 @@
+/*
+ * 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.common;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.function.LongPredicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
+
+/**
+ * The services that {@code camel infra run} started on this machine, from
their pid files in the Camel directory
+ * ({@code infra-<service>-<pid>.json}, which hold what the service tells
about itself, such as its brokers), with the
+ * ports they listen on. A chaos experiment uses it to know that a broker is a
local one from camel infra, not a real
+ * system (CAMEL-26804).
+ */
+public final class InfraServices {
+
+ // host:port, as in localhost:9092, PLAINTEXT://localhost:9092 or
tcp://127.0.0.1:61616
+ private static final Pattern HOST_PORT =
Pattern.compile("([A-Za-z0-9._-]+|\\[[0-9A-Fa-f:]+\\]):(\\d{1,5})(?!\\d)");
+ private static final Set<String> LOCAL_HOSTS = Set.of("localhost",
"127.0.0.1", "0.0.0.0", "::1", "[::1]");
+
+ /** A service camel infra runs: its name (such as kafka), its pid, and the
ports it listens on. */
+ public record Service(String name, long pid, Set<Integer> ports) {
+ }
+
+ private InfraServices() {
+ }
+
+ /** The services camel infra runs now, whose process is alive. */
+ public static List<Service> running() {
+ return running(CommandLineHelper.getCamelDir(),
+ pid ->
ProcessHandle.of(pid).map(ProcessHandle::isAlive).orElse(false));
+ }
+
+ static List<Service> running(Path camelDir, LongPredicate alive) {
+ List<Service> answer = new ArrayList<>();
+ try (Stream<Path> files = Files.list(camelDir)) {
+ for (Path file : files.toList()) {
+ String name = file.getFileName().toString();
+ if (!name.startsWith("infra-") || !name.endsWith(".json") ||
name.lastIndexOf('-') <= 6) {
+ continue;
+ }
+ long pid;
+ try {
+ pid = Long.parseLong(name.substring(name.lastIndexOf('-')
+ 1, name.length() - 5));
+ } catch (NumberFormatException e) {
+ continue;
+ }
+ if (!alive.test(pid)) {
+ continue;
+ }
+ Set<Integer> ports = new TreeSet<>();
+ try {
+ ports(Jsoner.deserialize(Files.readString(file)), null,
ports);
+ } catch (Exception e) {
+ // a pid file being written, or not ours: no ports known
+ }
+ answer.add(new Service(name.substring(6,
name.lastIndexOf('-')), pid, ports));
+ }
+ } catch (IOException e) {
+ // no Camel directory yet: nothing runs
+ }
+
answer.sort(Comparator.comparing(Service::name).thenComparingLong(Service::pid));
+ return answer;
+ }
+
+ /** The ports in the data of a service: a field whose name has port in it,
and the port of each host:port. */
+ private static void ports(Object value, String key, Set<Integer> ports) {
+ if (value instanceof JsonObject jo) {
+ jo.forEach((k, v) -> ports(v, k, ports));
+ } else if (value instanceof JsonArray ja) {
+ ja.forEach(v -> ports(v, key, ports));
+ } else if (value instanceof Number n && key != null &&
key.toLowerCase(Locale.ROOT).contains("port")) {
+ ports.add(n.intValue());
+ } else if (value instanceof String s) {
+ if (key != null && key.toLowerCase(Locale.ROOT).contains("port")
&& s.matches("\\d{1,5}")) {
+ ports.add(Integer.parseInt(s));
+ }
+ addresses(s).forEach((host, port) -> ports.add(port));
+ }
+ }
+
+ /** The host:port addresses in a text, such as the brokers of Kafka or the
URL of a broker. */
+ static Map<String, Integer> addresses(String text) {
+ Map<String, Integer> answer = new LinkedHashMap<>();
+ if (text == null) {
+ return answer;
+ }
+ Matcher m = HOST_PORT.matcher(text);
+ while (m.find()) {
+ int port = Integer.parseInt(m.group(2));
+ if (port > 0 && port <= 65535) {
+ answer.put(m.group(1).toLowerCase(Locale.ROOT) + ":" + port,
port);
+ }
+ }
+ return answer;
+ }
+
+ /**
+ * The service camel infra runs that the addresses are all on, such as
{@code kafka} for localhost:9092 when camel
+ * infra runs Kafka on that port; null when one of them is on another host
or another port, or there are none.
+ */
+ public static Service serving(List<String> texts, List<Service> services) {
+ Service found = null;
+ boolean any = false;
+ for (String text : texts) {
+ for (String address : addresses(text).keySet()) {
+ any = true;
+ String host = address.substring(0, address.lastIndexOf(':'));
+ int port =
Integer.parseInt(address.substring(address.lastIndexOf(':') + 1));
+ Service service = LOCAL_HOSTS.contains(host)
+ ? services.stream().filter(s ->
s.ports().contains(port)).findFirst().orElse(null) : null;
+ if (service == null) {
+ return null;
+ }
+ found = found == null ? service : found;
+ }
+ }
+ return any ? found : null;
+ }
+}
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 bd7a3e007a03..90816f647e5e 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
@@ -22,6 +22,7 @@ import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@@ -428,18 +429,44 @@ class ChaosExperimentRunTest {
"chaos", new JsonObject(Map.of("report", report))));
// not stubbed: the messages go to the broker (CAMEL-26792)
- assertThat(ChaosExperimentRun.feedWarning(status,
"refunds")).isEqualTo(
+ assertThat(ChaosExperimentRun.feedWarning(status, "refunds",
List.of())).isEqualTo(
"The feed goes to kafka://refunds?brokers=localhost:9092,
where route refunds starts, and it is not"
-
+ " stubbed: the messages go to the real kafka. Use a local one (camel infra"
-
+ " run kafka), or run the integration with --stub=kafka so no real system"
-
+ " gets them");
+
+ " stubbed: the messages go to the real kafka. Use a local one
(camel infra"
+
+ " run kafka), or run the integration with --stub=kafka so no real
system"
+
+ " gets them");
// stubbed, a file, or a report that does not tell: no warning
- assertThat(ChaosExperimentRun.feedWarning(status, "returns")).isNull();
- assertThat(ChaosExperimentRun.feedWarning(status, "inbox")).isNull();
- assertThat(ChaosExperimentRun.feedWarning(new
JsonObject(Map.of("routes", status.get("routes"))), "refunds"))
+ assertThat(ChaosExperimentRun.feedWarning(status, "returns",
List.of())).isNull();
+ assertThat(ChaosExperimentRun.feedWarning(status, "inbox",
List.of())).isNull();
+ assertThat(ChaosExperimentRun.feedWarning(new
JsonObject(Map.of("routes", status.get("routes"))), "refunds", List.of()))
.isNull();
}
+ @Test
+ void aFeedToABrokerOfCamelInfraIsNotWarned() {
+ JsonObject report
+ = new JsonObject(Map.of("routes", new JsonArray(List.of(new
JsonObject(Map.of("route", "refunds"))))));
+ // the brokers of Kafka come from the services of the status, as they
are often configured on the component
+ JsonObject services = new JsonObject(
+ Map.of("services", new JsonArray(
+ List.of(new JsonObject(
+ Map.of("component", "kafka", "direction",
"in", "routeId", "refunds", "serviceUrl",
+ "localhost:32775"))))));
+ JsonObject status = new JsonObject(
+ Map.of(
+ "routes",
+ new JsonArray(List.of(new JsonObject(Map.of("routeId",
"refunds", "from", "kafka://refunds")))),
+ "services", services, "chaos", new
JsonObject(Map.of("report", report))));
+ List<InfraServices.Service> infra = List.of(new
InfraServices.Service("kafka", 42, Set.of(32775)));
+
+ // camel infra runs Kafka there: a local broker, not a real system
(CAMEL-26804)
+ assertThat(ChaosExperimentRun.feedWarning(status, "refunds",
infra)).isNull();
+ assertThat(ChaosExperimentRun.localInfra(status, "refunds",
infra).name()).isEqualTo("kafka");
+ // camel infra runs Kafka on another port, or nothing: the broker may
be a real one
+ List<InfraServices.Service> other = List.of(new
InfraServices.Service("kafka", 42, Set.of(9092)));
+ assertThat(ChaosExperimentRun.feedWarning(status, "refunds",
other)).startsWith("The feed goes to kafka://refunds");
+ assertThat(ChaosExperimentRun.feedWarning(status, "refunds",
List.of())).startsWith("The feed goes to kafka://refunds");
+ }
+
@Test
void aRuleThatMatchesNothingComesWithItsWarning() throws Exception {
integration.shows("PENDING", true);
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/InfraServicesTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/InfraServicesTest.java
new file mode 100644
index 000000000000..0875ad85d73b
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/InfraServicesTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.common;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class InfraServicesTest {
+
+ @TempDir
+ Path camelDir;
+
+ @Test
+ void theServicesOfCamelInfraWithTheirPorts() throws Exception {
+ // the pid files of camel infra run hold what the service tells about
itself
+ Files.writeString(camelDir.resolve("infra-kafka-101.json"),
+
"{\"brokers\":\"localhost:32775\",\"getBootstrapServers\":\"PLAINTEXT://localhost:32775\"}");
+ Files.writeString(camelDir.resolve("infra-artemis-102.json"),
+
"{\"serviceAddress\":\"tcp://127.0.0.1:61616\",\"port\":61616,\"webPort\":\"8161\"}");
+ // a service whose process is gone, and a file that is not a pid file
+ Files.writeString(camelDir.resolve("infra-mosquitto-103.json"),
"{\"port\":1883}");
+ Files.writeString(camelDir.resolve("infra-notes.json"), "{}");
+
+ List<InfraServices.Service> services = InfraServices.running(camelDir,
pid -> pid != 103);
+
+ assertThat(services).containsExactly(
+ new InfraServices.Service("artemis", 102, Set.of(61616, 8161)),
+ new InfraServices.Service("kafka", 101, Set.of(32775)));
+ }
+
+ @Test
+ void addressesAllOnAServiceOfCamelInfra() {
+ List<InfraServices.Service> services = List.of(
+ new InfraServices.Service("kafka", 101, Set.of(32775)),
+ new InfraServices.Service("artemis", 102, Set.of(61616)));
+
+ assertThat(InfraServices.serving(List.of("localhost:32775"),
services).name()).isEqualTo("kafka");
+ assertThat(InfraServices.serving(List.of("jms:queue:orders",
"tcp://127.0.0.1:61616"), services).name())
+ .isEqualTo("artemis");
+ // another host, another port, one broker of two elsewhere, or no
address at all: not known to be local
+ assertThat(InfraServices.serving(List.of("kafka.example.com:32775"),
services)).isNull();
+ assertThat(InfraServices.serving(List.of("localhost:9092"),
services)).isNull();
+
assertThat(InfraServices.serving(List.of("localhost:32775,localhost:9092"),
services)).isNull();
+ assertThat(InfraServices.serving(List.of("kafka://refunds"),
services)).isNull();
+ }
+}