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 786282b0529c CAMEL-23688: Add unit tests for camel-jbang-core process/
commands
786282b0529c is described below
commit 786282b0529c8d6127aec5e5424cc99b2e6eda5c
Author: Adriano Machado <[email protected]>
AuthorDate: Tue Jun 9 07:32:03 2026 -0400
CAMEL-23688: Add unit tests for camel-jbang-core process/ commands
Add 13 new test classes covering the process/ package commands that
previously had no unit test coverage: CamelContextTop, CamelCount,
CamelProcessorStatus, CamelProcessorTop, CamelRouteGroupStatus,
CamelRouteGroupTop, CamelRouteTop, Dirty, ListGroovy, ListKafka,
ListPlatformHttp, RestartProcess, and StopProcess. Also improves
ListProcessTest.testSortByName to verify sort ordering is
insertion-order independent.
Closes #23869
---
.../core/commands/process/CamelContextTopTest.java | 186 ++++++++++++++++++++
.../core/commands/process/CamelCountTest.java | 176 +++++++++++++++++++
.../commands/process/CamelProcessorStatusTest.java | 167 ++++++++++++++++++
.../commands/process/CamelProcessorTopTest.java | 120 +++++++++++++
.../process/CamelRouteGroupStatusTest.java | 170 +++++++++++++++++++
.../commands/process/CamelRouteGroupTopTest.java | 126 ++++++++++++++
.../core/commands/process/CamelRouteTopTest.java | 137 +++++++++++++++
.../dsl/jbang/core/commands/process/DirtyTest.java | 109 ++++++++++++
.../core/commands/process/ListGroovyTest.java | 136 +++++++++++++++
.../jbang/core/commands/process/ListKafkaTest.java | 145 ++++++++++++++++
.../commands/process/ListPlatformHttpTest.java | 188 +++++++++++++++++++++
.../core/commands/process/ListProcessTest.java | 25 ++-
.../core/commands/process/RestartProcessTest.java | 75 ++++++++
.../core/commands/process/StopProcessTest.java | 102 +++++++++++
14 files changed, 1854 insertions(+), 8 deletions(-)
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelContextTopTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelContextTopTest.java
new file mode 100644
index 000000000000..d87e0c163ad1
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelContextTopTest.java
@@ -0,0 +1,186 @@
+/*
+ * 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.process;
+
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.StringPrinter;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class CamelContextTopTest extends ProcessCommandTestSupport {
+
+ @Test
+ void testDisplaysMemoryAndThreadFields() throws Exception {
+ JsonObject status = buildContextStatus("topApp", 5);
+ addMemoryAndThreads(status, 256_000_000L, 8, 10);
+ writeStatusFile(TEST_PID, status);
+
+ CamelContextTop command = new CamelContextTop(new
CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ String output = printer.getOutput();
+ assertTrue(output.contains("topApp"), "Should display context
name");
+ assertTrue(output.contains("HEAP"), "Should display HEAP column
header");
+ assertTrue(output.contains("THREADS"), "Should display THREADS
column header");
+ }
+ }
+
+ @Test
+ void testDisplaysThroughputAndLoad() throws Exception {
+ JsonObject status = buildContextStatus("loadApp", 5);
+ JsonObject ctx = (JsonObject) status.get("context");
+ JsonObject stats = (JsonObject) ctx.get("statistics");
+ stats.put("exchangesThroughput", "12.5");
+ stats.put("load01", "0.10");
+ stats.put("load05", "0.20");
+ stats.put("load15", "0.05");
+ writeStatusFile(TEST_PID, status);
+
+ CamelContextTop command = new CamelContextTop(new
CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ String output = printer.getOutput();
+ assertTrue(output.contains("loadApp"), "Should show context name");
+ // non-zero load values should appear as load1/load5/load15
+ assertTrue(output.contains("0.10"), "Should show load01 value");
+ }
+ }
+
+ @Test
+ void testSortByMemShowsHigherHeapFirst() throws Exception {
+ long highPid = TEST_PID;
+ long lowPid = TEST_PID + 1;
+
+ JsonObject highMem = buildContextStatus("highMem", 5);
+ addMemoryAndThreads(highMem, 500_000_000L, 8, 10);
+ writeStatusFile(highPid, highMem);
+
+ JsonObject lowMem = buildContextStatus("lowMem", 5);
+ addMemoryAndThreads(lowMem, 100_000_000L, 4, 5);
+ writeStatusFile(lowPid, lowMem);
+
+ // low process first — sort must promote high to top
+ assertHighBeforeLow(lowPid, highPid);
+ // high process first — sort must preserve the order
+ printer = new StringPrinter();
+ assertHighBeforeLow(highPid, lowPid);
+ }
+
+ private void assertHighBeforeLow(long firstPid, long secondPid) throws
Exception {
+ CamelContextTop command = new CamelContextTop(new
CamelJBangMain().withPrinter(printer));
+ command.sort = "mem";
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph1 = mockProcessHandle(firstPid);
+ ProcessHandle ph2 = mockProcessHandle(secondPid);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ // fresh stream per invocation so allProcesses() can be called
multiple times
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph1, ph2));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ String output = printer.getOutput();
+ int highIdx = output.indexOf("highMem");
+ int lowIdx = output.indexOf("lowMem");
+ assertTrue(highIdx < lowIdx, "Higher heap should appear first
regardless of stream order");
+ }
+ }
+
+ @Test
+ void testEmptyOutputWhenNoProcesses() throws Exception {
+ CamelContextTop command = new CamelContextTop(new
CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.empty());
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertEquals("", printer.getOutput().trim());
+ }
+ }
+
+ @Test
+ void testJsonOutput() throws Exception {
+ JsonObject status = buildContextStatus("jsonTopApp", 5);
+ addMemoryAndThreads(status, 128_000_000L, 4, 4);
+ writeStatusFile(TEST_PID, status);
+
+ CamelContextTop command = new CamelContextTop(new
CamelJBangMain().withPrinter(printer));
+ command.jsonOutput = true;
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ String output = printer.getOutput();
+ assertTrue(output.startsWith("["), "JSON output should be an
array");
+ assertTrue(output.contains("jsonTopApp"), "JSON should contain
context name");
+ assertTrue(output.contains("heap"), "JSON should contain heap
field");
+ }
+ }
+
+ private static void addMemoryAndThreads(JsonObject root, long heapUsed,
int threads, int peakThreads) {
+ JsonObject mem = new JsonObject();
+ mem.put("heapMemoryUsed", heapUsed);
+ mem.put("heapMemoryCommitted", heapUsed + 50_000_000L);
+ mem.put("heapMemoryMax", 1_000_000_000L);
+ mem.put("nonHeapMemoryUsed", 64_000_000L);
+ mem.put("nonHeapMemoryCommitted", 70_000_000L);
+ root.put("memory", mem);
+
+ JsonObject th = new JsonObject();
+ th.put("threadCount", threads);
+ th.put("peakThreadCount", peakThreads);
+ root.put("threads", th);
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelCountTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelCountTest.java
new file mode 100644
index 000000000000..9d4b9f3c98f4
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelCountTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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.process;
+
+import java.util.stream.Stream;
+
+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.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class CamelCountTest extends ProcessCommandTestSupport {
+
+ @Test
+ void testShowsTableWithTotalsAndFailed() throws Exception {
+ JsonObject status = buildContextStatus("countApp", 5);
+ JsonObject stats = (JsonObject) ((JsonObject)
status.get("context")).get("statistics");
+ stats.put("exchangesTotal", "42");
+ stats.put("exchangesFailed", "3");
+ writeStatusFile(TEST_PID, status);
+
+ CamelCount command = new CamelCount(new
CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ String output = printer.getOutput();
+ assertTrue(output.contains("countApp"), "Should show context
name");
+ assertTrue(output.contains("42"), "Should show total exchanges");
+ assertTrue(output.contains("3"), "Should show failed exchanges");
+ }
+ }
+
+ @Test
+ void testTotalFlagOutputsOnlyCount() throws Exception {
+ JsonObject status = buildContextStatus("totalApp", 5);
+ JsonObject stats = (JsonObject) ((JsonObject)
status.get("context")).get("statistics");
+ stats.put("exchangesTotal", "100");
+ stats.put("exchangesFailed", "5");
+ writeStatusFile(TEST_PID, status);
+
+ CamelCount command = new CamelCount(new
CamelJBangMain().withPrinter(printer));
+ command.total = true;
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertEquals("100", printer.getOutput().trim());
+ }
+ }
+
+ @Test
+ void testFailFlagOutputsOnlyFailedCount() throws Exception {
+ JsonObject status = buildContextStatus("failApp", 5);
+ JsonObject stats = (JsonObject) ((JsonObject)
status.get("context")).get("statistics");
+ stats.put("exchangesTotal", "50");
+ stats.put("exchangesFailed", "7");
+ writeStatusFile(TEST_PID, status);
+
+ CamelCount command = new CamelCount(new
CamelJBangMain().withPrinter(printer));
+ command.fail = true;
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertEquals("7", printer.getOutput().trim());
+ }
+ }
+
+ @Test
+ void testTotalAndFailFlagsOutputCommaSeparated() throws Exception {
+ JsonObject status = buildContextStatus("bothApp", 5);
+ JsonObject stats = (JsonObject) ((JsonObject)
status.get("context")).get("statistics");
+ stats.put("exchangesTotal", "20");
+ stats.put("exchangesFailed", "2");
+ writeStatusFile(TEST_PID, status);
+
+ CamelCount command = new CamelCount(new
CamelJBangMain().withPrinter(printer));
+ command.total = true;
+ command.fail = true;
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertEquals("20,2", printer.getOutput().trim());
+ }
+ }
+
+ @Test
+ void testEmptyOutputWhenNoProcesses() throws Exception {
+ CamelCount command = new CamelCount(new
CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.empty());
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertEquals("", printer.getOutput().trim());
+ }
+ }
+
+ @Test
+ void testJsonOutput() throws Exception {
+ JsonObject status = buildContextStatus("jsonCountApp", 5);
+ JsonObject stats = (JsonObject) ((JsonObject)
status.get("context")).get("statistics");
+ stats.put("exchangesTotal", "15");
+ stats.put("exchangesFailed", "1");
+ writeStatusFile(TEST_PID, status);
+
+ CamelCount command = new CamelCount(new
CamelJBangMain().withPrinter(printer));
+ command.jsonOutput = true;
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ String output = printer.getOutput();
+ assertTrue(output.startsWith("["), "JSON output should be an
array");
+ assertTrue(output.contains("jsonCountApp"), "JSON should contain
context name");
+ assertTrue(output.contains("total"), "JSON should contain total
field");
+ }
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelProcessorStatusTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelProcessorStatusTest.java
new file mode 100644
index 000000000000..af78243b22fd
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelProcessorStatusTest.java
@@ -0,0 +1,167 @@
+/*
+ * 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.process;
+
+import java.util.stream.Stream;
+
+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.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class CamelProcessorStatusTest extends ProcessCommandTestSupport {
+
+ @Test
+ void testDisplaysProcessorRow() throws Exception {
+ writeStatusFile(TEST_PID, buildStatusWithProcessor("myRoute", "log1",
"log:output", "Started", "25", "0", "0"));
+
+ CamelProcessorStatus command = new CamelProcessorStatus(new
CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ String output = printer.getOutput();
+ assertTrue(output.contains("myApp"), "Should show integration
name");
+ assertTrue(output.contains("myRoute"), "Should show route ID");
+ assertTrue(output.contains("25"), "Should show mean processing
time");
+ }
+ }
+
+ @Test
+ void testFilterMeanExcludesSlowProcessors() throws Exception {
+ // processor with mean=5 should be excluded when --filter-mean=10
+ writeStatusFile(TEST_PID, buildStatusWithProcessor("myRoute", "log1",
"log:output", "Started", "5", "0", "0"));
+
+ CamelProcessorStatus command = new CamelProcessorStatus(new
CamelJBangMain().withPrinter(printer));
+ command.mean = 10;
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertEquals("", printer.getOutput().trim(), "Processor below mean
threshold should be filtered out");
+ }
+ }
+
+ @Test
+ void testFilterMeanIncludesSlowEnoughProcessors() throws Exception {
+ writeStatusFile(TEST_PID, buildStatusWithProcessor("myRoute", "log1",
"log:output", "Started", "50", "0", "0"));
+
+ CamelProcessorStatus command = new CamelProcessorStatus(new
CamelJBangMain().withPrinter(printer));
+ command.mean = 10;
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertTrue(printer.getOutput().contains("50"), "Processor above
mean threshold should be visible");
+ }
+ }
+
+ @Test
+ void testEmptyOutputWhenNoProcesses() throws Exception {
+ CamelProcessorStatus command = new CamelProcessorStatus(new
CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.empty());
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertEquals("", printer.getOutput().trim());
+ }
+ }
+
+ @Test
+ void testJsonOutput() throws Exception {
+ writeStatusFile(TEST_PID, buildStatusWithProcessor("myRoute", "log1",
"log:output", "Started", "30", "2", "0"));
+
+ CamelProcessorStatus command = new CamelProcessorStatus(new
CamelJBangMain().withPrinter(printer));
+ command.jsonOutput = true;
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ String output = printer.getOutput();
+ assertTrue(output.startsWith("["), "JSON output should be an
array");
+ assertTrue(output.contains("myRoute"), "JSON should contain route
ID");
+ }
+ }
+
+ private static JsonObject buildStatusWithProcessor(
+ String routeId, String processorId, String from,
+ String state, String mean, String failed, String inflight) {
+ JsonObject stats = new JsonObject();
+ stats.put("exchangesTotal", "10");
+ stats.put("exchangesFailed", failed);
+ stats.put("exchangesInflight", inflight);
+ stats.put("meanProcessingTime", mean);
+ stats.put("maxProcessingTime", mean);
+ stats.put("minProcessingTime", mean);
+
+ JsonObject route = new JsonObject();
+ route.put("routeId", routeId);
+ route.put("processorId", processorId);
+ route.put("from", from);
+ route.put("state", state);
+ route.put("statistics", stats);
+
+ JsonArray routes = new JsonArray();
+ routes.add(route);
+
+ JsonObject context = new JsonObject();
+ context.put("name", "myApp");
+ context.put("phase", 5);
+
+ JsonObject root = new JsonObject();
+ root.put("context", context);
+ root.put("routes", routes);
+ return root;
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelProcessorTopTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelProcessorTopTest.java
new file mode 100644
index 000000000000..d6ec6860af70
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelProcessorTopTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.process;
+
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.StringPrinter;
+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.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class CamelProcessorTopTest extends ProcessCommandTestSupport {
+
+ @Test
+ void testSortsByMeanDescendingSlowerProcessorFirst() throws Exception {
+ long slowPid = TEST_PID;
+ long fastPid = TEST_PID + 1;
+ // same context name so primary sort (name) ties and mean tiebreaker
fires
+ writeStatusFile(slowPid, buildStatusWithOneProcessor("myApp",
"slowRoute", "200"));
+ writeStatusFile(fastPid, buildStatusWithOneProcessor("myApp",
"fastRoute", "10"));
+
+ // fast process first in stream — sort must correct the order
+ assertSlowBeforeFast(fastPid, slowPid);
+ // slow process first in stream — sort must preserve the order
+ printer = new StringPrinter();
+ assertSlowBeforeFast(slowPid, fastPid);
+ }
+
+ private void assertSlowBeforeFast(long firstPid, long secondPid) throws
Exception {
+ CamelProcessorTop command = new CamelProcessorTop(new
CamelJBangMain().withPrinter(printer));
+ command.sort = "name";
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph1 = mockProcessHandle(firstPid);
+ ProcessHandle ph2 = mockProcessHandle(secondPid);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ // fresh stream per invocation so allProcesses() can be called
multiple times
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph1, ph2));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ String output = printer.getOutput();
+ int slowIdx = output.indexOf("slowRoute");
+ int fastIdx = output.indexOf("fastRoute");
+ assertTrue(slowIdx < fastIdx, "Slower processor (higher mean)
should appear first regardless of stream order");
+ }
+ }
+
+ @Test
+ void testEmptyOutputWhenNoProcesses() throws Exception {
+ CamelProcessorTop command = new CamelProcessorTop(new
CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.empty());
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertEquals("", printer.getOutput().trim());
+ }
+ }
+
+ private static JsonObject buildStatusWithOneProcessor(String contextName,
String routeId, String mean) {
+ JsonArray routes = new JsonArray();
+ routes.add(processorEntry(routeId, mean));
+
+ JsonObject context = new JsonObject();
+ context.put("name", contextName);
+ context.put("phase", 5);
+
+ JsonObject root = new JsonObject();
+ root.put("context", context);
+ root.put("routes", routes);
+ return root;
+ }
+
+ private static JsonObject processorEntry(String routeId, String mean) {
+ JsonObject stats = new JsonObject();
+ stats.put("exchangesTotal", "10");
+ stats.put("exchangesFailed", "0");
+ stats.put("exchangesInflight", "0");
+ stats.put("meanProcessingTime", mean);
+ stats.put("maxProcessingTime", mean);
+ stats.put("minProcessingTime", mean);
+
+ JsonObject route = new JsonObject();
+ route.put("routeId", routeId);
+ route.put("from", "timer:tick");
+ route.put("state", "Started");
+ route.put("statistics", stats);
+ return route;
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteGroupStatusTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteGroupStatusTest.java
new file mode 100644
index 000000000000..f1d287dec6f3
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteGroupStatusTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.process;
+
+import java.util.stream.Stream;
+
+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.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class CamelRouteGroupStatusTest extends ProcessCommandTestSupport {
+
+ @Test
+ void testDisplaysRouteGroup() throws Exception {
+ writeStatusFile(TEST_PID, buildStatusWithGroup("orderGroup",
"Started", "10", "0"));
+
+ CamelRouteGroupStatus command = new CamelRouteGroupStatus(new
CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ String output = printer.getOutput();
+ assertTrue(output.contains("myApp"), "Should show integration
name");
+ assertTrue(output.contains("orderGroup"), "Should show group
name");
+ assertTrue(output.contains("Started"), "Should show group state");
+ }
+ }
+
+ @Test
+ void testRunningFilterExcludesSuspendedGroups() throws Exception {
+ writeStatusFile(TEST_PID, buildStatusWithGroup("suspendedGroup",
"Suspended", "0", "0"));
+
+ CamelRouteGroupStatus command = new CamelRouteGroupStatus(new
CamelJBangMain().withPrinter(printer));
+ command.running = true;
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertEquals("", printer.getOutput().trim(), "Suspended group
should be hidden when --running is set");
+ }
+ }
+
+ @Test
+ void testFilterMeanExcludesFastGroups() throws Exception {
+ // group mean=5 should be filtered out when --filter-mean=20
+ writeStatusFile(TEST_PID, buildStatusWithGroup("fastGroup", "Started",
"5", "0"));
+
+ CamelRouteGroupStatus command = new CamelRouteGroupStatus(new
CamelJBangMain().withPrinter(printer));
+ command.mean = 20;
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertEquals("", printer.getOutput().trim(), "Group below mean
threshold should be filtered out");
+ }
+ }
+
+ @Test
+ void testEmptyOutputWhenNoProcesses() throws Exception {
+ CamelRouteGroupStatus command = new CamelRouteGroupStatus(new
CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.empty());
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertEquals("", printer.getOutput().trim());
+ }
+ }
+
+ @Test
+ void testJsonOutput() throws Exception {
+ writeStatusFile(TEST_PID, buildStatusWithGroup("shippingGroup",
"Started", "30", "1"));
+
+ CamelRouteGroupStatus command = new CamelRouteGroupStatus(new
CamelJBangMain().withPrinter(printer));
+ command.jsonOutput = true;
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ String output = printer.getOutput();
+ assertTrue(output.startsWith("["), "JSON output should be an
array");
+ assertTrue(output.contains("shippingGroup"), "JSON should contain
group name");
+ }
+ }
+
+ private static JsonObject buildStatusWithGroup(String groupName, String
state, String mean, String failed) {
+ JsonObject stats = new JsonObject();
+ stats.put("exchangesTotal", "10");
+ stats.put("exchangesFailed", failed);
+ stats.put("exchangesInflight", "0");
+ stats.put("meanProcessingTime", mean);
+ stats.put("maxProcessingTime", mean);
+ stats.put("minProcessingTime", mean);
+
+ JsonArray routeIds = new JsonArray();
+ routeIds.add("route1");
+
+ JsonObject group = new JsonObject();
+ group.put("group", groupName);
+ group.put("size", 1);
+ group.put("routeIds", routeIds);
+ group.put("state", state);
+ group.put("uptime", "1m0s");
+ group.put("statistics", stats);
+
+ JsonArray groups = new JsonArray();
+ groups.add(group);
+
+ JsonObject context = new JsonObject();
+ context.put("name", "myApp");
+ context.put("phase", 5);
+
+ JsonObject root = new JsonObject();
+ root.put("context", context);
+ root.put("routeGroups", groups);
+ root.put("routes", new JsonArray());
+ return root;
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteGroupTopTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteGroupTopTest.java
new file mode 100644
index 000000000000..a17b465b3981
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteGroupTopTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.process;
+
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.StringPrinter;
+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.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class CamelRouteGroupTopTest extends ProcessCommandTestSupport {
+
+ @Test
+ void testSortsByMeanDescendingSlowerGroupFirst() throws Exception {
+ long slowPid = TEST_PID;
+ long fastPid = TEST_PID + 1;
+ // same context name so primary sort (name) ties and mean tiebreaker
fires
+ writeStatusFile(slowPid, buildStatusWithOneGroup("myApp", "slowGroup",
"150"));
+ writeStatusFile(fastPid, buildStatusWithOneGroup("myApp", "fastGroup",
"5"));
+
+ // fast process first in stream — sort must correct the order
+ assertSlowBeforeFast(fastPid, slowPid);
+ // slow process first in stream — sort must preserve the order
+ printer = new StringPrinter();
+ assertSlowBeforeFast(slowPid, fastPid);
+ }
+
+ private void assertSlowBeforeFast(long firstPid, long secondPid) throws
Exception {
+ CamelRouteGroupTop command = new CamelRouteGroupTop(new
CamelJBangMain().withPrinter(printer));
+ command.sort = "name";
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph1 = mockProcessHandle(firstPid);
+ ProcessHandle ph2 = mockProcessHandle(secondPid);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ // fresh stream per invocation so allProcesses() can be called
multiple times
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph1, ph2));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ String output = printer.getOutput();
+ int slowIdx = output.indexOf("slowGroup");
+ int fastIdx = output.indexOf("fastGroup");
+ assertTrue(slowIdx < fastIdx, "Slower group (higher mean) should
appear first regardless of stream order");
+ }
+ }
+
+ @Test
+ void testEmptyOutputWhenNoProcesses() throws Exception {
+ CamelRouteGroupTop command = new CamelRouteGroupTop(new
CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.empty());
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertEquals("", printer.getOutput().trim());
+ }
+ }
+
+ private static JsonObject buildStatusWithOneGroup(String contextName,
String groupName, String mean) {
+ JsonArray groups = new JsonArray();
+ groups.add(groupEntry(groupName, mean));
+
+ JsonObject context = new JsonObject();
+ context.put("name", contextName);
+ context.put("phase", 5);
+
+ JsonObject root = new JsonObject();
+ root.put("context", context);
+ root.put("routeGroups", groups);
+ root.put("routes", new JsonArray());
+ return root;
+ }
+
+ private static JsonObject groupEntry(String groupName, String mean) {
+ JsonObject stats = new JsonObject();
+ stats.put("exchangesTotal", "10");
+ stats.put("exchangesFailed", "0");
+ stats.put("exchangesInflight", "0");
+ stats.put("meanProcessingTime", mean);
+ stats.put("maxProcessingTime", mean);
+ stats.put("minProcessingTime", mean);
+
+ JsonArray routeIds = new JsonArray();
+ routeIds.add("route1");
+
+ JsonObject group = new JsonObject();
+ group.put("group", groupName);
+ group.put("size", 1);
+ group.put("routeIds", routeIds);
+ group.put("state", "Started");
+ group.put("uptime", "1m0s");
+ group.put("statistics", stats);
+ return group;
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteTopTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteTopTest.java
new file mode 100644
index 000000000000..4860ac697953
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteTopTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.process;
+
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.StringPrinter;
+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.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class CamelRouteTopTest extends ProcessCommandTestSupport {
+
+ @Test
+ void testDisplaysRouteRow() throws Exception {
+ writeStatusFile(TEST_PID, buildRouteStatus("myRoute", "Started"));
+
+ CamelRouteTop command = new CamelRouteTop(new
CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertTrue(printer.getOutput().contains("myRoute"), "Should
display the route ID");
+ }
+ }
+
+ @Test
+ void testSortsByMeanDescendingSlowerRouteFirst() throws Exception {
+ long slowPid = TEST_PID;
+ long fastPid = TEST_PID + 1;
+ // same context name so primary sort (name) ties and mean tiebreaker
fires
+ writeStatusFile(slowPid, buildStatusWithOneRoute("myApp", "slowRoute",
"300"));
+ writeStatusFile(fastPid, buildStatusWithOneRoute("myApp", "fastRoute",
"8"));
+
+ // fast process first in stream — sort must correct the order
+ assertSlowBeforeFast(fastPid, slowPid);
+ // slow process first in stream — sort must preserve the order
+ printer = new StringPrinter();
+ assertSlowBeforeFast(slowPid, fastPid);
+ }
+
+ private void assertSlowBeforeFast(long firstPid, long secondPid) throws
Exception {
+ CamelRouteTop command = new CamelRouteTop(new
CamelJBangMain().withPrinter(printer));
+ command.sort = "name";
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph1 = mockProcessHandle(firstPid);
+ ProcessHandle ph2 = mockProcessHandle(secondPid);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ // fresh stream per invocation so allProcesses() can be called
multiple times
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph1, ph2));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ String output = printer.getOutput();
+ int slowIdx = output.indexOf("slowRoute");
+ int fastIdx = output.indexOf("fastRoute");
+ assertTrue(slowIdx < fastIdx, "Slower route (higher mean) should
appear first regardless of stream order");
+ }
+ }
+
+ @Test
+ void testEmptyOutputWhenNoProcesses() throws Exception {
+ CamelRouteTop command = new CamelRouteTop(new
CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.empty());
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertEquals("", printer.getOutput().trim());
+ }
+ }
+
+ private static JsonObject buildStatusWithOneRoute(String contextName,
String routeId, String mean) {
+ JsonObject stats = new JsonObject();
+ stats.put("exchangesTotal", "10");
+ stats.put("exchangesFailed", "0");
+ stats.put("exchangesInflight", "0");
+ stats.put("meanProcessingTime", mean);
+ stats.put("maxProcessingTime", mean);
+ stats.put("minProcessingTime", mean);
+
+ JsonObject route = new JsonObject();
+ route.put("routeId", routeId);
+ route.put("from", "timer:tick");
+ route.put("state", "Started");
+ route.put("uptime", "1m");
+ route.put("statistics", stats);
+
+ JsonArray routes = new JsonArray();
+ routes.add(route);
+
+ JsonObject context = new JsonObject();
+ context.put("name", contextName);
+ context.put("phase", 5);
+
+ JsonObject root = new JsonObject();
+ root.put("context", context);
+ root.put("routes", routes);
+ return root;
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/DirtyTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/DirtyTest.java
new file mode 100644
index 000000000000..38f962e1f432
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/DirtyTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.process;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class DirtyTest extends ProcessCommandTestSupport {
+
+ @Test
+ void testReturnsZeroWhenNoFiles() throws Exception {
+ // camel dir is empty: Dirty returns before touching ProcessHandle
+ Dirty command = new Dirty(new CamelJBangMain().withPrinter(printer));
+ int exit = command.doCall();
+ assertEquals(0, exit);
+ assertEquals("", printer.getOutput().trim());
+ }
+
+ @Test
+ void testDetectsOrphanFile() throws Exception {
+ // write a status file for a PID that has no running process
+ Path orphan =
CommandLineHelper.getCamelDir().resolve("77777-status.json");
+ Files.writeString(orphan, "{}");
+
+ Dirty command = new Dirty(new CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ // no running processes -> orphan file is not matched
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.empty());
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertTrue(printer.getOutput().contains("orphan"), "Should report
orphan file count");
+ }
+ }
+
+ @Test
+ void testRunningProcessFileIsNotDirty() throws Exception {
+ // status file for TEST_PID is owned by a running process -> not dirty
+ writeStatusFile(TEST_PID, buildContextStatus("liveApp", 5));
+
+ Dirty command = new Dirty(new CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertEquals("", printer.getOutput().trim(), "File belonging to a
running PID is not dirty");
+ }
+ }
+
+ @Test
+ void testCleanFlagDeletesOrphanFile() throws Exception {
+ Path orphan =
CommandLineHelper.getCamelDir().resolve("88888-status.json");
+ Files.writeString(orphan, "{}");
+ assertTrue(Files.exists(orphan), "Orphan file must exist before
cleaning");
+
+ Dirty command = new Dirty(new CamelJBangMain().withPrinter(printer));
+ command.clean = true;
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.empty());
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertFalse(Files.exists(orphan), "Orphan file should be deleted
by --clean");
+ assertTrue(printer.getOutput().contains("Cleaned"), "Should print
cleaned confirmation");
+ }
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListGroovyTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListGroovyTest.java
new file mode 100644
index 000000000000..c774cda08aef
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListGroovyTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.process;
+
+import java.util.stream.Stream;
+
+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.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class ListGroovyTest extends ProcessCommandTestSupport {
+
+ @Test
+ void testDisplaysGroovyCompilerStats() throws Exception {
+ writeStatusFile(TEST_PID, buildStatusWithGroovy(3, 1, "MyRoute",
"MyBean"));
+
+ ListGroovy command = new ListGroovy(new
CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ String output = printer.getOutput();
+ assertTrue(output.contains("myApp"), "Should show integration
name");
+ assertTrue(output.contains("MyRoute"), "Should show compiled class
names");
+ }
+ }
+
+ @Test
+ void testEmptyOutputWhenNoGroovySection() throws Exception {
+ JsonObject status = buildContextStatus("noGroovyApp", 5);
+ writeStatusFile(TEST_PID, status);
+
+ ListGroovy command = new ListGroovy(new
CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertEquals("", printer.getOutput().trim(), "No groovy section
means no output rows");
+ }
+ }
+
+ @Test
+ void testEmptyOutputWhenNoProcesses() throws Exception {
+ ListGroovy command = new ListGroovy(new
CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.empty());
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertEquals("", printer.getOutput().trim());
+ }
+ }
+
+ @Test
+ void testJsonOutput() throws Exception {
+ writeStatusFile(TEST_PID, buildStatusWithGroovy(2, 0, "MyProcessor"));
+
+ ListGroovy command = new ListGroovy(new
CamelJBangMain().withPrinter(printer));
+ command.jsonOutput = true;
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ String output = printer.getOutput();
+ assertTrue(output.startsWith("["), "JSON output should be an
array");
+ assertTrue(output.contains("compile"), "JSON should contain
compile field");
+ }
+ }
+
+ private static JsonObject buildStatusWithGroovy(int compiled, int
preloaded, String... classNames) {
+ JsonArray classes = new JsonArray();
+ for (String name : classNames) {
+ classes.add(name);
+ }
+
+ JsonObject compiler = new JsonObject();
+ compiler.put("compiledCounter", compiled);
+ compiler.put("preloadedCounter", preloaded);
+ compiler.put("classesSize", classNames.length);
+ compiler.put("compiledTime", 1500L);
+ compiler.put("lastCompilationTimestamp", 0L);
+ compiler.put("classes", classes);
+
+ JsonObject groovy = new JsonObject();
+ groovy.put("compiler", compiler);
+
+ JsonObject root = buildContextStatus("myApp", 5);
+ root.put("groovy", groovy);
+ return root;
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListKafkaTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListKafkaTest.java
new file mode 100644
index 000000000000..4033000a9f33
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListKafkaTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.process;
+
+import java.util.stream.Stream;
+
+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.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class ListKafkaTest extends ProcessCommandTestSupport {
+
+ @Test
+ void testDisplaysKafkaConsumer() throws Exception {
+ writeStatusFile(TEST_PID, buildStatusWithKafkaConsumer("myRoute",
"kafka:orders", "my-group", "orders", 0, 42L));
+
+ ListKafka command = new ListKafka(new
CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ String output = printer.getOutput();
+ assertTrue(output.contains("myApp"), "Should show integration
name");
+ assertTrue(output.contains("orders"), "Should show topic name");
+ assertTrue(output.contains("my-group"), "Should show consumer
group");
+ }
+ }
+
+ @Test
+ void testEmptyOutputWhenNoKafkaSection() throws Exception {
+ JsonObject status = buildContextStatus("noKafkaApp", 5);
+ writeStatusFile(TEST_PID, status);
+
+ ListKafka command = new ListKafka(new
CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertEquals("", printer.getOutput().trim(), "No kafka section
means no output rows");
+ }
+ }
+
+ @Test
+ void testEmptyOutputWhenNoProcesses() throws Exception {
+ ListKafka command = new ListKafka(new
CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.empty());
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertEquals("", printer.getOutput().trim());
+ }
+ }
+
+ @Test
+ void testJsonOutput() throws Exception {
+ writeStatusFile(TEST_PID, buildStatusWithKafkaConsumer("myRoute",
"kafka:events", "event-group", "events", 1, 7L));
+
+ ListKafka command = new ListKafka(new
CamelJBangMain().withPrinter(printer));
+ command.jsonOutput = true;
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ String output = printer.getOutput();
+ assertTrue(output.startsWith("["), "JSON output should be an
array");
+ assertTrue(output.contains("events"), "JSON should contain topic
name");
+ }
+ }
+
+ private static JsonObject buildStatusWithKafkaConsumer(
+ String routeId, String uri, String groupId, String topic, int
partition, long offset) {
+ JsonObject worker = new JsonObject();
+ worker.put("threadId", "thread-1");
+ worker.put("state", "Running");
+ worker.put("groupId", groupId);
+ worker.put("lastTopic", topic);
+ worker.put("lastPartition", partition);
+ worker.put("lastOffset", offset);
+
+ JsonArray workers = new JsonArray();
+ workers.add(worker);
+
+ JsonObject consumer = new JsonObject();
+ consumer.put("routeId", routeId);
+ consumer.put("uri", uri);
+ consumer.put("state", "Running");
+ consumer.put("workers", workers);
+
+ JsonArray consumers = new JsonArray();
+ consumers.add(consumer);
+
+ JsonObject kafka = new JsonObject();
+ kafka.put("kafkaConsumers", consumers);
+
+ JsonObject root = buildContextStatus("myApp", 5);
+ root.put("kafka", kafka);
+ return root;
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListPlatformHttpTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListPlatformHttpTest.java
new file mode 100644
index 000000000000..08f84bbdb5ff
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListPlatformHttpTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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.process;
+
+import java.util.stream.Stream;
+
+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.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class ListPlatformHttpTest extends ProcessCommandTestSupport {
+
+ @Test
+ void testDisplaysEndpoint() throws Exception {
+ writeStatusFile(TEST_PID, buildStatusWithHttp("/api/orders", "GET",
"/q/health", "GET"));
+
+ ListPlatformHttp command = new ListPlatformHttp(new
CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ String output = printer.getOutput();
+ assertTrue(output.contains("/api/orders"), "Should show user
endpoint path");
+ assertTrue(output.contains("GET"), "Should show HTTP verb");
+ }
+ }
+
+ @Test
+ void testManagementEndpointsHiddenByDefault() throws Exception {
+ writeStatusFile(TEST_PID, buildStatusWithHttp("/api/orders", "GET",
"/q/health", "GET"));
+
+ ListPlatformHttp command = new ListPlatformHttp(new
CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertFalse(printer.getOutput().contains("/q/health"),
+ "Management endpoint should be hidden without --all");
+ }
+ }
+
+ @Test
+ void testAllFlagIncludesManagementEndpoints() throws Exception {
+ writeStatusFile(TEST_PID, buildStatusWithHttp("/api/orders", "POST",
"/q/health", "GET"));
+
+ ListPlatformHttp command = new ListPlatformHttp(new
CamelJBangMain().withPrinter(printer));
+ command.sort = "pid";
+ command.all = true;
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ String output = printer.getOutput();
+ assertTrue(output.contains("/api/orders"), "Should show user
endpoint");
+ assertTrue(output.contains("/q/health"), "Should show management
endpoint when --all");
+ }
+ }
+
+ @Test
+ void testEmptyOutputWhenNoPlatformHttpSection() throws Exception {
+ JsonObject status = buildContextStatus("noHttpApp", 5);
+ writeStatusFile(TEST_PID, status);
+
+ ListPlatformHttp command = new ListPlatformHttp(new
CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertEquals("", printer.getOutput().trim(), "No platform-http
section means no output rows");
+ }
+ }
+
+ @Test
+ void testEmptyOutputWhenNoProcesses() throws Exception {
+ ListPlatformHttp command = new ListPlatformHttp(new
CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.empty());
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertEquals("", printer.getOutput().trim());
+ }
+ }
+
+ @Test
+ void testJsonOutput() throws Exception {
+ writeStatusFile(TEST_PID, buildStatusWithHttp("/api/ping", "GET",
"/q/health", "GET"));
+
+ ListPlatformHttp command = new ListPlatformHttp(new
CamelJBangMain().withPrinter(printer));
+ command.jsonOutput = true;
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ String output = printer.getOutput();
+ assertTrue(output.startsWith("["), "JSON output should be an
array");
+ // Jsoner escapes forward slashes, so assert on the slug without
them
+ assertTrue(output.contains("api") && output.contains("ping"),
"JSON should contain endpoint path segments");
+ assertTrue(output.contains("method"), "JSON should contain method
field");
+ }
+ }
+
+ private static JsonObject buildStatusWithHttp(
+ String userPath, String userVerb, String mgmtPath, String
mgmtVerb) {
+ JsonObject userEndpoint = new JsonObject();
+ userEndpoint.put("url", "http://localhost:8080" + userPath);
+ userEndpoint.put("path", userPath);
+ userEndpoint.put("verbs", userVerb);
+
+ JsonArray endpoints = new JsonArray();
+ endpoints.add(userEndpoint);
+
+ JsonObject mgmtEndpoint = new JsonObject();
+ mgmtEndpoint.put("url", "http://localhost:8080" + mgmtPath);
+ mgmtEndpoint.put("path", mgmtPath);
+ mgmtEndpoint.put("verbs", mgmtVerb);
+
+ JsonArray mgmtEndpoints = new JsonArray();
+ mgmtEndpoints.add(mgmtEndpoint);
+
+ JsonObject platformHttp = new JsonObject();
+ platformHttp.put("server", "netty");
+ platformHttp.put("endpoints", endpoints);
+ platformHttp.put("managementEndpoints", mgmtEndpoints);
+
+ JsonObject root = buildContextStatus("myApp", 5);
+ root.put("platform-http", platformHttp);
+ return root;
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListProcessTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListProcessTest.java
index 4c277d82bee5..b1a4681c67a6 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListProcessTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListProcessTest.java
@@ -20,6 +20,7 @@ import java.util.List;
import java.util.stream.Stream;
import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.StringPrinter;
import org.apache.camel.util.json.JsonObject;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -170,32 +171,40 @@ class ListProcessTest extends ProcessCommandTestSupport {
@Test
void testSortByName() throws Exception {
- long pid1 = 11111L;
- long pid2 = 22222L;
+ long zebraPid = 11111L;
+ long applePid = 22222L;
JsonObject status1 = buildContextStatus("zebra", 5);
JsonObject status2 = buildContextStatus("apple", 5);
- writeStatusFile(pid1, status1);
- writeStatusFile(pid2, status2);
+ writeStatusFile(zebraPid, status1);
+ writeStatusFile(applePid, status2);
+
+ // zebra process first in stream — sort must promote apple to top
+ assertAppleBeforeZebra(zebraPid, applePid);
+ // apple process first in stream — sort must preserve the order
+ printer = new StringPrinter();
+ assertAppleBeforeZebra(applePid, zebraPid);
+ }
+ private void assertAppleBeforeZebra(long firstPid, long secondPid) throws
Exception {
ListProcess command = new ListProcess(new
CamelJBangMain().withPrinter(printer));
command.sort = "name";
try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
- ProcessHandle ph1 = mockProcessHandle(pid1);
- ProcessHandle ph2 = mockProcessHandle(pid2);
+ ProcessHandle ph1 = mockProcessHandle(firstPid);
+ ProcessHandle ph2 = mockProcessHandle(secondPid);
ProcessHandle currentHandle = mockCurrentHandle();
mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+ // fresh stream per invocation so allProcesses() can be called
multiple times
mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph1, ph2));
int exit = command.doCall();
assertEquals(0, exit);
String output = printer.getOutput();
- // apple should appear before zebra when sorted ascending by name
int applePos = output.indexOf("apple");
int zebraPos = output.indexOf("zebra");
- assertTrue(applePos < zebraPos, "Sort by name ascending: apple
must precede zebra");
+ assertTrue(applePos < zebraPos, "Sort by name ascending: apple
must precede zebra regardless of stream order");
}
}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/RestartProcessTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/RestartProcessTest.java
new file mode 100644
index 000000000000..c8aa610f2f94
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/RestartProcessTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.process;
+
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+// TODO: add tests for the successful restart path once
ProcessBuilder/Runtime.exec can be mocked
+// or an integration-test harness is available. The current tests only
cover early-exit guards.
+@ExtendWith(MockitoExtension.class)
+class RestartProcessTest extends ProcessCommandTestSupport {
+
+ @Test
+ void testReturnsErrorWhenNoMatchingProcess() throws Exception {
+ RestartProcess command = new RestartProcess(new
CamelJBangMain().withPrinter(printer));
+ command.name = "nonExistentApp";
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.empty());
+
+ int exit = command.doCall();
+
+ assertEquals(1, exit, "Should return non-zero when no process
matches");
+ assertTrue(printer.getOutput().contains("No matching"), "Should
print no-match message");
+ }
+ }
+
+ @Test
+ void testReturnsErrorWhenMultipleProcessesMatch() throws Exception {
+ long pid2 = TEST_PID + 1;
+ writeStatusFile(TEST_PID, buildContextStatus("myApp", 5));
+ writeStatusFile(pid2, buildContextStatus("myApp", 5));
+
+ RestartProcess command = new RestartProcess(new
CamelJBangMain().withPrinter(printer));
+ command.name = "myApp";
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph1 = mockProcessHandle(TEST_PID);
+ ProcessHandle ph2 = mockProcessHandle(pid2);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph1, ph2));
+
+ int exit = command.doCall();
+
+ assertEquals(1, exit, "Should return non-zero when multiple
processes match");
+ assertTrue(printer.getOutput().contains("Multiple"), "Should print
ambiguity message");
+ }
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/StopProcessTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/StopProcessTest.java
new file mode 100644
index 000000000000..db63aecea085
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/StopProcessTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.process;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+class StopProcessTest extends ProcessCommandTestSupport {
+
+ @Test
+ void testStopDeletesPidFile() throws Exception {
+ writeStatusFile(TEST_PID, buildContextStatus("myApp", 5));
+ // the pid file (named by PID only) signals graceful shutdown when
deleted
+ Path pidFile =
CommandLineHelper.getCamelDir().resolve(Long.toString(TEST_PID));
+ Files.writeString(pidFile, "");
+ assertTrue(Files.exists(pidFile), "Pid file must exist before stop");
+
+ StopProcess command = new StopProcess(new
CamelJBangMain().withPrinter(printer));
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertFalse(Files.exists(pidFile), "Pid file should be deleted to
signal graceful stop");
+ assertTrue(printer.getOutput().contains("Shutting down"), "Should
print shutdown message");
+ }
+ }
+
+ @Test
+ void testStopWithKillCallsDestroyForcibly() throws Exception {
+ writeStatusFile(TEST_PID, buildContextStatus("myApp", 5));
+
+ StopProcess command = new StopProcess(new
CamelJBangMain().withPrinter(printer));
+ command.kill = true;
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle ph = mockProcessHandle(TEST_PID);
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.of(ph));
+ mocked.when(() ->
ProcessHandle.of(TEST_PID)).thenReturn(Optional.of(ph));
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ verify(ph).destroyForcibly();
+ assertTrue(printer.getOutput().contains("Killing"), "Should print
kill message");
+ }
+ }
+
+ @Test
+ void testStopWithNoMatchingProcessDoesNothing() throws Exception {
+ StopProcess command = new StopProcess(new
CamelJBangMain().withPrinter(printer));
+ command.name = "nonExistentApp";
+
+ try (MockedStatic<ProcessHandle> mocked =
mockStatic(ProcessHandle.class)) {
+ ProcessHandle current = mockCurrentHandle();
+ mocked.when(ProcessHandle::current).thenReturn(current);
+ mocked.when(ProcessHandle::allProcesses).thenAnswer(inv ->
Stream.empty());
+
+ int exit = command.doCall();
+
+ assertEquals(0, exit);
+ assertEquals("", printer.getOutput().trim(), "No output expected
when no processes match");
+ }
+ }
+}