This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new d971d1ff48de CAMEL-23147: Embed citrus test plugin in camel-launcher 
(#21979)
d971d1ff48de is described below

commit d971d1ff48dec1b47eb4fb624a94ef1fdbc9d81b
Author: Guillaume Nodet <[email protected]>
AuthorDate: Tue Mar 17 08:12:23 2026 +0100

    CAMEL-23147: Embed citrus test plugin in camel-launcher (#21979)
    
    Co-authored-by: Claude Opus 4.6 <[email protected]>
---
 dsl/camel-jbang/camel-jbang-plugin-test/pom.xml    |   2 +-
 .../dsl/jbang/core/commands/test/TestInit.java     | 158 ++++++++++++++
 .../dsl/jbang/core/commands/test/TestPlugin.java   | 148 +------------
 .../dsl/jbang/core/commands/test/TestRun.java      | 229 +++++++++++++++++++++
 .../main/resources/templates/citrus-feature.tmpl   |   8 +
 .../main/resources/templates/citrus-groovy.tmpl    |   5 +
 .../src/main/resources/templates/citrus-java.tmpl  |  21 ++
 .../src/main/resources/templates/citrus-xml.tmpl   |  12 ++
 .../src/main/resources/templates/citrus-yaml.tmpl  |  10 +
 dsl/camel-jbang/camel-launcher/pom.xml             |   6 +-
 .../dsl/jbang/launcher/CamelLauncherMain.java      |   2 +
 11 files changed, 456 insertions(+), 145 deletions(-)

diff --git a/dsl/camel-jbang/camel-jbang-plugin-test/pom.xml 
b/dsl/camel-jbang/camel-jbang-plugin-test/pom.xml
index b660622ff8ea..593ee0288eee 100644
--- a/dsl/camel-jbang/camel-jbang-plugin-test/pom.xml
+++ b/dsl/camel-jbang/camel-jbang-plugin-test/pom.xml
@@ -49,7 +49,7 @@
 
         <dependency>
             <groupId>org.citrusframework</groupId>
-            <artifactId>citrus-jbang-connector</artifactId>
+            <artifactId>citrus-base</artifactId>
             <version>${citrus-version}</version>
         </dependency>
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-test/src/main/java/org/apache/camel/dsl/jbang/core/commands/test/TestInit.java
 
b/dsl/camel-jbang/camel-jbang-plugin-test/src/main/java/org/apache/camel/dsl/jbang/core/commands/test/TestInit.java
new file mode 100644
index 000000000000..7bc2aedbabdd
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-test/src/main/java/org/apache/camel/dsl/jbang/core/commands/test/TestInit.java
@@ -0,0 +1,158 @@
+/*
+ * 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.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelCommand;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.commands.ExportHelper;
+import org.apache.camel.util.IOHelper;
+import org.citrusframework.CitrusVersion;
+import picocli.CommandLine;
+
+/**
+ * Initializes a new Citrus test file from a template. Creates the test 
subfolder if it does not exist and writes a
+ * template test source for the given file type. Also creates a 
jbang.properties file with default Citrus dependencies
+ * when not already present.
+ */
[email protected](name = "init",
+                     description = "Initialize a new Citrus test from a 
template")
+public class TestInit extends CamelCommand {
+
+    public static final String TEST_DIR = "test";
+
+    @CommandLine.Parameters(index = "0", description = "Test file name (e.g. 
MyTest.yaml, MyTest.xml, MyTest.java)")
+    String file;
+
+    @CommandLine.Option(names = { "-d", "--directory" }, description = "Target 
directory", defaultValue = ".")
+    String directory;
+
+    public TestInit(CamelJBangMain main) {
+        super(main);
+    }
+
+    @Override
+    public Integer doCall() throws Exception {
+        // Determine file extension and base name
+        String ext = getFileExtension(file);
+        String baseName = getBaseName(file);
+
+        if (ext.isEmpty()) {
+            printer().println("Cannot determine file type for: " + file);
+            return 1;
+        }
+
+        // Load template for the file type
+        String template;
+        try (InputStream is = 
TestInit.class.getClassLoader().getResourceAsStream("templates/citrus-" + ext + 
".tmpl")) {
+            if (is == null) {
+                printer().println("Unsupported test file type: " + ext);
+                return 1;
+            }
+            template = IOHelper.loadText(is);
+        }
+
+        // Replace template placeholders
+        template = template.replaceAll("\\{\\{ \\.Name }}", baseName);
+
+        // Determine the working directory
+        Path workingDir = resolveTestDir();
+        if (workingDir == null) {
+            printer().println("Cannot create test working directory");
+            return 1;
+        }
+
+        // Create target directory if specified
+        Path targetDir;
+        if (".".equals(directory)) {
+            targetDir = workingDir;
+        } else {
+            targetDir = workingDir.resolve(directory);
+            Files.createDirectories(targetDir);
+        }
+
+        // Create jbang properties with default dependencies if not present
+        createJBangProperties(workingDir);
+
+        // Write the test file
+        Path testFile = targetDir.resolve(file);
+        Files.writeString(testFile, template);
+
+        printer().println("Created test file: " + testFile);
+        return 0;
+    }
+
+    /**
+     * Resolves and creates the test directory. Automatically uses the test 
subfolder as the working directory.
+     */
+    private Path resolveTestDir() {
+        Path currentDir = Paths.get(".");
+        Path workingDir;
+        if 
(TEST_DIR.equals(currentDir.toAbsolutePath().normalize().getFileName().toString()))
 {
+            // current directory is already the test subfolder
+            workingDir = currentDir;
+        } else if (currentDir.resolve(TEST_DIR).toFile().exists()) {
+            // navigate to existing test subfolder
+            workingDir = currentDir.resolve(TEST_DIR);
+        } else if (currentDir.resolve(TEST_DIR).toFile().mkdirs()) {
+            // create test subfolder and navigate to it
+            workingDir = currentDir.resolve(TEST_DIR);
+        } else {
+            return null;
+        }
+        return workingDir;
+    }
+
+    /**
+     * Creates jbang.properties with default Citrus dependencies if not 
already present.
+     */
+    private void createJBangProperties(Path workingDir) {
+        if (!workingDir.resolve("jbang.properties").toFile().exists()) {
+            Path jbangProperties = workingDir.resolve("jbang.properties");
+            try (InputStream is
+                    = 
TestInit.class.getClassLoader().getResourceAsStream("templates/jbang-properties.tmpl"))
 {
+                String context = IOHelper.loadText(is);
+                context = context.replaceAll("\\{\\{ \\.CitrusVersion }}", 
CitrusVersion.version());
+                ExportHelper.safeCopy(new 
ByteArrayInputStream(context.getBytes(StandardCharsets.UTF_8)), 
jbangProperties);
+            } catch (Exception e) {
+                printer().println("Failed to create jbang.properties for tests 
in: " + jbangProperties);
+            }
+        }
+    }
+
+    private static String getFileExtension(String fileName) {
+        int dotIndex = fileName.lastIndexOf('.');
+        if (dotIndex > 0 && dotIndex < fileName.length() - 1) {
+            return fileName.substring(dotIndex + 1);
+        }
+        return "";
+    }
+
+    private static String getBaseName(String fileName) {
+        int dotIndex = fileName.lastIndexOf('.');
+        if (dotIndex > 0) {
+            return fileName.substring(0, dotIndex);
+        }
+        return fileName;
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-test/src/main/java/org/apache/camel/dsl/jbang/core/commands/test/TestPlugin.java
 
b/dsl/camel-jbang/camel-jbang-plugin-test/src/main/java/org/apache/camel/dsl/jbang/core/commands/test/TestPlugin.java
index a556df43b7c3..085fd9cb294d 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-test/src/main/java/org/apache/camel/dsl/jbang/core/commands/test/TestPlugin.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-test/src/main/java/org/apache/camel/dsl/jbang/core/commands/test/TestPlugin.java
@@ -16,27 +16,12 @@
  */
 package org.apache.camel.dsl.jbang.core.commands.test;
 
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
 import java.util.Optional;
 
-import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
-import org.apache.camel.dsl.jbang.core.commands.ExportHelper;
 import org.apache.camel.dsl.jbang.core.common.CamelJBangPlugin;
 import org.apache.camel.dsl.jbang.core.common.Plugin;
 import org.apache.camel.dsl.jbang.core.common.PluginExporter;
-import org.apache.camel.util.IOHelper;
-import org.citrusframework.CitrusVersion;
-import org.citrusframework.jbang.JBangSettings;
-import org.citrusframework.jbang.JBangSupport;
-import org.citrusframework.jbang.ProcessAndOutput;
 import picocli.CommandLine;
 
 @CamelJBangPlugin(name = "camel-jbang-plugin-test", firstVersion = "4.14.0")
@@ -44,138 +29,15 @@ public class TestPlugin implements Plugin {
 
     @Override
     public void customize(CommandLine commandLine, CamelJBangMain main) {
-        commandLine.setExecutionStrategy(new CitrusExecutionStrategy(main))
-                .addSubcommand("test", new CommandLine(new TestCommand(main))
-                        .setUnmatchedArgumentsAllowed(true)
-                        .setUnmatchedOptionsAllowedAsOptionParameters(true));
+        var cmd = new CommandLine(new TestCommand(main))
+                .addSubcommand("init", new CommandLine(new TestInit(main)))
+                .addSubcommand("run", new CommandLine(new TestRun(main)));
+
+        commandLine.addSubcommand("test", cmd);
     }
 
     @Override
     public Optional<PluginExporter> getExporter() {
         return Optional.of(new TestPluginExporter());
     }
-
-    /**
-     * Command execution strategy delegates to Citrus JBang for subcommands 
like init or run. Performs special command
-     * preparations and makes sure to run the proper Citrus version for this 
Camel release.
-     *
-     * @param main Camel JBang main that provides the output printer.
-     */
-    private record CitrusExecutionStrategy(CamelJBangMain main) implements 
CommandLine.IExecutionStrategy {
-
-        public static final String TEST_DIR = "test";
-
-        @Override
-        public int execute(CommandLine.ParseResult parseResult)
-                throws CommandLine.ExecutionException, 
CommandLine.ParameterException {
-
-            String command;
-            List<String> args = Collections.emptyList();
-
-            if (parseResult.originalArgs().size() > 2) {
-                command = parseResult.originalArgs().get(1);
-                args = parseResult.originalArgs().subList(2, 
parseResult.originalArgs().size());
-            } else if (parseResult.originalArgs().size() == 2) {
-                command = parseResult.originalArgs().get(1);
-            } else {
-                // run help command by default
-                command = "--help";
-            }
-
-            JBangSupport citrus = 
JBangSupport.jbang().app(JBangSettings.getApp())
-                    .withSystemProperty("citrus.jbang.version", 
CitrusVersion.version());
-
-            // Prepare commands
-            if ("init".equals(command)) {
-                return executeInitCommand(citrus, args);
-            } else if ("run".equals(command)) {
-                return executeRunCommand(citrus, args);
-            }
-
-            return execute(citrus, command, args);
-        }
-
-        /**
-         * Prepare and execute init command. Automatically uses test subfolder 
as a working directory for creating new
-         * tests. Automatically adds a jbang.properties configuration to add 
required Camel Citrus dependencies.
-         */
-        private int executeInitCommand(JBangSupport citrus, List<String> args) 
{
-            Path currentDir = Paths.get(".");
-            Path workingDir;
-            // Automatically set test subfolder as a working directory
-            if (TEST_DIR.equals(currentDir.getFileName().toString())) {
-                // current directory is already the test subfolder
-                workingDir = currentDir;
-            } else if (currentDir.resolve(TEST_DIR).toFile().exists()) {
-                // navigate to existing test subfolder
-                workingDir = currentDir.resolve(TEST_DIR);
-                citrus.workingDir(workingDir);
-            } else if (currentDir.resolve(TEST_DIR).toFile().mkdirs()) {
-                // create test subfolder and navigate to it
-                workingDir = currentDir.resolve(TEST_DIR);
-                citrus.workingDir(workingDir);
-            } else {
-                throw new RuntimeCamelException("Cannot create test working 
directory in: " + currentDir);
-            }
-
-            // Create jbang properties with default dependencies if not present
-            if (!workingDir.resolve("jbang.properties").toFile().exists()) {
-                Path jbangProperties = workingDir.resolve("jbang.properties");
-                try (InputStream is
-                        = 
TestPlugin.class.getClassLoader().getResourceAsStream("templates/jbang-properties.tmpl"))
 {
-                    String context = IOHelper.loadText(is);
-
-                    context = context.replaceAll("\\{\\{ \\.CitrusVersion }}", 
CitrusVersion.version());
-
-                    ExportHelper.safeCopy(new 
ByteArrayInputStream(context.getBytes(StandardCharsets.UTF_8)), 
jbangProperties);
-                } catch (Exception e) {
-                    main.getOut().println("Failed to create jbang.properties 
for tests in:" + jbangProperties);
-                }
-            }
-
-            return execute(citrus, "init", args);
-        }
-
-        /**
-         * Prepare and execute Citrus run command. Automatically navigates to 
test subfolder if it is present and uses
-         * this as a working directory. Runs command asynchronous streaming 
logs to the main output of this Camel JBang
-         * process.
-         */
-        private int executeRunCommand(JBangSupport citrus, List<String> args) {
-            Path currentDir = Paths.get(".");
-            List<String> runArgs = new ArrayList<>(args);
-            // automatically navigate to test subfolder for test execution
-            if (currentDir.resolve(TEST_DIR).toFile().exists()) {
-                // set test subfolder as working directory
-                citrus.workingDir(currentDir.resolve(TEST_DIR));
-
-                // remove test folder prefix in test file path if present
-                if (!args.isEmpty() && args.get(0).startsWith(TEST_DIR + "/")) 
{
-                    runArgs = new ArrayList<>(args.subList(1, args.size()));
-                    runArgs.add(0, args.get(0).substring((TEST_DIR + 
"/").length()));
-                }
-            }
-
-            citrus.withOutputListener(output -> main.getOut().print(output));
-            ProcessAndOutput pao = citrus.runAsync("run", runArgs);
-            try {
-                pao.waitFor();
-            } catch (InterruptedException e) {
-                main.getOut().printErr("Interrupted while running Citrus 
command", e);
-            }
-
-            return pao.getProcess().exitValue();
-        }
-
-        /**
-         * Uses given Citrus JBang instance to run the given command using the 
given arguments.
-         *
-         * @return exit code of the command process.
-         */
-        private int execute(JBangSupport citrus, String command, List<String> 
args) {
-            ProcessAndOutput pao = citrus.run(command, args);
-            main.getOut().print(pao.getOutput());
-            return pao.getProcess().exitValue();
-        }
-    }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-test/src/main/java/org/apache/camel/dsl/jbang/core/commands/test/TestRun.java
 
b/dsl/camel-jbang/camel-jbang-plugin-test/src/main/java/org/apache/camel/dsl/jbang/core/commands/test/TestRun.java
new file mode 100644
index 000000000000..6d1094ada282
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-test/src/main/java/org/apache/camel/dsl/jbang/core/commands/test/TestRun.java
@@ -0,0 +1,229 @@
+/*
+ * 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.test;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelCommand;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.citrusframework.TestSource;
+import org.citrusframework.main.TestEngine;
+import org.citrusframework.main.TestRunConfiguration;
+import org.citrusframework.util.FileUtils;
+import picocli.CommandLine;
+
+/**
+ * Runs Citrus test files directly using the Citrus test engine. This replaces 
the indirect JBang process call with a
+ * direct in-process test execution.
+ */
[email protected](name = "run",
+                     description = "Run Citrus tests")
+public class TestRun extends CamelCommand {
+
+    public static final String TEST_DIR = "test";
+    public static final String WORK_DIR = ".citrus-jbang";
+
+    private static final String[] ACCEPTED_FILE_EXT = { "xml", "yaml", "yml", 
"java", "groovy", "feature" };
+
+    @CommandLine.Parameters(description = "Test files or directories to run", 
arity = "0..*")
+    String[] files;
+
+    @CommandLine.Option(names = { "--engine" }, description = "Test engine to 
use", defaultValue = "default")
+    String engine;
+
+    @CommandLine.Option(names = { "--verbose" }, description = "Enable verbose 
output")
+    boolean verbose;
+
+    @CommandLine.Option(names = { "-p", "--property" }, description = "Set 
system properties (key=value)")
+    String[] properties;
+
+    @CommandLine.Option(names = { "--includes" }, description = "Test name 
include patterns")
+    String[] includes;
+
+    public TestRun(CamelJBangMain main) {
+        super(main);
+    }
+
+    @Override
+    public Integer doCall() throws Exception {
+        Path currentDir = Paths.get(".");
+
+        // Determine working directory (prefer test subfolder)
+        Path workDir;
+        if (currentDir.resolve(TEST_DIR).toFile().exists()) {
+            workDir = currentDir.resolve(TEST_DIR);
+        } else {
+            workDir = currentDir;
+        }
+
+        // Set up system properties
+        Map<String, String> props = new HashMap<>();
+        if (properties != null) {
+            for (String prop : properties) {
+                String[] parts = prop.split("=", 2);
+                if (parts.length == 2) {
+                    props.put(parts[0], parts[1]);
+                }
+            }
+        }
+
+        // Apply system properties
+        for (Map.Entry<String, String> entry : props.entrySet()) {
+            System.setProperty(entry.getKey(), entry.getValue());
+        }
+
+        // Clean and create work directory
+        File citrusWorkDir = new File(WORK_DIR);
+        removeDir(citrusWorkDir);
+        if (!citrusWorkDir.mkdirs()) {
+            printer().println("Failed to create working directory " + 
WORK_DIR);
+            return 1;
+        }
+
+        // Resolve test sources
+        List<String> testSources = new ArrayList<>();
+        resolveTests(files, testSources, workDir);
+
+        // If no explicit files given, scan current directory
+        if (testSources.isEmpty()) {
+            resolveTests(new String[] { workDir.toString() }, testSources, 
workDir);
+        }
+
+        if (testSources.isEmpty()) {
+            printer().println("No test files found");
+            return 1;
+        }
+
+        // Build test run configuration
+        TestRunConfiguration configuration = getRunConfiguration(testSources, 
workDir);
+
+        // Look up and run the test engine
+        TestEngine testEngine = TestEngine.lookup(configuration);
+        testEngine.run();
+
+        return 0;
+    }
+
+    /**
+     * Creates a test run configuration from the resolved test sources.
+     */
+    protected TestRunConfiguration getRunConfiguration(List<String> 
testSources, Path workDir) {
+        String ext = FileUtils.getFileExtension(testSources.get(0));
+
+        TestRunConfiguration configuration = new TestRunConfiguration();
+        configuration.setWorkDir(workDir.toAbsolutePath().toString());
+
+        if (!"default".equals(engine)) {
+            configuration.setEngine(engine);
+        } else if ("feature".equals(ext)) {
+            configuration.setEngine("cucumber");
+        }
+
+        configuration.setVerbose(verbose);
+
+        if (includes != null) {
+            configuration.setIncludes(includes);
+        }
+
+        // Add test sources
+        List<TestSource> sources = new ArrayList<>();
+        for (String source : testSources) {
+            String sourceExt = FileUtils.getFileExtension(source);
+            String baseName = FileUtils.getBaseName(new 
File(source).getName());
+            sources.add(new TestSource(sourceExt, baseName, source));
+        }
+        configuration.setTestSources(sources);
+
+        return configuration;
+    }
+
+    /**
+     * Resolves test file paths from the given arguments. Handles both 
individual files and directories.
+     */
+    private void resolveTests(String[] testArgs, List<String> resolved, Path 
workDir) {
+        if (testArgs == null) {
+            return;
+        }
+
+        for (String arg : testArgs) {
+            // Adjust path if it starts with test/ prefix and we're using the 
test subfolder
+            String filePath = arg;
+            if (filePath.startsWith(TEST_DIR + "/")) {
+                filePath = filePath.substring((TEST_DIR + "/").length());
+            }
+
+            File resolved0 = workDir.resolve(filePath).toFile();
+            if (!resolved0.exists()) {
+                resolved0 = new File(filePath);
+            }
+            final File testFile = resolved0;
+
+            if (testFile.isDirectory()) {
+                // Scan directory for test files
+                String[] dirFiles = testFile.list();
+                if (dirFiles != null) {
+                    String[] fullPaths = Arrays.stream(dirFiles)
+                            .filter(f -> !skipFile(f))
+                            .map(f -> new File(testFile, f).getPath())
+                            .toArray(String[]::new);
+                    resolveTests(fullPaths, resolved, workDir);
+                }
+            } else if (testFile.exists() && !skipFile(testFile.getName())) {
+                resolved.add(testFile.getPath());
+            }
+        }
+    }
+
+    /**
+     * Checks if a file should be skipped based on its extension.
+     */
+    private boolean skipFile(String fileName) {
+        if (fileName.startsWith(".")) {
+            return true;
+        }
+        String ext = FileUtils.getFileExtension(fileName);
+        return Arrays.stream(ACCEPTED_FILE_EXT).noneMatch(e -> e.equals(ext));
+    }
+
+    /**
+     * Recursively removes a directory.
+     */
+    private static void removeDir(File dir) {
+        if (dir.exists()) {
+            delete(dir);
+        }
+    }
+
+    private static void delete(File file) {
+        if (file.isDirectory()) {
+            File[] children = file.listFiles();
+            if (children != null) {
+                for (File child : children) {
+                    delete(child);
+                }
+            }
+        }
+        file.delete();
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-test/src/main/resources/templates/citrus-feature.tmpl
 
b/dsl/camel-jbang/camel-jbang-plugin-test/src/main/resources/templates/citrus-feature.tmpl
new file mode 100644
index 000000000000..88c4e347d8a1
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-test/src/main/resources/templates/citrus-feature.tmpl
@@ -0,0 +1,8 @@
+Feature: {{ .Name }}
+
+  Background:
+    Given variables
+      | message | "Citrus rocks!" |
+
+  Scenario: Print message
+    Then print '${message}'
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-test/src/main/resources/templates/citrus-groovy.tmpl
 
b/dsl/camel-jbang/camel-jbang-plugin-test/src/main/resources/templates/citrus-groovy.tmpl
new file mode 100644
index 000000000000..0acce60ab4cc
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-test/src/main/resources/templates/citrus-groovy.tmpl
@@ -0,0 +1,5 @@
+variables {
+    message = "Citrus rocks!"
+}
+
+$(echo('${message}'))
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-test/src/main/resources/templates/citrus-java.tmpl
 
b/dsl/camel-jbang/camel-jbang-plugin-test/src/main/resources/templates/citrus-java.tmpl
new file mode 100644
index 000000000000..f99d9dbac01e
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-test/src/main/resources/templates/citrus-java.tmpl
@@ -0,0 +1,21 @@
+import org.citrusframework.TestActionSupport;
+import org.citrusframework.TestCaseRunner;
+import org.citrusframework.annotations.CitrusResource;
+
+
+public class {{ .Name }} implements Runnable, TestActionSupport {
+
+    @CitrusResource
+    TestCaseRunner t;
+
+    @Override
+    public void run() {
+        t.given(
+            createVariables().variable("message", "Citrus rocks!")
+        );
+
+        t.then(
+            echo().message("${message}")
+        );
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-test/src/main/resources/templates/citrus-xml.tmpl
 
b/dsl/camel-jbang/camel-jbang-plugin-test/src/main/resources/templates/citrus-xml.tmpl
new file mode 100644
index 000000000000..e394315b7f6b
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-test/src/main/resources/templates/citrus-xml.tmpl
@@ -0,0 +1,12 @@
+<test name="{{ .Name }}" author="Citrus" status="FINAL" 
xmlns="http://citrusframework.org/schema/xml/testcase";
+      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+      xsi:schemaLocation="http://citrusframework.org/schema/xml/testcase 
http://citrusframework.org/schema/xml/testcase/citrus-testcase.xsd";>
+  <description>Sample test in XML</description>
+  <variables>
+    <variable name="message" value="Citrus rocks!"/>
+  </variables>
+
+  <actions>
+    <echo message="${message}"/>
+  </actions>
+</test>
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-test/src/main/resources/templates/citrus-yaml.tmpl
 
b/dsl/camel-jbang/camel-jbang-plugin-test/src/main/resources/templates/citrus-yaml.tmpl
new file mode 100644
index 000000000000..c88c44624115
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-test/src/main/resources/templates/citrus-yaml.tmpl
@@ -0,0 +1,10 @@
+name: {{ .Name }}
+author: Citrus
+status: FINAL
+description: Sample test in YAML
+variables:
+  - name: message
+    value: Citrus rocks!
+actions:
+  - echo:
+      message: "${message}"
diff --git a/dsl/camel-jbang/camel-launcher/pom.xml 
b/dsl/camel-jbang/camel-launcher/pom.xml
index 3d876889e110..5f5a2a0bdf95 100644
--- a/dsl/camel-jbang/camel-launcher/pom.xml
+++ b/dsl/camel-jbang/camel-launcher/pom.xml
@@ -66,12 +66,16 @@
         <!-- Pre-installed plugins -->
         <!-- the edit plugin is not pre-installed -->
         <!-- the route-parser plugin is not pre-installed -->
-        <!-- the citrus test plugin cannot be embedded -->
         <dependency>
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-jbang-plugin-generate</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-jbang-plugin-test</artifactId>
+            <version>${project.version}</version>
+        </dependency>
         <dependency>
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-jbang-plugin-kubernetes</artifactId>
diff --git 
a/dsl/camel-jbang/camel-launcher/src/main/java/org/apache/camel/dsl/jbang/launcher/CamelLauncherMain.java
 
b/dsl/camel-jbang/camel-launcher/src/main/java/org/apache/camel/dsl/jbang/launcher/CamelLauncherMain.java
index 594c916bfb81..8acfc37760ae 100644
--- 
a/dsl/camel-jbang/camel-launcher/src/main/java/org/apache/camel/dsl/jbang/launcher/CamelLauncherMain.java
+++ 
b/dsl/camel-jbang/camel-launcher/src/main/java/org/apache/camel/dsl/jbang/launcher/CamelLauncherMain.java
@@ -19,6 +19,7 @@ package org.apache.camel.dsl.jbang.launcher;
 import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
 import org.apache.camel.dsl.jbang.core.commands.generate.GeneratePlugin;
 import org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesPlugin;
+import org.apache.camel.dsl.jbang.core.commands.test.TestPlugin;
 import org.apache.camel.dsl.jbang.core.commands.validate.ValidatePlugin;
 import picocli.CommandLine;
 
@@ -32,6 +33,7 @@ public class CamelLauncherMain extends CamelJBangMain {
         // install embedded plugins
         new GeneratePlugin().customize(commandLine, this);
         new KubernetesPlugin().customize(commandLine, this);
+        new TestPlugin().customize(commandLine, this);
         new ValidatePlugin().customize(commandLine, this);
     }
 

Reply via email to