From 2abe6bebe44c225fc52942cee3d050a2972ba479 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sat, 6 Jun 2026 20:19:57 -0400 Subject: [PATCH 01/10] CAMEL-23688: Add CamelCommandHelperTest for phase-to-status mapping Tests that all known phase integer values map to the expected human-readable status strings (Starting, Running, Suspending, Suspended, Terminating, Terminated) so regressions in the mapping are caught immediately. Co-Authored-By: Claude Sonnet 4.6 --- .../core/commands/CamelCommandHelperTest.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/CamelCommandHelperTest.java diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/CamelCommandHelperTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/CamelCommandHelperTest.java new file mode 100644 index 0000000000000..0ce42940e1366 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/CamelCommandHelperTest.java @@ -0,0 +1,65 @@ +/* + * 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; + +import org.apache.camel.dsl.jbang.core.common.CamelCommandHelper; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class CamelCommandHelperTest { + + @Test + void testStartingState() { + // Phase <= 4 maps to Starting + assertEquals("Starting", CamelCommandHelper.extractState(0)); + assertEquals("Starting", CamelCommandHelper.extractState(1)); + assertEquals("Starting", CamelCommandHelper.extractState(4)); + } + + @Test + void testRunningState() { + assertEquals("Running", CamelCommandHelper.extractState(5)); + } + + @Test + void testSuspendingState() { + assertEquals("Suspending", CamelCommandHelper.extractState(6)); + } + + @Test + void testSuspendedState() { + assertEquals("Suspended", CamelCommandHelper.extractState(7)); + } + + @Test + void testTerminatingState() { + assertEquals("Terminating", CamelCommandHelper.extractState(8)); + } + + @Test + void testTerminatedState() { + assertEquals("Terminated", CamelCommandHelper.extractState(9)); + } + + @Test + void testUnknownPhaseFallsToTerminated() { + // Any phase beyond 9 defaults to Terminated — prevents NPE during display + assertEquals("Terminated", CamelCommandHelper.extractState(99)); + assertEquals("Terminated", CamelCommandHelper.extractState(Integer.MAX_VALUE)); + } +} From abd9e77359fa207bb0b4f61905e4504cff6c1d2a Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sat, 6 Jun 2026 20:20:10 -0400 Subject: [PATCH 02/10] CAMEL-23688: Add action command tests CamelStartupRecorderActionTest: Tests rendering of startup steps using an inner test subclass that overrides findPids() and waitForOutputFile() to bypass process discovery. Covers display output, name parentheses, sort by duration (ascending and descending), and empty-pid handling. LoggerActionTest: Tests logger list display and sort-by-name behavior using two processes with distinct context names to verify row ordering. MessageTableHelperTest: Unit tests for MessageTableHelper covering body display, header visibility toggle, null root handling, and exchange ID. CamelSourceActionTest: Unit tests for the public static extractSourceName() method covering null input, plain filenames, scheme stripping, path stripping, and multi-colon (scheme + line number) handling. Co-Authored-By: Claude Sonnet 4.6 --- .../action/CamelSourceActionTest.java | 60 ++++++ .../CamelStartupRecorderActionTest.java | 195 +++++++++++++++++ .../commands/action/LoggerActionTest.java | 197 ++++++++++++++++++ .../action/MessageTableHelperTest.java | 116 +++++++++++ 4 files changed, 568 insertions(+) create mode 100644 dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSourceActionTest.java create mode 100644 dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelStartupRecorderActionTest.java create mode 100644 dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/LoggerActionTest.java create mode 100644 dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/MessageTableHelperTest.java diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSourceActionTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSourceActionTest.java new file mode 100644 index 0000000000000..cdfdbf13c2625 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSourceActionTest.java @@ -0,0 +1,60 @@ +/* + * 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.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class CamelSourceActionTest { + + @Test + void testNullReturnsNull() { + assertNull(CamelSourceAction.extractSourceName(null)); + } + + @Test + void testPlainFilenameUnchanged() { + assertEquals("MyRoute.yaml", CamelSourceAction.extractSourceName("MyRoute.yaml")); + } + + @Test + void testSchemeAndPathStripsSchemeAndDirectory() { + // "file:/some/path/MyRoute.yaml" -> strip scheme -> "/some/path/MyRoute.yaml" -> strip path -> "MyRoute.yaml" + assertEquals("MyRoute.yaml", CamelSourceAction.extractSourceName("file:/some/path/MyRoute.yaml")); + } + + @Test + void testClasspathSchemeStripsScheme() { + assertEquals("routes.xml", CamelSourceAction.extractSourceName("classpath:routes.xml")); + } + + @Test + void testSingleColonTreatedAsScheme() { + // Only one colon: treated as scheme separator, not line number. + // "MyRoute.java:42" -> substring after ':' = "42" -> stripPath("42") = "42" + assertEquals("42", CamelSourceAction.extractSourceName("MyRoute.java:42")); + } + + @Test + void testSchemeWithLineNumberStripsSchemeAndLineNumber() { + // Two colons: stripSourceLocationLineNumber removes ":10" -> "file:/path/MyRoute.yaml" + // then strip scheme and path -> "MyRoute.yaml" + assertEquals("MyRoute.yaml", CamelSourceAction.extractSourceName("file:/path/MyRoute.yaml:10")); + } +} diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelStartupRecorderActionTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelStartupRecorderActionTest.java new file mode 100644 index 0000000000000..3cbdb9fc3a056 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelStartupRecorderActionTest.java @@ -0,0 +1,195 @@ +/* + * 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 java.nio.file.Path; +import java.util.Collections; +import java.util.List; + +import org.apache.camel.dsl.jbang.core.commands.CamelCommandBaseTestSupport; +import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; +import org.apache.camel.dsl.jbang.core.common.CommandLineHelper; +import org.apache.camel.util.json.JsonArray; +import org.apache.camel.util.json.JsonObject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class CamelStartupRecorderActionTest extends CamelCommandBaseTestSupport { + + @BeforeEach + @Override + public void setup() throws Exception { + super.setup(); + CommandLineHelper.useHomeDir("target"); + } + + @Test + void testDisplaysStepsFromOutput() throws Exception { + JsonObject mockOutput = buildStartupOutput( + step(1, 0, 0, "MyRoute", "Route", "Build route", 150), + step(2, 1, 1, null, "From", "timer:tick", 50), + step(3, 1, 1, null, "To", "log:info", 30)); + + TestableStartupRecorder command = new TestableStartupRecorder( + new CamelJBangMain().withPrinter(printer), mockOutput); + + int exit = command.doWatchCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("Route"), "Should show step type Route"); + assertTrue(output.contains("From"), "Should show step type From"); + assertTrue(output.contains("Build route"), "Should show step description"); + } + + @Test + void testStepWithNameShowsParentheses() throws Exception { + JsonObject mockOutput = buildStartupOutput( + step(1, 0, 0, "myRoute", "Route", "Configure route", 100)); + + TestableStartupRecorder command = new TestableStartupRecorder( + new CamelJBangMain().withPrinter(printer), mockOutput); + + int exit = command.doWatchCall(); + + assertEquals(0, exit); + // When a step has a name, it appears as "description(name)" + assertTrue(printer.getOutput().contains("Configure route(myRoute)"), + "Name should be appended in parentheses"); + } + + @Test + void testSortByDuration() throws Exception { + JsonObject mockOutput = buildStartupOutput( + step(1, 0, 0, null, "Route", "FastStep", 10), + step(2, 0, 0, null, "Route", "SlowStep", 500), + step(3, 0, 0, null, "Route", "MidStep", 200)); + + TestableStartupRecorder command = new TestableStartupRecorder( + new CamelJBangMain().withPrinter(printer), mockOutput); + command.sort = "duration"; + + int exit = command.doWatchCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + int fastPos = output.indexOf("FastStep"); + int midPos = output.indexOf("MidStep"); + int slowPos = output.indexOf("SlowStep"); + // Ascending duration: FastStep (10ms) before MidStep (200ms) before SlowStep (500ms) + assertTrue(fastPos < midPos && midPos < slowPos, + "Sort by duration ascending: fastest step must appear first"); + } + + @Test + void testSortByDurationDescending() throws Exception { + JsonObject mockOutput = buildStartupOutput( + step(1, 0, 0, null, "Route", "FastStep", 10), + step(2, 0, 0, null, "Route", "SlowStep", 500)); + + TestableStartupRecorder command = new TestableStartupRecorder( + new CamelJBangMain().withPrinter(printer), mockOutput); + command.sort = "-duration"; + + int exit = command.doWatchCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + int slowPos = output.indexOf("SlowStep"); + int fastPos = output.indexOf("FastStep"); + assertTrue(slowPos < fastPos, "Descending duration: slowest step must appear first"); + } + + @Test + void testZeroDurationDisplayedAsEmpty() throws Exception { + JsonObject mockOutput = buildStartupOutput( + step(1, 0, 0, null, "Route", "ZeroDurationStep", 0)); + + TestableStartupRecorder command = new TestableStartupRecorder( + new CamelJBangMain().withPrinter(printer), mockOutput); + + int exit = command.doWatchCall(); + + assertEquals(0, exit); + // Zero duration is rendered as empty string, not "0" — verify step name still appears + assertTrue(printer.getOutput().contains("ZeroDurationStep")); + } + + @Test + void testEmptyOutputWhenNoPidFound() throws Exception { + // No pids returned — command should return error code 1 + TestableStartupRecorder command = new TestableStartupRecorder( + new CamelJBangMain().withPrinter(printer), null) { + @Override + List findPids(String name) { + return List.of(); + } + }; + + int exit = command.doWatchCall(); + + assertEquals(1, exit, "Should return error when no pids found"); + } + + private static JsonObject buildStartupOutput(JsonObject... steps) { + JsonArray arr = new JsonArray(); + Collections.addAll(arr, steps); + JsonObject jo = new JsonObject(); + jo.put("steps", arr); + return jo; + } + + private static JsonObject step( + int id, int parentId, int level, String name, String type, String description, + long duration) { + JsonObject step = new JsonObject(); + step.put("id", id); + step.put("parentId", parentId); + step.put("level", level); + step.put("name", name); + step.put("type", type); + step.put("description", description); + step.put("duration", duration); + return step; + } + + /** + * Test subclass that bypasses process discovery and output file waiting so the rendering logic can be tested + * against a pre-built JSON response. + */ + private static class TestableStartupRecorder extends CamelStartupRecorderAction { + private final JsonObject mockOutput; + + TestableStartupRecorder(CamelJBangMain main, JsonObject mockOutput) { + super(main); + this.mockOutput = mockOutput; + } + + @Override + List findPids(String name) { + return List.of(12345L); + } + + @Override + protected JsonObject waitForOutputFile(Path outputFile) { + return mockOutput; + } + } +} diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/LoggerActionTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/LoggerActionTest.java new file mode 100644 index 0000000000000..8fedd383260c8 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/LoggerActionTest.java @@ -0,0 +1,197 @@ +/* + * 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 java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Instant; +import java.util.Optional; +import java.util.stream.Stream; + +import org.apache.camel.dsl.jbang.core.commands.CamelCommandBaseTestSupport; +import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; +import org.apache.camel.dsl.jbang.core.common.CommandLineHelper; +import org.apache.camel.util.json.JsonObject; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +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.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class LoggerActionTest extends CamelCommandBaseTestSupport { + + private static final long TEST_PID = 12345L; + private static final long CURRENT_PID = 99999L; + + @BeforeEach + @Override + public void setup() throws Exception { + super.setup(); + CommandLineHelper.useHomeDir("target/test-logger"); + Files.createDirectories(CommandLineHelper.getCamelDir()); + } + + @AfterEach + void cleanup() throws IOException { + Path camelDir = CommandLineHelper.getCamelDir(); + if (Files.exists(camelDir)) { + try (var entries = Files.list(camelDir)) { + for (Path p : entries.toList()) { + Files.deleteIfExists(p); + } + } + Files.deleteIfExists(camelDir); + } + } + + @Test + void testListShowsLoggers() throws Exception { + JsonObject levels = new JsonObject(); + levels.put("org.apache.camel", "INFO"); + levels.put("com.example", "DEBUG"); + + JsonObject logger = new JsonObject(); + logger.put("levels", levels); + + JsonObject context = new JsonObject(); + context.put("name", "myApp"); + + JsonObject root = new JsonObject(); + root.put("context", context); + root.put("logger", logger); + writeStatusFile(TEST_PID, root); + + LoggerAction command = new LoggerAction(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockHandle(TEST_PID); + ProcessHandle current = mockHandle(CURRENT_PID); + 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("org.apache.camel"), "Should show logger name"); + assertTrue(output.contains("INFO"), "Should show logger level"); + assertTrue(output.contains("com.example"), "Should list all loggers"); + assertTrue(output.contains("DEBUG"), "Should show DEBUG level"); + } + } + + @Test + void testEmptyOutputWhenNoLoggers() throws Exception { + JsonObject context = new JsonObject(); + context.put("name", "myApp"); + + JsonObject root = new JsonObject(); + root.put("context", context); + // No logger section + writeStatusFile(TEST_PID, root); + + LoggerAction command = new LoggerAction(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockHandle(TEST_PID); + ProcessHandle current = mockHandle(CURRENT_PID); + 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()); + } + } + + @Test + void testSortByName() throws Exception { + // "sort=name" sorts rows by context/process name; use two processes with different names + long pidZzz = TEST_PID; + long pidAaa = TEST_PID + 1; + + JsonObject levels = new JsonObject(); + levels.put("root", "INFO"); + + JsonObject logger = new JsonObject(); + logger.put("levels", levels); + + JsonObject rootZzz = new JsonObject(); + rootZzz.put("context", contextJson("zzzApp")); + rootZzz.put("logger", logger); + writeStatusFile(pidZzz, rootZzz); + + JsonObject rootAaa = new JsonObject(); + rootAaa.put("context", contextJson("aaaApp")); + rootAaa.put("logger", logger); + writeStatusFile(pidAaa, rootAaa); + + LoggerAction command = new LoggerAction(new CamelJBangMain().withPrinter(printer)); + command.sort = "name"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle phZzz = mockHandle(pidZzz); + ProcessHandle phAaa = mockHandle(pidAaa); + ProcessHandle current = mockHandle(CURRENT_PID); + mocked.when(ProcessHandle::current).thenReturn(current); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(phZzz, phAaa)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + int aaaPos = output.indexOf("aaaApp"); + int zzzPos = output.indexOf("zzzApp"); + assertTrue(aaaPos < zzzPos, "Sort by name ascending: aaaApp must appear before zzzApp"); + } + } + + private static JsonObject contextJson(String name) { + JsonObject ctx = new JsonObject(); + ctx.put("name", name); + return ctx; + } + + private static ProcessHandle mockHandle(long pid) { + ProcessHandle ph = mock(ProcessHandle.class); + ProcessHandle.Info info = mock(ProcessHandle.Info.class); + when(ph.pid()).thenReturn(pid); + // info/commandLine/startInstant are only used for process handles, not for the current handle + lenient().when(ph.info()).thenReturn(info); + lenient().when(info.commandLine()).thenReturn(Optional.empty()); + lenient().when(info.startInstant()).thenReturn(Optional.of(Instant.now().minusSeconds(60))); + return ph; + } + + private static void writeStatusFile(long pid, JsonObject root) throws Exception { + Path f = CommandLineHelper.getCamelDir().resolve(pid + "-status.json"); + Files.writeString(f, root.toJson()); + } +} diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/MessageTableHelperTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/MessageTableHelperTest.java new file mode 100644 index 0000000000000..9e41b1ae9f944 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/MessageTableHelperTest.java @@ -0,0 +1,116 @@ +/* + * 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.util.json.JsonArray; +import org.apache.camel.util.json.JsonObject; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class MessageTableHelperTest { + + @Test + void testBodyAppearsInOutput() { + MessageTableHelper helper = new MessageTableHelper(); + + JsonObject root = new JsonObject(); + root.put("exchangeType", "DefaultExchange"); + root.put("messageType", "DefaultMessage"); + + JsonObject body = new JsonObject(); + body.put("type", "String"); + body.put("value", "Hello World"); + root.put("body", body); + + String result = helper.getDataAsTable("exch-1", "InOnly", null, null, null, root, null); + + assertNotNull(result, "Result should not be null"); + assertTrue(result.contains("Hello World"), "Output should contain the body value"); + } + + @Test + void testHeadersAppearsWhenEnabled() { + MessageTableHelper helper = new MessageTableHelper(); + helper.setShowHeaders(true); + + JsonObject root = new JsonObject(); + root.put("exchangeType", "DefaultExchange"); + + JsonArray headers = new JsonArray(); + JsonObject header = new JsonObject(); + header.put("key", "Content-Type"); + header.put("type", "String"); + header.put("value", "application/json"); + headers.add(header); + root.put("headers", headers); + + String result = helper.getDataAsTable("exch-2", "InOnly", null, null, null, root, null); + + assertNotNull(result); + assertTrue(result.contains("Content-Type"), "Output should contain header name when showHeaders=true"); + assertTrue(result.contains("application/json"), "Output should contain header value"); + } + + @Test + void testHeadersHiddenWhenDisabled() { + MessageTableHelper helper = new MessageTableHelper(); + helper.setShowHeaders(false); + + JsonObject root = new JsonObject(); + root.put("exchangeType", "DefaultExchange"); + + JsonArray headers = new JsonArray(); + JsonObject header = new JsonObject(); + header.put("key", "X-Custom-Header"); + header.put("type", "String"); + header.put("value", "hidden-value"); + headers.add(header); + root.put("headers", headers); + + String result = helper.getDataAsTable("exch-3", "InOnly", null, null, null, root, null); + + assertNotNull(result); + assertFalse(result.contains("X-Custom-Header"), "Headers should not appear when showHeaders=false"); + + } + + @Test + void testNullRootReturnsEmptyTable() { + MessageTableHelper helper = new MessageTableHelper(); + + String result = helper.getDataAsTable("exch-4", "InOnly", null, null, null, null, null); + + assertNotNull(result, "Result should not be null even for null root"); + assertTrue(result.isBlank(), "Should return empty output when root is null"); + } + + @Test + void testExchangeIdAppearsInOutput() { + MessageTableHelper helper = new MessageTableHelper(); + + JsonObject root = new JsonObject(); + root.put("exchangeType", "DefaultExchange"); + + String result = helper.getDataAsTable("my-exchange-id-12345", "InOut", null, null, null, root, null); + + assertNotNull(result); + assertTrue(result.contains("my-exchange-id-12345"), "Exchange ID should appear in output"); + } +} From 4cfeca41c895f79bd7049eb1645247a17154eba8 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sat, 6 Jun 2026 20:20:18 -0400 Subject: [PATCH 03/10] CAMEL-23688: Add CamelContextStatusTest and CamelRouteStatusTest CamelContextStatusTest: Tests camel context status display for Running, Starting, and Stopped phases including table rendering and JSON output. CamelRouteStatusTest: Tests route status listing with empty result, single route, multiple routes, suspended route, JSON output, routeId filter, and row limit behavior. Co-Authored-By: Claude Sonnet 4.6 --- .../process/CamelContextStatusTest.java | 147 ++++++++++ .../process/CamelRouteStatusTest.java | 255 ++++++++++++++++++ 2 files changed, 402 insertions(+) create mode 100644 dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelContextStatusTest.java create mode 100644 dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatusTest.java diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelContextStatusTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelContextStatusTest.java new file mode 100644 index 0000000000000..801cb00774828 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelContextStatusTest.java @@ -0,0 +1,147 @@ +/* + * 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 CamelContextStatusTest extends ProcessCommandTestSupport { + + @Test + void testDisplaysRunningContext() throws Exception { + JsonObject status = buildContextStatus("myApp", 5); + writeStatusFile(TEST_PID, status); + + CamelContextStatus command = new CamelContextStatus(new CamelJBangMain().withPrinter(printer)); + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + 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 display context name"); + assertTrue(output.contains("Running"), "Phase 5 should show Running status"); + } + } + + @Test + void testDisplaysSuspendedContext() throws Exception { + JsonObject status = buildContextStatus("suspendedApp", 7); + writeStatusFile(TEST_PID, status); + + CamelContextStatus command = new CamelContextStatus(new CamelJBangMain().withPrinter(printer)); + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + assertTrue(printer.getOutput().contains("Suspended"), "Phase 7 should show Suspended"); + } + } + + @Test + void testRouteCountsInOutput() throws Exception { + JsonObject status = buildContextStatus("routeCountApp", 5); + // Add two routes: one started, one suspended + JsonArray routes = new JsonArray(); + JsonObject r1 = new JsonObject(); + r1.put("state", "Started"); + JsonObject r2 = new JsonObject(); + r2.put("state", "Suspended"); + routes.add(r1); + routes.add(r2); + status.put("routes", routes); + writeStatusFile(TEST_PID, status); + + CamelContextStatus command = new CamelContextStatus(new CamelJBangMain().withPrinter(printer)); + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + // The context status shows started/total routes — 1 started out of 2 total + String output = printer.getOutput(); + assertTrue(output.contains("1/2"), "Should show 1 started out of 2 total routes"); + } + } + + @Test + void testEmptyOutputWhenNoProcesses() throws Exception { + CamelContextStatus command = new CamelContextStatus(new CamelJBangMain().withPrinter(printer)); + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + 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("jsonApp", 5); + writeStatusFile(TEST_PID, status); + + CamelContextStatus command = new CamelContextStatus(new CamelJBangMain().withPrinter(printer)); + command.jsonOutput = true; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + 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 a JSON array"); + assertTrue(output.contains("jsonApp"), "JSON should contain the context name"); + } + } +} diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatusTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatusTest.java new file mode 100644 index 0000000000000..68261a1f36ced --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatusTest.java @@ -0,0 +1,255 @@ +/* + * 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 CamelRouteStatusTest extends ProcessCommandTestSupport { + + @Test + void testEmptyOutputWhenNoProcesses() throws Exception { + CamelRouteStatus command = new CamelRouteStatus(new CamelJBangMain().withPrinter(printer)); + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.empty()); + + int exit = command.doCall(); + + assertEquals(0, exit); + assertEquals("", printer.getOutput().trim()); + } + } + + @Test + void testDisplaysSingleRoute() throws Exception { + JsonObject status = buildRouteStatus("route1", "Started"); + writeStatusFile(TEST_PID, status); + + CamelRouteStatus command = new CamelRouteStatus(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("route1"), "Should show route ID"); + assertTrue(output.contains("timer:tick"), "Should show from URI"); + assertTrue(output.contains("Started"), "Should show route state"); + } + } + + @Test + void testDisplaysMultipleRoutes() throws Exception { + JsonObject routeStats = buildRouteStats(); + JsonObject route1 = buildRoute("route1", "timer:tick1", routeStats); + JsonObject route2 = buildRoute("route2", "timer:tick2", routeStats); + + JsonArray routes = new JsonArray(); + routes.add(route1); + routes.add(route2); + + JsonObject context = new JsonObject(); + context.put("name", "multiRouteApp"); + context.put("phase", 5); + + JsonObject root = new JsonObject(); + root.put("context", context); + root.put("routes", routes); + writeStatusFile(TEST_PID, root); + + CamelRouteStatus command = new CamelRouteStatus(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("route1")); + assertTrue(output.contains("route2")); + } + } + + @Test + void testSuspendedRouteShownCorrectly() throws Exception { + JsonObject status = buildRouteStatus("suspendedRoute", "Suspended"); + writeStatusFile(TEST_PID, status); + + CamelRouteStatus command = new CamelRouteStatus(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + assertTrue(printer.getOutput().contains("Suspended")); + } + } + + @Test + void testJsonOutput() throws Exception { + JsonObject status = buildRouteStatus("route1", "Started"); + writeStatusFile(TEST_PID, status); + + CamelRouteStatus command = new CamelRouteStatus(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.jsonOutput = true; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + 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 array"); + assertTrue(output.contains("route1")); + } + } + + @Test + void testFilterByRouteId() throws Exception { + JsonObject routeStats = buildRouteStats(); + JsonObject route1 = buildRoute("matchingRoute", "timer:tick1", routeStats); + JsonObject route2 = buildRoute("otherRoute", "timer:tick2", routeStats); + + JsonArray routes = new JsonArray(); + routes.add(route1); + routes.add(route2); + + JsonObject context = new JsonObject(); + context.put("name", "myApp"); + context.put("phase", 5); + + JsonObject root = new JsonObject(); + root.put("context", context); + root.put("routes", routes); + writeStatusFile(TEST_PID, root); + + CamelRouteStatus command = new CamelRouteStatus(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.filter = new String[] { "matchingRoute" }; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("matchingRoute"), "Filtered route should appear"); + assertFalse(output.contains("otherRoute"), "Non-matching route should be hidden by filter"); + } + } + + @Test + void testLimitReducesRows() throws Exception { + JsonObject routeStats = buildRouteStats(); + JsonArray routes = new JsonArray(); + for (int i = 1; i <= 5; i++) { + routes.add(buildRoute("route" + i, "timer:tick" + i, routeStats)); + } + + JsonObject context = new JsonObject(); + context.put("name", "bigApp"); + context.put("phase", 5); + + JsonObject root = new JsonObject(); + root.put("context", context); + root.put("routes", routes); + writeStatusFile(TEST_PID, root); + + CamelRouteStatus command = new CamelRouteStatus(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.limit = 2; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + // Only 2 routes should appear — limit is enforced during row collection + assertTrue(output.contains("route1")); + assertTrue(output.contains("route2")); + assertFalse(output.contains("route5"), "route5 should be excluded by limit=2"); + } + } + + private static JsonObject buildRouteStats() { + JsonObject stats = new JsonObject(); + stats.put("exchangesTotal", "0"); + stats.put("exchangesFailed", "0"); + stats.put("exchangesInflight", "0"); + stats.put("meanProcessingTime", "-1"); + stats.put("maxProcessingTime", "0"); + stats.put("minProcessingTime", "0"); + return stats; + } + + private static JsonObject buildRoute(String routeId, String fromUri, JsonObject stats) { + JsonObject route = new JsonObject(); + route.put("routeId", routeId); + route.put("from", fromUri); + route.put("state", "Started"); + route.put("uptime", "1m"); + route.put("statistics", stats); + return route; + } +} From 90e924dba1d102fac3e45f0c191cbdb7fa2cf8a5 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sat, 6 Jun 2026 20:20:40 -0400 Subject: [PATCH 04/10] CAMEL-23688: Add ListProcessTest, ListEndpointTest, ListHealthTest ListProcessTest: Covers table display, sort by pid/name/age, name filter, reload-error display, and JSON output for the process list command. ListEndpointTest: Covers endpoint URI display, empty result, direction filter (from/to), and JSON output. ListHealthTest: Covers empty result, UP/DOWN health check display, readiness filter, and JSON output. Co-Authored-By: Claude Sonnet 4.6 --- .../commands/process/ListEndpointTest.java | 160 ++++++++++++ .../core/commands/process/ListHealthTest.java | 174 +++++++++++++ .../commands/process/ListProcessTest.java | 238 ++++++++++++++++++ 3 files changed, 572 insertions(+) create mode 100644 dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListEndpointTest.java create mode 100644 dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListHealthTest.java create mode 100644 dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListProcessTest.java diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListEndpointTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListEndpointTest.java new file mode 100644 index 0000000000000..e2605a1340f3b --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListEndpointTest.java @@ -0,0 +1,160 @@ +/* + * 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.Collections; +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 ListEndpointTest extends ProcessCommandTestSupport { + + @Test + void testDisplaysEndpoints() throws Exception { + JsonObject root = buildEndpointStatus("myApp", + endpoint("timer:tick", "in", "10"), + endpoint("log:info", "out", "10")); + writeStatusFile(TEST_PID, root); + + ListEndpoint command = new ListEndpoint(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("timer:tick"), "Should show consumer endpoint"); + assertTrue(output.contains("log:info"), "Should show producer endpoint"); + } + } + + @Test + void testEmptyOutputWhenNoEndpoints() throws Exception { + JsonObject root = buildEndpointStatus("myApp"); + writeStatusFile(TEST_PID, root); + + ListEndpoint command = new ListEndpoint(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + assertEquals("", printer.getOutput().trim()); + } + } + + @Test + void testFilterByDirection() throws Exception { + JsonObject root = buildEndpointStatus("myApp", + endpoint("timer:tick", "in", "5"), + endpoint("kafka:topic", "out", "5")); + writeStatusFile(TEST_PID, root); + + ListEndpoint command = new ListEndpoint(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.filterDirection = "in"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("timer:tick"), "Consumer endpoint should be shown for direction=in"); + assertFalse(output.contains("kafka:topic"), "Producer endpoint should be hidden for direction=in"); + } + } + + @Test + void testJsonOutput() throws Exception { + JsonObject root = buildEndpointStatus("myApp", + endpoint("timer:tick", "in", "1")); + writeStatusFile(TEST_PID, root); + + ListEndpoint command = new ListEndpoint(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.jsonOutput = true; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + 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("timer:tick")); + } + } + + private static JsonObject buildEndpointStatus(String contextName, JsonObject... endpoints) { + JsonArray epArr = new JsonArray(); + Collections.addAll(epArr, endpoints); + + JsonObject epContainer = new JsonObject(); + epContainer.put("endpoints", epArr); + + JsonObject context = new JsonObject(); + context.put("name", contextName); + + JsonObject root = new JsonObject(); + root.put("context", context); + root.put("endpoints", epContainer); + return root; + } + + private static JsonObject endpoint(String uri, String direction, String hits) { + JsonObject ep = new JsonObject(); + ep.put("uri", uri); + ep.put("direction", direction); + ep.put("hits", hits); + ep.put("stub", false); + ep.put("remote", true); + return ep; + } +} diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListHealthTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListHealthTest.java new file mode 100644 index 0000000000000..a386b9b6cce3e --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListHealthTest.java @@ -0,0 +1,174 @@ +/* + * 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.Collections; +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 ListHealthTest extends ProcessCommandTestSupport { + + @Test + void testEmptyOutputWhenNoProcesses() throws Exception { + ListHealth command = new ListHealth(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.empty()); + + int exit = command.doCall(); + + assertEquals(0, exit); + assertEquals("", printer.getOutput().trim()); + } + } + + @Test + void testShowsUpHealthCheck() throws Exception { + JsonObject root = buildHealthStatus(check("context", "UP", true)); + writeStatusFile(TEST_PID, root); + + ListHealth command = new ListHealth(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("UP"), "Should show UP state"); + assertTrue(output.contains("context"), "Should show check ID"); + } + } + + @Test + void testShowsDownHealthCheck() throws Exception { + JsonObject root = buildHealthStatus(check("context", "DOWN", true)); + writeStatusFile(TEST_PID, root); + + ListHealth command = new ListHealth(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + assertTrue(printer.getOutput().contains("DOWN"), "Should show DOWN state"); + } + } + + @Test + void testJsonOutput() throws Exception { + JsonObject root = buildHealthStatus(check("context", "UP", true)); + writeStatusFile(TEST_PID, root); + + ListHealth command = new ListHealth(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.jsonOutput = true; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + 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("context")); + } + } + + @Test + void testFilterReadyOnly() throws Exception { + JsonObject root = buildHealthStatus( + check("context", "UP", true), + check("consumer", "DOWN", false)); + writeStatusFile(TEST_PID, root); + + ListHealth command = new ListHealth(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.ready = true; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("context"), "Readiness check should be shown"); + assertFalse(output.contains("consumer"), "Non-readiness check should be hidden with --ready"); + } + } + + private static JsonObject buildHealthStatus(JsonObject... checks) { + JsonArray checksArr = new JsonArray(); + Collections.addAll(checksArr, checks); + + JsonObject hc = new JsonObject(); + hc.put("checks", checksArr); + + JsonObject context = new JsonObject(); + context.put("name", "myApp"); + + JsonObject root = new JsonObject(); + root.put("context", context); + root.put("healthChecks", hc); + return root; + } + + private static JsonObject check(String id, String state, boolean readiness) { + JsonObject c = new JsonObject(); + c.put("id", id); + c.put("group", "camel"); + c.put("state", state); + c.put("readiness", readiness); + c.put("liveness", true); + return c; + } +} 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 new file mode 100644 index 0000000000000..4c277d82bee56 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListProcessTest.java @@ -0,0 +1,238 @@ +/* + * 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.List; +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 ListProcessTest extends ProcessCommandTestSupport { + + @Test + void testEmptyOutputWhenNoProcesses() throws Exception { + ListProcess command = new ListProcess(new CamelJBangMain().withPrinter(printer)); + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.empty()); + + int exit = command.doCall(); + + assertEquals(0, exit); + assertEquals("", printer.getOutput().trim()); + } + } + + @Test + void testListsRunningProcess() throws Exception { + JsonObject stats = buildStats(); + JsonObject status = buildContextStatus("myApp", 5); + ((JsonObject) status.get("context")).put("statistics", stats); + writeStatusFile(TEST_PID, status); + + ListProcess command = new ListProcess(new CamelJBangMain().withPrinter(printer)); + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + 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 contain context name"); + assertTrue(output.contains("Running"), "Should show Running status for phase 5"); + assertTrue(output.contains("1/1"), "Should show ready 1/1 when phase is 5"); + } + } + + @Test + void testListsStartingProcess() throws Exception { + // Phase 3 = Starting (anything <= 4 is Starting) + JsonObject status = buildContextStatus("startingApp", 3); + writeStatusFile(TEST_PID, status); + + ListProcess command = new ListProcess(new CamelJBangMain().withPrinter(printer)); + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("startingApp")); + assertTrue(output.contains("Starting"), "Phase <= 4 should map to Starting"); + assertTrue(output.contains("0/1"), "Should show 0/1 ready when not phase 5"); + } + } + + @Test + void testPidOnlyFlag() throws Exception { + JsonObject status = buildContextStatus("myApp", 5); + writeStatusFile(TEST_PID, status); + + ListProcess command = new ListProcess(new CamelJBangMain().withPrinter(printer)); + command.pid = true; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + List lines = printer.getLines(); + assertEquals(1, lines.size(), "Should output exactly one PID line"); + assertEquals(String.valueOf(TEST_PID), lines.get(0).trim()); + } + } + + @Test + void testJsonOutput() throws Exception { + JsonObject status = buildContextStatus("myApp", 5); + writeStatusFile(TEST_PID, status); + + ListProcess command = new ListProcess(new CamelJBangMain().withPrinter(printer)); + command.jsonOutput = true; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + 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 a JSON array"); + assertTrue(output.contains("myApp"), "JSON should contain the context name"); + } + } + + @Test + void testJsonPidOnlyFlag() throws Exception { + JsonObject status = buildContextStatus("myApp", 5); + writeStatusFile(TEST_PID, status); + + ListProcess command = new ListProcess(new CamelJBangMain().withPrinter(printer)); + command.pid = true; + command.jsonOutput = true; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.startsWith("["), "JSON pid output should be a JSON array"); + assertTrue(output.contains(String.valueOf(TEST_PID)), "Should contain the PID"); + } + } + + @Test + void testSortByName() throws Exception { + long pid1 = 11111L; + long pid2 = 22222L; + + JsonObject status1 = buildContextStatus("zebra", 5); + JsonObject status2 = buildContextStatus("apple", 5); + writeStatusFile(pid1, status1); + writeStatusFile(pid2, status2); + + ListProcess command = new ListProcess(new CamelJBangMain().withPrinter(printer)); + command.sort = "name"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph1 = mockProcessHandle(pid1); + ProcessHandle ph2 = mockProcessHandle(pid2); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + 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"); + } + } + + @Test + void testReloadErrorShownInOutput() throws Exception { + JsonObject status = buildContextStatus("faultyApp", 5); + JsonObject stats = (JsonObject) ((JsonObject) status.get("context")).get("statistics"); + JsonObject reload = new JsonObject(); + JsonObject lastError = new JsonObject(); + lastError.put("message", "Route file parse error"); + reload.put("lastError", lastError); + stats.put("reload", reload); + writeStatusFile(TEST_PID, status); + + ListProcess command = new ListProcess(new CamelJBangMain().withPrinter(printer)); + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("Error"), "Should show Error status when reload failed"); + // Description column is width-limited; "Reload" is the start of the description + assertTrue(output.contains("Reload"), "Should display the reload description"); + } + } + + private static JsonObject buildStats() { + JsonObject stats = new JsonObject(); + stats.put("exchangesTotal", "100"); + stats.put("exchangesFailed", "5"); + stats.put("exchangesInflight", "3"); + return stats; + } +} From 18c516f91d8f598696d95008c7c01e97163e499a Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sat, 6 Jun 2026 20:20:52 -0400 Subject: [PATCH 05/10] CAMEL-23688: Add ListRestTest, ListConsumerTest, ListErrorTest ListRestTest: Covers REST endpoint listing (URL, method), empty result, and JSON output. Notes that JSON-encoded URLs use escaped slashes (\/path). ListConsumerTest: Covers consumer URI and state display, empty result, and JSON output. The class field is required to avoid NPE in getType(). ListErrorTest: Covers error display from -error.json (separate from the status file), empty result (no error file), routeId display, and JSON output. The exception field must be a JsonObject, not a plain String. Co-Authored-By: Claude Sonnet 4.6 --- .../commands/process/ListConsumerTest.java | 139 ++++++++++++++++++ .../core/commands/process/ListErrorTest.java | 133 +++++++++++++++++ .../core/commands/process/ListRestTest.java | 135 +++++++++++++++++ 3 files changed, 407 insertions(+) create mode 100644 dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListConsumerTest.java create mode 100644 dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListErrorTest.java create mode 100644 dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListRestTest.java diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListConsumerTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListConsumerTest.java new file mode 100644 index 0000000000000..d31e51f557bd0 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListConsumerTest.java @@ -0,0 +1,139 @@ +/* + * 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.Collections; +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 ListConsumerTest extends ProcessCommandTestSupport { + + @Test + void testEmptyOutputWhenNoConsumers() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + writeStatusFile(TEST_PID, root); + + ListConsumer command = new ListConsumer(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + assertEquals("", printer.getOutput().trim()); + } + } + + @Test + void testShowsConsumerUriAndState() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("consumers", consumerContainer(consumer())); + writeStatusFile(TEST_PID, root); + + ListConsumer command = new ListConsumer(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("timer:tick"), "Should show consumer URI"); + assertTrue(output.contains("Started"), "Should show consumer state"); + } + } + + @Test + void testJsonOutput() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("consumers", consumerContainer(consumer())); + writeStatusFile(TEST_PID, root); + + ListConsumer command = new ListConsumer(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.jsonOutput = true; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + 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 array"); + assertTrue(output.contains("timer:tick")); + } + } + + private static JsonObject contextObj() { + JsonObject ctx = new JsonObject(); + ctx.put("name", "myApp"); + return ctx; + } + + private static JsonObject consumerContainer(JsonObject... consumers) { + JsonArray arr = new JsonArray(); + Collections.addAll(arr, consumers); + JsonObject container = new JsonObject(); + container.put("consumers", arr); + return container; + } + + private static JsonObject consumer() { + JsonObject c = new JsonObject(); + c.put("id", "route1"); + c.put("uri", "timer:tick"); + c.put("state", "Started"); + c.put("class", "org.apache.camel.component.timer.TimerConsumer"); + c.put("inflight", 0); + c.put("polling", false); + c.put("scheduled", false); + c.put("hostedService", false); + c.put("totalCounter", 0L); + c.put("delay", 0L); + c.put("period", 1000L); + return c; + } +} diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListErrorTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListErrorTest.java new file mode 100644 index 0000000000000..c17661f43cb57 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListErrorTest.java @@ -0,0 +1,133 @@ +/* + * 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.Collections; +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.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 ListErrorTest extends ProcessCommandTestSupport { + + @Test + void testEmptyOutputWhenNoErrors() throws Exception { + JsonObject status = buildContextStatus("myApp", 5); + writeStatusFile(TEST_PID, status); + // no error file written + + ListError command = new ListError(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + assertEquals("", printer.getOutput().trim()); + } + } + + @Test + void testShowsErrorRouteId() throws Exception { + JsonObject status = buildContextStatus("myApp", 5); + writeStatusFile(TEST_PID, status); + writeErrorFile(errorEntry()); + + ListError command = new ListError(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("myRoute"), "Should show routeId"); + } + } + + @Test + void testJsonOutput() throws Exception { + JsonObject status = buildContextStatus("myApp", 5); + writeStatusFile(TEST_PID, status); + writeErrorFile(errorEntry()); + + ListError command = new ListError(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.jsonOutput = true; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + 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 array"); + assertTrue(output.contains("myRoute")); + } + } + + private static void writeErrorFile(JsonObject... errors) throws Exception { + JsonArray arr = new JsonArray(); + Collections.addAll(arr, errors); + JsonObject root = new JsonObject(); + root.put("errors", arr); + Path f = CommandLineHelper.getCamelDir().resolve(ProcessCommandTestSupport.TEST_PID + "-error.json"); + Files.writeString(f, root.toJson()); + } + + private static JsonObject errorEntry() { + JsonObject ex = new JsonObject(); + ex.put("type", "java.lang.RuntimeException"); + ex.put("message", "Test error"); + + JsonObject e = new JsonObject(); + e.put("routeId", "myRoute"); + e.put("nodeId", "myNode"); + e.put("exchangeId", "abc-123"); + e.put("handled", false); + e.put("timestamp", System.currentTimeMillis()); + e.put("exception", ex); + return e; + } +} diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListRestTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListRestTest.java new file mode 100644 index 0000000000000..2672932e43a69 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListRestTest.java @@ -0,0 +1,135 @@ +/* + * 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.Collections; +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 ListRestTest extends ProcessCommandTestSupport { + + @Test + void testEmptyOutputWhenNoRests() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + writeStatusFile(TEST_PID, root); + + ListRest command = new ListRest(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + assertEquals("", printer.getOutput().trim()); + } + } + + @Test + void testShowsRestEndpoints() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("rests", restContainer(restEntry("/api/users", "GET"))); + writeStatusFile(TEST_PID, root); + + ListRest command = new ListRest(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("api/users"), "Should show URL"); + assertTrue(output.contains("GET"), "Should show HTTP method"); + } + } + + @Test + void testJsonOutput() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("rests", restContainer(restEntry("/api/items", "POST"))); + writeStatusFile(TEST_PID, root); + + ListRest command = new ListRest(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.jsonOutput = true; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + 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("items"), "Should contain the REST URL path"); + assertTrue(output.contains("POST"), "Should contain the HTTP method"); + } + } + + private static JsonObject contextObj() { + JsonObject ctx = new JsonObject(); + ctx.put("name", "myApp"); + return ctx; + } + + private static JsonObject restContainer(JsonObject... entries) { + JsonArray arr = new JsonArray(); + Collections.addAll(arr, entries); + JsonObject container = new JsonObject(); + container.put("rests", arr); + return container; + } + + private static JsonObject restEntry(String url, String method) { + JsonObject e = new JsonObject(); + e.put("url", url); + e.put("method", method); + e.put("consumes", "application/json"); + e.put("produces", "application/json"); + e.put("description", ""); + e.put("contractFirst", false); + return e; + } +} From 2a4a19a5e1e759ff8af459d29273223d3763a508 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sat, 6 Jun 2026 20:21:01 -0400 Subject: [PATCH 06/10] CAMEL-23688: Add ListVariableTest, ListMetricTest, ListCircuitBreakerTest ListVariableTest: Covers variable key/value display using the global scope JSON structure, empty result, and JSON output. ListMetricTest: Covers counter metric display. Requires command.all=true to show metrics regardless of count value, empty result, and JSON output. ListCircuitBreakerTest: Covers CLOSED and OPEN circuit breaker state display, empty result, and JSON output. Uses the resilience4j JSON structure. Co-Authored-By: Claude Sonnet 4.6 --- .../process/ListCircuitBreakerTest.java | 159 ++++++++++++++++++ .../core/commands/process/ListMetricTest.java | 132 +++++++++++++++ .../commands/process/ListVariableTest.java | 131 +++++++++++++++ 3 files changed, 422 insertions(+) create mode 100644 dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListCircuitBreakerTest.java create mode 100644 dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListMetricTest.java create mode 100644 dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListVariableTest.java diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListCircuitBreakerTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListCircuitBreakerTest.java new file mode 100644 index 0000000000000..32c73e1dc511a --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListCircuitBreakerTest.java @@ -0,0 +1,159 @@ +/* + * 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.Collections; +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 ListCircuitBreakerTest extends ProcessCommandTestSupport { + + @Test + void testEmptyOutputWhenNoCircuitBreakers() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + writeStatusFile(TEST_PID, root); + + ListCircuitBreaker command = new ListCircuitBreaker(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + assertEquals("", printer.getOutput().trim()); + } + } + + @Test + void testShowsClosedCircuitBreaker() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("resilience4j", cbContainer(cbEntry("CLOSED"))); + writeStatusFile(TEST_PID, root); + + ListCircuitBreaker command = new ListCircuitBreaker(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("myCB"), "Should show circuit breaker ID"); + assertTrue(output.contains("CLOSED"), "Should show CLOSED state"); + } + } + + @Test + void testShowsOpenCircuitBreaker() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("resilience4j", cbContainer(cbEntry("OPEN"))); + writeStatusFile(TEST_PID, root); + + ListCircuitBreaker command = new ListCircuitBreaker(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + assertTrue(printer.getOutput().contains("OPEN"), "Should show OPEN state"); + } + } + + @Test + void testJsonOutput() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("resilience4j", cbContainer(cbEntry("CLOSED"))); + writeStatusFile(TEST_PID, root); + + ListCircuitBreaker command = new ListCircuitBreaker(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.jsonOutput = true; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + 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 array"); + assertTrue(output.contains("myCB")); + } + } + + private static JsonObject contextObj() { + JsonObject ctx = new JsonObject(); + ctx.put("name", "myApp"); + return ctx; + } + + private static JsonObject cbContainer(JsonObject... cbs) { + JsonArray arr = new JsonArray(); + Collections.addAll(arr, cbs); + JsonObject container = new JsonObject(); + container.put("circuitBreakers", arr); + return container; + } + + private static JsonObject cbEntry(String state) { + JsonObject cb = new JsonObject(); + cb.put("id", "myCB"); + cb.put("routeId", "myRoute"); + cb.put("state", state); + cb.put("bufferedCalls", 0); + cb.put("successfulCalls", 0); + cb.put("failedCalls", 0); + cb.put("notPermittedCalls", 0L); + cb.put("failureRate", 0.0); + return cb; + } +} diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListMetricTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListMetricTest.java new file mode 100644 index 0000000000000..953f3d1f39648 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListMetricTest.java @@ -0,0 +1,132 @@ +/* + * 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.Collections; +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 ListMetricTest extends ProcessCommandTestSupport { + + @Test + void testEmptyOutputWhenNoMetrics() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + writeStatusFile(TEST_PID, root); + + ListMetric command = new ListMetric(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + assertEquals("", printer.getOutput().trim()); + } + } + + @Test + void testShowsCounterMetric() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("micrometer", micrometerObj(counterEntry(42.0))); + writeStatusFile(TEST_PID, root); + + ListMetric command = new ListMetric(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.all = true; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("myCounter"), "Should show metric name"); + } + } + + @Test + void testJsonOutput() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("micrometer", micrometerObj(counterEntry(10.0))); + writeStatusFile(TEST_PID, root); + + ListMetric command = new ListMetric(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.all = true; + command.jsonOutput = true; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + 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 array"); + assertTrue(output.contains("myCounter")); + } + } + + private static JsonObject contextObj() { + JsonObject ctx = new JsonObject(); + ctx.put("name", "myApp"); + return ctx; + } + + private JsonObject micrometerObj(JsonObject... counters) { + JsonArray arr = new JsonArray(); + Collections.addAll(arr, counters); + JsonObject mo = new JsonObject(); + mo.put("counters", arr); + return mo; + } + + private static JsonObject counterEntry(double count) { + JsonObject c = new JsonObject(); + c.put("name", "myCounter"); + c.put("description", ""); + c.put("count", count); + return c; + } +} diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListVariableTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListVariableTest.java new file mode 100644 index 0000000000000..cb8bd15fff397 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListVariableTest.java @@ -0,0 +1,131 @@ +/* + * 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.Collections; +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 ListVariableTest extends ProcessCommandTestSupport { + + @Test + void testEmptyOutputWhenNoVariables() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + writeStatusFile(TEST_PID, root); + + ListVariable command = new ListVariable(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + assertEquals("", printer.getOutput().trim()); + } + } + + @Test + void testShowsVariableKeyAndValue() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("variables", variablesObj(variableEntry("myKey", "String", "hello"))); + writeStatusFile(TEST_PID, root); + + ListVariable command = new ListVariable(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("myKey"), "Should show variable key"); + assertTrue(output.contains("hello"), "Should show variable value"); + } + } + + @Test + void testJsonOutput() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("variables", variablesObj(variableEntry("counter", "Integer", "42"))); + writeStatusFile(TEST_PID, root); + + ListVariable command = new ListVariable(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.jsonOutput = true; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + 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 array"); + assertTrue(output.contains("counter")); + } + } + + private static JsonObject contextObj() { + JsonObject ctx = new JsonObject(); + ctx.put("name", "myApp"); + return ctx; + } + + private static JsonObject variablesObj(JsonObject... entries) { + JsonArray arr = new JsonArray(); + Collections.addAll(arr, entries); + JsonObject vars = new JsonObject(); + vars.put("global", arr); + return vars; + } + + private static JsonObject variableEntry(String key, String type, String value) { + JsonObject e = new JsonObject(); + e.put("key", key); + e.put("type", type); + e.put("value", value); + return e; + } +} From 7ad2e2764751e7df5ce1add9a132d48c988167a3 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sat, 6 Jun 2026 23:36:26 -0400 Subject: [PATCH 07/10] CAMEL-23688: Add ListBlockedTest, ListEventTest, ListInflightTest --- .../commands/process/ListBlockedTest.java | 133 ++++++++++++++ .../core/commands/process/ListEventTest.java | 165 ++++++++++++++++++ .../commands/process/ListInflightTest.java | 137 +++++++++++++++ 3 files changed, 435 insertions(+) create mode 100644 dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListBlockedTest.java create mode 100644 dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListEventTest.java create mode 100644 dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListInflightTest.java diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListBlockedTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListBlockedTest.java new file mode 100644 index 0000000000000..25f247ca37e70 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListBlockedTest.java @@ -0,0 +1,133 @@ +/* + * 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.Collections; +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 ListBlockedTest extends ProcessCommandTestSupport { + + @Test + void testEmptyOutputWhenNoBlockedExchanges() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + writeStatusFile(TEST_PID, root); + + ListBlocked command = new ListBlocked(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + assertEquals("", printer.getOutput().trim()); + } + } + + @Test + void testShowsBlockedExchange() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("blocked", exchangeContainer(blockedExchange("exchange-1", "route1", "to1"))); + writeStatusFile(TEST_PID, root); + + ListBlocked command = new ListBlocked(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("exchange-1"), "Should show blocked exchange id"); + assertTrue(output.contains("route1"), "Should show route id"); + assertTrue(output.contains("to1"), "Should show node id"); + } + } + + @Test + void testJsonOutput() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("blocked", exchangeContainer(blockedExchange("exchange-2", "route2", "bean1"))); + writeStatusFile(TEST_PID, root); + + ListBlocked command = new ListBlocked(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.jsonOutput = true; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + 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 array"); + assertTrue(output.contains("exchange-2")); + } + } + + private static JsonObject contextObj() { + JsonObject ctx = new JsonObject(); + ctx.put("name", "myApp"); + return ctx; + } + + private static JsonObject exchangeContainer(JsonObject... exchanges) { + JsonArray arr = new JsonArray(); + Collections.addAll(arr, exchanges); + JsonObject container = new JsonObject(); + container.put("exchanges", arr); + return container; + } + + private static JsonObject blockedExchange(String exchangeId, String routeId, String nodeId) { + JsonObject e = new JsonObject(); + e.put("exchangeId", exchangeId); + e.put("routeId", routeId); + e.put("nodeId", nodeId); + e.put("duration", 1500L); + return e; + } +} diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListEventTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListEventTest.java new file mode 100644 index 0000000000000..16ceaaac64e7f --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListEventTest.java @@ -0,0 +1,165 @@ +/* + * 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.Collections; +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 ListEventTest extends ProcessCommandTestSupport { + + @Test + void testEmptyOutputWhenNoEvents() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + writeStatusFile(TEST_PID, root); + + ListEvent command = new ListEvent(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + assertEquals("", printer.getOutput().trim()); + } + } + + @Test + void testShowsAllEventGroups() throws Exception { + JsonObject root = buildEventStatus(); + writeStatusFile(TEST_PID, root); + + ListEvent command = new ListEvent(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("context started"), "Should show context events"); + assertTrue(output.contains("route started"), "Should show route events"); + assertTrue(output.contains("exchange done"), "Should show exchange events"); + } + } + + @Test + void testFilterRouteEvents() throws Exception { + JsonObject root = buildEventStatus(); + writeStatusFile(TEST_PID, root); + + ListEvent command = new ListEvent(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.filter = "route"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("route started"), "Route events should be shown"); + assertFalse(output.contains("context started"), "Context events should be filtered out"); + assertFalse(output.contains("exchange done"), "Exchange events should be filtered out"); + } + } + + @Test + void testJsonOutput() throws Exception { + JsonObject root = buildEventStatus(); + writeStatusFile(TEST_PID, root); + + ListEvent command = new ListEvent(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.jsonOutput = true; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + 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 array"); + assertTrue(output.contains("ContextStarted")); + } + } + + private static JsonObject contextObj() { + JsonObject ctx = new JsonObject(); + ctx.put("name", "myApp"); + return ctx; + } + + private static JsonObject buildEventStatus() { + JsonObject events = new JsonObject(); + events.put("events", eventArray(event("ContextStarted", "context started"))); + events.put("routeEvents", eventArray(event("RouteStarted", "route started"))); + events.put("exchangeEvents", eventArray(event("ExchangeCompleted", "exchange done"))); + + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("events", events); + return root; + } + + private static JsonArray eventArray(JsonObject... events) { + JsonArray arr = new JsonArray(); + Collections.addAll(arr, events); + return arr; + } + + private static JsonObject event(String type, String message) { + JsonObject e = new JsonObject(); + e.put("type", type); + e.put("timestamp", System.currentTimeMillis() - 1000); + e.put("exchangeId", "exchange-1"); + e.put("message", message); + return e; + } +} diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListInflightTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListInflightTest.java new file mode 100644 index 0000000000000..6caec235e0532 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListInflightTest.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.Collections; +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 ListInflightTest extends ProcessCommandTestSupport { + + @Test + void testEmptyOutputWhenNoInflightExchanges() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + writeStatusFile(TEST_PID, root); + + ListInflight command = new ListInflight(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + assertEquals("", printer.getOutput().trim()); + } + } + + @Test + void testShowsInflightExchangeWithRemoteColumn() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("inflight", exchangeContainer(inflightExchange("exchange-1", true))); + writeStatusFile(TEST_PID, root); + + ListInflight command = new ListInflight(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("exchange-1"), "Should show inflight exchange id"); + assertTrue(output.contains("REMOTE"), "Remote column should be visible when status has remote data"); + assertTrue(output.contains("to1"), "Should show current node id"); + } + } + + @Test + void testJsonOutput() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("inflight", exchangeContainer(inflightExchange("exchange-2", false))); + writeStatusFile(TEST_PID, root); + + ListInflight command = new ListInflight(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.jsonOutput = true; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + 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 array"); + assertTrue(output.contains("\"remote\":false")); + assertTrue(output.contains("exchange-2")); + } + } + + private static JsonObject contextObj() { + JsonObject ctx = new JsonObject(); + ctx.put("name", "myApp"); + return ctx; + } + + private static JsonObject exchangeContainer(JsonObject... exchanges) { + JsonArray arr = new JsonArray(); + Collections.addAll(arr, exchanges); + JsonObject container = new JsonObject(); + container.put("exchanges", arr); + return container; + } + + private static JsonObject inflightExchange(String exchangeId, boolean remote) { + JsonObject e = new JsonObject(); + e.put("exchangeId", exchangeId); + e.put("fromRouteId", "route1"); + e.put("fromRemoteEndpoint", remote); + e.put("atRouteId", "route1"); + e.put("nodeId", "to1"); + e.put("elapsed", 100L); + e.put("duration", 250L); + return e; + } +} From 0ff8b15a452ed37f6f0ed861af433a682c1b73e8 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sat, 6 Jun 2026 23:36:34 -0400 Subject: [PATCH 08/10] CAMEL-23688: Add ListInternalTaskTest, ListProducerTest, ListServiceTest --- .../process/ListInternalTaskTest.java | 139 +++++++++++++ .../commands/process/ListProducerTest.java | 194 ++++++++++++++++++ .../commands/process/ListServiceTest.java | 174 ++++++++++++++++ 3 files changed, 507 insertions(+) create mode 100644 dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListInternalTaskTest.java create mode 100644 dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListProducerTest.java create mode 100644 dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListServiceTest.java diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListInternalTaskTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListInternalTaskTest.java new file mode 100644 index 0000000000000..7b700e6ffc7d4 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListInternalTaskTest.java @@ -0,0 +1,139 @@ +/* + * 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.Collections; +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 ListInternalTaskTest extends ProcessCommandTestSupport { + + @Test + void testEmptyOutputWhenNoInternalTasks() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + writeStatusFile(TEST_PID, root); + + ListInternalTask command = new ListInternalTask(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + assertEquals("", printer.getOutput().trim()); + } + } + + @Test + void testShowsInternalTask() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("internal-tasks", taskContainer(task("reload", "Blocked"))); + writeStatusFile(TEST_PID, root); + + ListInternalTask command = new ListInternalTask(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("reload"), "Should show task name"); + assertTrue(output.contains("Blocked"), "Should show task status"); + assertTrue(output.contains("boom"), "Should show task failure"); + } + } + + @Test + void testJsonOutput() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("internal-tasks", taskContainer(task("sync", "Done"))); + writeStatusFile(TEST_PID, root); + + ListInternalTask command = new ListInternalTask(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.jsonOutput = true; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + 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 array"); + assertTrue(output.contains("sync")); + } + } + + private static JsonObject contextObj() { + JsonObject ctx = new JsonObject(); + ctx.put("name", "myApp"); + return ctx; + } + + private static JsonObject taskContainer(JsonObject... tasks) { + JsonArray arr = new JsonArray(); + Collections.addAll(arr, tasks); + JsonObject container = new JsonObject(); + container.put("tasks", arr); + return container; + } + + private static JsonObject task(String name, String status) { + long now = System.currentTimeMillis(); + JsonObject t = new JsonObject(); + t.put("name", name); + t.put("status", status); + t.put("attempts", 2L); + t.put("delay", 1000L); + t.put("elapsed", 50L); + t.put("firstTime", now - 5000); + t.put("lastTime", now - 1000); + t.put("nextTime", now + 1000); + t.put("error", "boom"); + return t; + } +} diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListProducerTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListProducerTest.java new file mode 100644 index 0000000000000..90d58230e34d2 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListProducerTest.java @@ -0,0 +1,194 @@ +/* + * 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.Collections; +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 ListProducerTest extends ProcessCommandTestSupport { + + @Test + void testEmptyOutputWhenNoProducers() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + writeStatusFile(TEST_PID, root); + + ListProducer command = new ListProducer(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + assertEquals("", printer.getOutput().trim()); + } + } + + @Test + void testShowsProducerUriAndState() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("producers", producerContainer(producer("route1", "log:info?showAll=true"))); + writeStatusFile(TEST_PID, root); + + ListProducer command = new ListProducer(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("log:info"), "Should show producer URI"); + assertTrue(output.contains("Started"), "Should show producer state"); + } + } + + @Test + void testFilterAndShortUri() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("producers", producerContainer( + producer("route1", "log:info?showAll=true"), + producer("route2", "mock:result?retainFirst=1"))); + writeStatusFile(TEST_PID, root); + + ListProducer command = new ListProducer(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.filter = "log:"; + command.shortUri = true; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("log:info"), "Matching producer should be shown"); + assertFalse(output.contains("showAll"), "Short URI should remove query parameters"); + assertFalse(output.contains("mock:result"), "Non-matching producer should be hidden"); + } + } + + @Test + void testLimit() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("producers", producerContainer( + producer("route1", "log:one"), + producer("route2", "log:two"))); + writeStatusFile(TEST_PID, root); + + ListProducer command = new ListProducer(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.limit = 1; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("log:one"), "First producer should be shown"); + assertFalse(output.contains("log:two"), "Second producer should be hidden by limit"); + } + } + + @Test + void testJsonOutput() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("producers", producerContainer(producer("route1", "direct:out"))); + writeStatusFile(TEST_PID, root); + + ListProducer command = new ListProducer(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.jsonOutput = true; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + 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 array"); + assertTrue(output.contains("direct:out")); + assertTrue(output.contains("Log"), "Producer suffix should be removed from type"); + } + } + + private static JsonObject contextObj() { + JsonObject ctx = new JsonObject(); + ctx.put("name", "myApp"); + return ctx; + } + + private static JsonObject producerContainer(JsonObject... producers) { + JsonArray arr = new JsonArray(); + Collections.addAll(arr, producers); + JsonObject container = new JsonObject(); + container.put("producers", arr); + return container; + } + + private static JsonObject producer(String routeId, String uri) { + JsonObject p = new JsonObject(); + p.put("routeId", routeId); + p.put("uri", uri); + p.put("state", "Started"); + p.put("class", "org.apache.camel.component.log.LogProducer"); + p.put("singleton", true); + p.put("remote", false); + return p; + } +} diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListServiceTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListServiceTest.java new file mode 100644 index 0000000000000..315902e390409 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListServiceTest.java @@ -0,0 +1,174 @@ +/* + * 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.Collections; +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 ListServiceTest extends ProcessCommandTestSupport { + + @Test + void testEmptyOutputWhenNoServices() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + writeStatusFile(TEST_PID, root); + + ListService command = new ListService(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + assertEquals("", printer.getOutput().trim()); + } + } + + @Test + void testShowsServiceDetails() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("services", serviceContainer(service("platform-http", "in", "http", "http://0.0.0.0:8080/hello"))); + writeStatusFile(TEST_PID, root); + + ListService command = new ListService(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("platform-http"), "Should show service component"); + assertTrue(output.contains("http://0.0.0.0:8080/hello"), "Should show service URL"); + } + } + + @Test + void testMetadataAndShortUri() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("services", serviceContainer(service("rest", "in", "http", "http://localhost:8080/orders"))); + writeStatusFile(TEST_PID, root); + + ListService command = new ListService(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.metadata = true; + command.shortUri = true; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("method=GET"), "Metadata should be shown when requested"); + assertTrue(output.contains("direct:orders"), "Endpoint URI should be shown"); + assertFalse(output.contains("bridgeEndpoint"), "Short URI should remove endpoint query parameters"); + } + } + + @Test + void testJsonOutputUsesEmptyRouteWhenMissing() throws Exception { + JsonObject entry = service("servlet", "in", "http", "http://localhost:8080/ping"); + entry.remove("routeId"); + + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("services", serviceContainer(entry)); + writeStatusFile(TEST_PID, root); + + ListService command = new ListService(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.jsonOutput = true; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + 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 array"); + assertTrue(output.contains("\"routeId\":\"\""), "Missing route id should be serialized as an empty string"); + assertTrue(output.contains("servlet")); + } + } + + private static JsonObject contextObj() { + JsonObject ctx = new JsonObject(); + ctx.put("name", "myApp"); + return ctx; + } + + private static JsonObject serviceContainer(JsonObject... services) { + JsonArray arr = new JsonArray(); + Collections.addAll(arr, services); + JsonObject container = new JsonObject(); + container.put("services", arr); + return container; + } + + private static JsonObject service(String component, String direction, String protocol, String serviceUrl) { + JsonObject metadata = new JsonObject(); + metadata.put("method", "GET"); + metadata.put("path", "/orders"); + + JsonObject s = new JsonObject(); + s.put("component", component); + s.put("direction", direction); + s.put("hosted", true); + s.put("protocol", protocol); + s.put("serviceUrl", serviceUrl); + s.put("endpointUri", "direct:orders?bridgeEndpoint=true"); + s.put("hits", 3L); + s.put("routeId", "route1"); + s.put("metadata", metadata); + return s; + } +} From f13c8d83666d6fa09d6cc310c58c44c589a78481 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sat, 6 Jun 2026 23:36:43 -0400 Subject: [PATCH 09/10] CAMEL-23688: Add ListTransformerTest, ListPropertiesTest, ListVaultTest ListVaultTest includes two @Disabled regression tests documenting a row-mutation bug in ListVault.doProcessWatchCall(): when multiple vault types each have exactly one secret, the same Row object is reused and its vault field is overwritten by each subsequent vault block. Affects both table and JSON output paths. # Conflicts: # dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListVaultTest.java --- .../commands/process/ListPropertiesTest.java | 173 ++++++++++++++++++ .../commands/process/ListTransformerTest.java | 132 +++++++++++++ 2 files changed, 305 insertions(+) create mode 100644 dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListPropertiesTest.java create mode 100644 dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListTransformerTest.java diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListPropertiesTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListPropertiesTest.java new file mode 100644 index 0000000000000..8ab0232b492fa --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListPropertiesTest.java @@ -0,0 +1,173 @@ +/* + * 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.Collections; +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 ListPropertiesTest extends ProcessCommandTestSupport { + + @Test + void testEmptyOutputWhenNoProperties() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("properties", propertyContainer("properties")); + writeStatusFile(TEST_PID, root); + + ListProperties command = new ListProperties(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + assertEquals("", printer.getOutput().trim()); + } + } + + @Test + void testMasksSensitiveValuesByDefault() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("properties", propertyContainer("properties", + property("camel.password", "secret", "secret", false, "properties:app", "initial"))); + writeStatusFile(TEST_PID, root); + + ListProperties command = new ListProperties(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + // "camel.password" is 14 chars; the KEY column minimum is 15 but may truncate with ellipsis + // in narrow terminal environments — check the prefix that always survives truncation + assertTrue(output.contains("camel.pass"), "Should show sensitive property key (possibly truncated)"); + assertTrue(output.contains("xxxxxx"), "Sensitive value should be masked"); + assertFalse(output.contains("secret"), "Raw sensitive value should not be printed"); + } + } + + @Test + void testInternalPropertiesHiddenUnlessRequested() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("properties", propertyContainer("properties", + property("public.key", "visible", "visible", false, "properties:app", "ENV"), + property("internal.key", "hidden", "hidden", true, "properties:app", "SYS"))); + writeStatusFile(TEST_PID, root); + + ListProperties command = new ListProperties(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("public.key"), "Public property should be shown"); + assertFalse(output.contains("internal.key"), "Internal property should be hidden by default"); + } + } + + @Test + void testStartupVerboseJsonOutput() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("main-configuration", propertyContainer("configurations", + property("camel.main.name", "demo", "demo", false, "sys:camel.main.name", "CLI"))); + writeStatusFile(TEST_PID, root); + + ListProperties command = new ListProperties(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.startup = true; + command.verbose = true; + command.jsonOutput = true; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + 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 array"); + assertTrue(output.contains("camel.main.name")); + assertTrue(output.contains("Command Line"), "CLI location should be sanitized"); + assertTrue(output.contains("\"function\":\"sys\""), "Function should be derived from source prefix"); + } + } + + private static JsonObject contextObj() { + JsonObject ctx = new JsonObject(); + ctx.put("name", "myApp"); + return ctx; + } + + private static JsonObject propertyContainer(String key, JsonObject... properties) { + JsonArray arr = new JsonArray(); + Collections.addAll(arr, properties); + JsonObject container = new JsonObject(); + container.put(key, arr); + return container; + } + + private static JsonObject property( + String key, String value, String originalValue, boolean internal, String source, String location) { + JsonObject p = new JsonObject(); + p.put("key", key); + p.put("value", value); + p.put("originalValue", originalValue); + p.put("internal", internal); + p.put("source", source); + p.put("location", location); + return p; + } +} diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListTransformerTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListTransformerTest.java new file mode 100644 index 0000000000000..4deeff87521ee --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListTransformerTest.java @@ -0,0 +1,132 @@ +/* + * 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.Collections; +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 ListTransformerTest extends ProcessCommandTestSupport { + + @Test + void testEmptyOutputWhenNoTransformers() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + writeStatusFile(TEST_PID, root); + + ListTransformer command = new ListTransformer(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + assertEquals("", printer.getOutput().trim()); + } + } + + @Test + void testShowsTransformerDataTypes() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("transformers", transformerContainer(transformer("order-transformer", "xml:Order", "json:Order"))); + writeStatusFile(TEST_PID, root); + + ListTransformer command = new ListTransformer(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph)); + + int exit = command.doCall(); + + assertEquals(0, exit); + String output = printer.getOutput(); + assertTrue(output.contains("order-transformer"), "Should show transformer name"); + assertTrue(output.contains("xml:Order"), "Should show source data type"); + assertTrue(output.contains("json:Order"), "Should show target data type"); + } + } + + @Test + void testJsonOutput() throws Exception { + JsonObject root = new JsonObject(); + root.put("context", contextObj()); + root.put("transformers", transformerContainer(transformer("order-transformer", "xml:Order", "json:Order"))); + writeStatusFile(TEST_PID, root); + + ListTransformer command = new ListTransformer(new CamelJBangMain().withPrinter(printer)); + command.sort = "pid"; + command.jsonOutput = true; + + try (MockedStatic mocked = mockStatic(ProcessHandle.class)) { + ProcessHandle ph = mockProcessHandle(TEST_PID); + ProcessHandle currentHandle = mockCurrentHandle(); + mocked.when(ProcessHandle::current).thenReturn(currentHandle); + 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 array"); + assertTrue(output.contains("order-transformer")); + } + } + + private static JsonObject contextObj() { + JsonObject ctx = new JsonObject(); + ctx.put("name", "myApp"); + return ctx; + } + + private static JsonObject transformerContainer(JsonObject... transformers) { + JsonArray arr = new JsonArray(); + Collections.addAll(arr, transformers); + JsonObject container = new JsonObject(); + container.put("transformers", arr); + return container; + } + + private static JsonObject transformer(String name, String from, String to) { + JsonObject t = new JsonObject(); + t.put("name", name); + t.put("from", from); + t.put("to", to); + return t; + } +} From 172487561e0382aa29b03dde0542e5541275158e Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sun, 7 Jun 2026 09:56:42 -0400 Subject: [PATCH 10/10] CAMEL-23688: Update ListMetricTest.java Co-authored-by: Claus Ibsen --- .../camel/dsl/jbang/core/commands/process/ListMetricTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListMetricTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListMetricTest.java index 953f3d1f39648..40f6909a0a8a5 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListMetricTest.java +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListMetricTest.java @@ -114,7 +114,7 @@ private static JsonObject contextObj() { return ctx; } - private JsonObject micrometerObj(JsonObject... counters) { + private static JsonObject micrometerObj(JsonObject... counters) { JsonArray arr = new JsonArray(); Collections.addAll(arr, counters); JsonObject mo = new JsonObject();