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 03ee8ca50b31 CAMEL-23688: camel-jbang - add unit tests for action
reader CLI commands
03ee8ca50b31 is described below
commit 03ee8ca50b310f428ba3010fac2f3cbcce761f0f
Author: Adriano Machado <[email protected]>
AuthorDate: Tue Jun 23 01:18:17 2026 -0400
CAMEL-23688: camel-jbang - add unit tests for action reader CLI commands
Adds unit tests for the request/response reader action commands in
camel-jbang-core: route-dump, bean, thread-dump, top-processors,
route-controller, and gc. A new callWithResponse helper in
ActionCommandTestSupport simulates the running Camel writing output
via a background responder thread synchronized with Awaitility.
Closes #24189
Co-Authored-By: Claude Opus 4.8 <[email protected]>
---
.../commands/action/ActionCommandTestSupport.java | 34 ++++++++
.../core/commands/action/CamelBeanDumpTest.java | 92 ++++++++++++++++++++++
.../core/commands/action/CamelGCActionTest.java | 59 ++++++++++++++
.../commands/action/CamelRouteDumpActionTest.java | 91 +++++++++++++++++++++
.../core/commands/action/CamelSourceTopTest.java | 87 ++++++++++++++++++++
.../core/commands/action/CamelThreadDumpTest.java | 91 +++++++++++++++++++++
.../commands/action/RouteControllerActionTest.java | 84 ++++++++++++++++++++
7 files changed, 538 insertions(+)
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/ActionCommandTestSupport.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/ActionCommandTestSupport.java
index 9a31d872dd30..0fc4e4423194 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/ActionCommandTestSupport.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/ActionCommandTestSupport.java
@@ -17,11 +17,13 @@
package org.apache.camel.dsl.jbang.core.commands.action;
import java.io.IOException;
+import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.Comparator;
import java.util.Optional;
+import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import org.apache.camel.dsl.jbang.core.commands.CamelCommand;
@@ -33,6 +35,7 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.mockito.MockedStatic;
+import static org.awaitility.Awaitility.await;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
@@ -117,6 +120,37 @@ abstract class ActionCommandTestSupport extends
CamelCommandBaseTestSupport {
}
}
+ /**
+ * Runs a request/response reader command end to end. Mocks a single
discoverable process (TEST_PID) and, in a
+ * background thread, simulates the running Camel writing {@code response}
to its output file once the command has
+ * written its action request.
+ * <p>
+ * The reader commands delete any stale output file <em>before</em>
writing the action file and only then poll for
+ * the output, so the responder waits for the action file to appear: that
guarantees the response lands after the
+ * command's own delete and cannot be wiped out. No {@code Thread.sleep}
is used; the wait is driven by Awaitility.
+ *
+ * @return the command exit code; rendered output can be asserted
afterwards via {@code printer.getOutput()}
+ */
+ protected int callWithResponse(CamelCommand command, JsonObject response)
throws Exception {
+ Path actionFile = CommandLineHelper.getCamelDir().resolve(TEST_PID +
"-action.json");
+ Path outputFile = CommandLineHelper.getCamelDir().resolve(TEST_PID +
"-output.json");
+ Thread responder = new Thread(() -> {
+ await().atMost(5, TimeUnit.SECONDS).until(() ->
Files.exists(actionFile));
+ try {
+ Files.writeString(outputFile, response.toJson());
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }, "test-camel-responder");
+ responder.setDaemon(true);
+ responder.start();
+ try {
+ return callWithSingleProcess(command);
+ } finally {
+ responder.join(TimeUnit.SECONDS.toMillis(10));
+ }
+ }
+
/**
* Creates a mock ProcessHandle for the test process. Info is
pre-configured with empty commandLine and a fixed
* start instant so extractName falls through to context.name.
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelBeanDumpTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelBeanDumpTest.java
new file mode 100644
index 000000000000..8f25e546ed58
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelBeanDumpTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.action;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@ExtendWith(MockitoExtension.class)
+class CamelBeanDumpTest extends ActionCommandTestSupport {
+
+ @Test
+ void testRequestsBeanDumpAndRendersBeans() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+
+ CamelBeanDump command = new CamelBeanDump(new
CamelJBangMain().withPrinter(printer));
+ command.name = "myApp";
+ // options are normally defaulted by picocli; set them as we construct
the command directly
+ command.filter = "all";
+ command.sort = "name";
+ command.properties = true;
+
+ int exit = callWithResponse(command, singleBeanResponse());
+
+ assertEquals(0, exit);
+
+ JsonObject action = readActionFile(TEST_PID);
+ assertNotNull(action, "action file should be written for the matched
process");
+ assertEquals("bean", action.getString("action"));
+
+ String out = printer.getOutput();
+ assertTrue(out.contains("BEAN: myBean"), "should print the bean name,
was: " + out);
+ assertTrue(out.contains("com.foo.MyBean"), "should print the bean
type, was: " + out);
+ assertTrue(out.contains("greeting"), "should print the bean property
name, was: " + out);
+ assertTrue(out.contains("helloWorld"), "should print the bean property
value, was: " + out);
+ }
+
+ @Test
+ void testReturnsErrorWhenNameDoesNotMatch() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+
+ CamelBeanDump command = new CamelBeanDump(new
CamelJBangMain().withPrinter(printer));
+ command.name = "doesNotExist";
+
+ int exit = callWithSingleProcess(command);
+
+ assertEquals(1, exit);
+ assertTrue(printer.getOutput().isEmpty(), "nothing should be rendered
when no process matches");
+ }
+
+ private static JsonObject singleBeanResponse() {
+ JsonObject property = new JsonObject();
+ property.put("name", "greeting");
+ property.put("type", "java.lang.String");
+ property.put("value", "helloWorld");
+ JsonArray properties = new JsonArray();
+ properties.add(property);
+
+ JsonObject bean = new JsonObject();
+ bean.put("name", "myBean");
+ bean.put("type", "com.foo.MyBean");
+ bean.put("properties", properties);
+
+ JsonObject beans = new JsonObject();
+ beans.put("myBean", bean);
+
+ JsonObject response = new JsonObject();
+ response.put("beans", beans);
+ return response;
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelGCActionTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelGCActionTest.java
new file mode 100644
index 000000000000..c723910ca967
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelGCActionTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.action;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+@ExtendWith(MockitoExtension.class)
+class CamelGCActionTest extends ActionCommandTestSupport {
+
+ @Test
+ void testWritesGcActionForMatchingName() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+
+ CamelGCAction command = new CamelGCAction(new
CamelJBangMain().withPrinter(printer));
+ command.name = "myApp";
+
+ int exit = callWithSingleProcess(command);
+
+ assertEquals(0, exit);
+ JsonObject action = readActionFile(TEST_PID);
+ assertNotNull(action, "action file should be written for the matched
process");
+ assertEquals("gc", action.getString("action"));
+ }
+
+ @Test
+ void testNoActionWhenNameDoesNotMatch() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+
+ CamelGCAction command = new CamelGCAction(new
CamelJBangMain().withPrinter(printer));
+ command.name = "doesNotExist";
+
+ int exit = callWithSingleProcess(command);
+
+ assertEquals(0, exit);
+ assertNull(readActionFile(TEST_PID), "no action file should be written
when no process matches");
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDumpActionTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDumpActionTest.java
new file mode 100644
index 000000000000..7e09e0a0b286
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDumpActionTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.action;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@ExtendWith(MockitoExtension.class)
+class CamelRouteDumpActionTest extends ActionCommandTestSupport {
+
+ @Test
+ void testRequestsRouteDumpAndRendersSource() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+
+ CamelRouteDumpAction command = new CamelRouteDumpAction(new
CamelJBangMain().withPrinter(printer));
+ command.name = "myApp";
+ // options are normally defaulted by picocli; set them as we construct
the command directly
+ command.format = "yaml";
+ command.sort = "name";
+
+ int exit = callWithResponse(command, singleRouteResponse());
+
+ assertEquals(0, exit);
+
+ // the command must request a route-dump with the default yaml format
+ JsonObject action = readActionFile(TEST_PID);
+ assertNotNull(action, "action file should be written for the matched
process");
+ assertEquals("route-dump", action.getString("action"));
+ assertEquals("yaml", action.getString("format"));
+
+ // the simulated response must be rendered as source code
+ String out = printer.getOutput();
+ assertTrue(out.contains("Source: myroute.yaml"), "should print the
source filename, was: " + out);
+ assertTrue(out.contains("from uri: timer:foo"), "should print the
route code, was: " + out);
+ }
+
+ @Test
+ void testReturnsErrorWhenNameDoesNotMatch() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+
+ CamelRouteDumpAction command = new CamelRouteDumpAction(new
CamelJBangMain().withPrinter(printer));
+ command.name = "doesNotExist";
+
+ // no process matches, so the command returns before requesting any
output
+ int exit = callWithSingleProcess(command);
+
+ assertEquals(1, exit);
+ assertTrue(printer.getOutput().isEmpty(), "nothing should be rendered
when no process matches");
+ }
+
+ private static JsonObject singleRouteResponse() {
+ JsonObject code = new JsonObject();
+ code.put("line", 1);
+ code.put("code", "from uri: timer:foo");
+ JsonArray codeLines = new JsonArray();
+ codeLines.add(code);
+
+ JsonObject route = new JsonObject();
+ route.put("source", "myroute.yaml");
+ route.put("routeId", "myRoute");
+ route.put("code", codeLines);
+ JsonArray routes = new JsonArray();
+ routes.add(route);
+
+ JsonObject response = new JsonObject();
+ response.put("routes", routes);
+ return response;
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSourceTopTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSourceTopTest.java
new file mode 100644
index 000000000000..37341bc382d8
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSourceTopTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.action;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@ExtendWith(MockitoExtension.class)
+class CamelSourceTopTest extends ActionCommandTestSupport {
+
+ @Test
+ void testRequestsTopProcessorsAndRendersRows() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+
+ CamelSourceTop command = new CamelSourceTop(new
CamelJBangMain().withPrinter(printer));
+ command.name = "myApp";
+
+ int exit = callWithResponse(command, singleProcessorResponse());
+
+ assertEquals(0, exit);
+
+ JsonObject action = readActionFile(TEST_PID);
+ assertNotNull(action, "action file should be written for the matched
process");
+ assertEquals("top-processors", action.getString("action"));
+
+ String out = printer.getOutput();
+ assertTrue(out.contains("Route: myRoute"), "should print the route id,
was: " + out);
+ assertTrue(out.contains("Total: 5"), "should print the processor
statistics, was: " + out);
+ }
+
+ @Test
+ void testReturnsErrorWhenNameDoesNotMatch() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+
+ CamelSourceTop command = new CamelSourceTop(new
CamelJBangMain().withPrinter(printer));
+ command.name = "doesNotExist";
+
+ int exit = callWithSingleProcess(command);
+
+ assertEquals(1, exit);
+ assertTrue(printer.getOutput().isEmpty(), "nothing should be rendered
when no process matches");
+ }
+
+ private static JsonObject singleProcessorResponse() {
+ JsonObject stats = new JsonObject();
+ stats.put("exchangesTotal", 5);
+ stats.put("meanProcessingTime", 2);
+ stats.put("maxProcessingTime", 3);
+ stats.put("minProcessingTime", 1);
+ stats.put("lastProcessingTime", 2);
+
+ JsonObject processor = new JsonObject();
+ processor.put("processorId", "myProcessor");
+ processor.put("routeId", "myRoute");
+ processor.put("location", "myroute.yaml");
+ processor.put("statistics", stats);
+
+ JsonArray processors = new JsonArray();
+ processors.add(processor);
+
+ JsonObject response = new JsonObject();
+ response.put("processors", processors);
+ return response;
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelThreadDumpTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelThreadDumpTest.java
new file mode 100644
index 000000000000..b76fb160994f
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelThreadDumpTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.action;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@ExtendWith(MockitoExtension.class)
+class CamelThreadDumpTest extends ActionCommandTestSupport {
+
+ @Test
+ void testRequestsThreadDumpAndRendersThreads() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+
+ CamelThreadDump command = new CamelThreadDump(new
CamelJBangMain().withPrinter(printer));
+ command.name = "myApp";
+ // options are normally defaulted by picocli; set them as we construct
the command directly
+ command.filters = new String[] { "all" };
+ command.sort = "id";
+ command.depth = 1;
+
+ int exit = callWithResponse(command, singleThreadResponse());
+
+ assertEquals(0, exit);
+
+ JsonObject action = readActionFile(TEST_PID);
+ assertNotNull(action, "action file should be written for the matched
process");
+ assertEquals("thread-dump", action.getString("action"));
+
+ String out = printer.getOutput();
+ assertTrue(out.contains("PID: " + TEST_PID), "should print the pid
header, was: " + out);
+ assertTrue(out.contains("Camel-thread-1"), "should print the thread
name, was: " + out);
+ assertTrue(out.contains("RUNNABLE"), "should print the thread state,
was: " + out);
+ }
+
+ @Test
+ void testReturnsErrorWhenNameDoesNotMatch() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+
+ CamelThreadDump command = new CamelThreadDump(new
CamelJBangMain().withPrinter(printer));
+ command.name = "doesNotExist";
+
+ int exit = callWithSingleProcess(command);
+
+ assertEquals(1, exit);
+ assertTrue(printer.getOutput().isEmpty(), "nothing should be rendered
when no process matches");
+ }
+
+ private static JsonObject singleThreadResponse() {
+ JsonObject thread = new JsonObject();
+ thread.put("id", 1);
+ thread.put("name", "Camel-thread-1");
+ thread.put("state", "RUNNABLE");
+ thread.put("waitedCount", 0);
+ thread.put("waitedTime", 0);
+ thread.put("blockedCount", 0);
+ thread.put("blockedTime", 0);
+ thread.put("stackTrace", new JsonArray());
+
+ JsonArray threads = new JsonArray();
+ threads.add(thread);
+
+ JsonObject response = new JsonObject();
+ response.put("threadCount", 1);
+ response.put("peakThreadCount", 2);
+ response.put("threads", threads);
+ return response;
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/RouteControllerActionTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/RouteControllerActionTest.java
new file mode 100644
index 000000000000..3e3f6b715e1e
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/RouteControllerActionTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.action;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@ExtendWith(MockitoExtension.class)
+class RouteControllerActionTest extends ActionCommandTestSupport {
+
+ @Test
+ void testRequestsRouteControllerAndRendersRoutes() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+
+ RouteControllerAction command = new RouteControllerAction(new
CamelJBangMain().withPrinter(printer));
+ command.name = "myApp";
+ // options are normally defaulted by picocli; set them as we construct
the command directly
+ command.sort = "id";
+ command.header = true;
+
+ int exit = callWithResponse(command, defaultControllerResponse());
+
+ assertEquals(0, exit);
+
+ JsonObject action = readActionFile(TEST_PID);
+ assertNotNull(action, "action file should be written for the matched
process");
+ assertEquals("route-controller", action.getString("action"));
+
+ String out = printer.getOutput();
+ assertTrue(out.contains("Default Route Controller"), "should print the
controller header, was: " + out);
+ assertTrue(out.contains("myRoute"), "should print the route id, was: "
+ out);
+ }
+
+ @Test
+ void testReturnsErrorWhenNameDoesNotMatch() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+
+ RouteControllerAction command = new RouteControllerAction(new
CamelJBangMain().withPrinter(printer));
+ command.name = "doesNotExist";
+
+ int exit = callWithSingleProcess(command);
+
+ assertEquals(1, exit);
+ assertTrue(printer.getOutput().isEmpty(), "nothing should be rendered
when no process matches");
+ }
+
+ private static JsonObject defaultControllerResponse() {
+ JsonObject route = new JsonObject();
+ route.put("routeId", "myRoute");
+ route.put("uri", "timer://foo");
+ route.put("status", "Started");
+ JsonArray routes = new JsonArray();
+ routes.add(route);
+
+ JsonObject response = new JsonObject();
+ response.put("controller", "DefaultRouteController");
+ response.put("startingRoutes", false);
+ response.put("totalRoutes", 1);
+ response.put("routes", routes);
+ return response;
+ }
+}