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

acosentino 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 49781eb8bfce CAMEL-23138 - Camel-jbang-mcp: Add 
camel_route_test_scaffold MCP tool (#21727)
49781eb8bfce is described below

commit 49781eb8bfce270aca951ce2923cc2977a98c41c
Author: Andrea Cosentino <[email protected]>
AuthorDate: Fri Mar 6 10:46:45 2026 +0100

    CAMEL-23138 - Camel-jbang-mcp: Add camel_route_test_scaffold MCP tool 
(#21727)
    
    * CAMEL-23138 - Camel-jbang-mcp: Add camel_route_test_scaffold MCP tool
    
    Add a new MCP tool that generates JUnit 5 test skeletons from YAML or
    XML Camel route definitions. The tool produces ready-to-adapt test
    classes with:
    
    - CamelTestSupport for main runtime or @CamelSpringBootTest for
      Spring Boot
    - Mock endpoints replacing non-trivial producers (kafka, http, etc.)
      while preserving log, direct, and seda endpoints
    - @RegisterExtension stubs for 20+ test-infra services (Kafka,
      Artemis/JMS, MongoDB, PostgreSQL, Cassandra, Elasticsearch, Redis,
      RabbitMQ, FTP, Consul, NATS, Pulsar, CouchDB, Infinispan, MinIO,
      Solr)
    - NotifyBuilder for timer-based routes, template.sendBody() for
      direct/seda consumers
    - Required Maven test dependency listing
    
    Includes 26 unit tests covering input validation, YAML/XML endpoint
    extraction, mock naming, runtime-specific code generation, test-infra
    detection with deduplication, and dependency resolution.
    
    Signed-off-by: Andrea Cosentino <[email protected]>
    
    * CAMEL-23138 - Camel-jbang-mcp: Add camel_route_test_scaffold MCP tool - 
Docs
    
    Signed-off-by: Andrea Cosentino <[email protected]>
    
    ---------
    
    Signed-off-by: Andrea Cosentino <[email protected]>
---
 .../modules/ROOT/pages/camel-jbang-mcp.adoc        |  49 +-
 .../jbang/core/commands/mcp/TestScaffoldTools.java | 726 +++++++++++++++++++++
 .../core/commands/mcp/TestScaffoldToolsTest.java   | 471 +++++++++++++
 3 files changed, 1245 insertions(+), 1 deletion(-)

diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
index d7d90a1acfc5..03863c00a25e 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
@@ -24,7 +24,7 @@ By default, the HTTP server is disabled. To enable it, set 
`quarkus.http.host-en
 
 == Available Tools
 
-The server exposes 26 tools organized into ten functional areas, plus 3 
prompts that provide structured
+The server exposes 27 tools organized into eleven functional areas, plus 3 
prompts that provide structured
 multi-step workflows.
 
 === Catalog Exploration
@@ -88,6 +88,24 @@ multi-step workflows.
   from the catalog, and returns structured context.
 |===
 
+=== Test Scaffolding
+
+[cols="1,3",options="header"]
+|===
+| Tool | Description
+
+| `camel_route_test_scaffold`
+| Generates a JUnit 5 test skeleton from a Camel route definition (YAML or 
XML). Accepts an optional `format`
+  (`yaml` or `xml`, default `yaml`) and `runtime` (`main` or `spring-boot`, 
default `main`). For `main` runtime,
+  the generated test extends `CamelTestSupport`; for `spring-boot`, it uses 
`@CamelSpringBootTest` with
+  `@SpringBootTest`. The tool replaces non-trivial producer endpoints with 
mock endpoints, generates
+  `@RegisterExtension` stubs for infrastructure components (Kafka, 
JMS/Artemis, MongoDB, PostgreSQL, Cassandra,
+  Elasticsearch, Redis, RabbitMQ, FTP, Consul, NATS, Pulsar, CouchDB, 
Infinispan, MinIO, Solr), and produces
+  a `NotifyBuilder` pattern for timer-based routes or `template.sendBody()` 
for direct/seda consumers. Returns
+  the generated test code, detected components, mock endpoint mappings, 
test-infra services, and required Maven
+  test dependencies.
+|===
+
 === Security Analysis
 
 [cols="1,3",options="header"]
@@ -453,6 +471,35 @@ The assistant calls `camel_error_diagnose` which 
identifies all three exceptions
 `kafka` component, and returns common causes (missing `camel-kafka` 
dependency, typo in URI scheme), suggested
 fixes (add the dependency, verify the URI), and links to the relevant Camel 
documentation.
 
+=== Generating a Test Skeleton
+
+----
+Generate a JUnit 5 test for this route:
+
+- route:
+    from:
+      uri: kafka:orders
+      steps:
+        - marshal:
+            json: {}
+        - to: mongodb:myDb?collection=orders&operation=insert
+----
+
+The assistant calls `camel_route_test_scaffold` with the route content. It 
detects the `kafka` and `mongodb`
+components, generates a test class extending `CamelTestSupport` with 
`@RegisterExtension` stubs for both
+`KafkaService` and `MongoDBService`, replaces the `mongodb` producer with a 
mock endpoint, and returns the
+generated test code along with the required Maven test dependencies 
(`camel-test-junit5`, `camel-mock`,
+`camel-test-infra-kafka`, `camel-test-infra-mongodb`).
+
+For Spring Boot projects, specify the runtime:
+
+----
+Generate a Spring Boot test for this Kafka-to-MongoDB route
+----
+
+The tool generates a test class annotated with `@CamelSpringBootTest` and 
`@SpringBootTest`, with
+`CamelContext` and `ProducerTemplate` injected via `@Autowired`.
+
 === Checking Dependency Hygiene
 
 ----
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/TestScaffoldTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/TestScaffoldTools.java
new file mode 100644
index 000000000000..39e7cace3d70
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/TestScaffoldTools.java
@@ -0,0 +1,726 @@
+/*
+ * 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.mcp;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import jakarta.enterprise.context.ApplicationScoped;
+
+import io.quarkiverse.mcp.server.Tool;
+import io.quarkiverse.mcp.server.ToolArg;
+import io.quarkiverse.mcp.server.ToolCallException;
+import org.apache.camel.catalog.CamelCatalog;
+import org.apache.camel.catalog.DefaultCamelCatalog;
+import org.apache.camel.tooling.model.ComponentModel;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+
+/**
+ * MCP Tool for generating JUnit 5 test scaffolding for Camel routes.
+ * <p>
+ * Given a YAML or XML route definition, this tool produces a test class 
skeleton with CamelTestSupport (or
+ * {@code @CamelSpringBootTest} for Spring Boot), mock endpoints for 
producers, and {@code @RegisterExtension} stubs for
+ * infrastructure components like Kafka, JMS, MongoDB, etc.
+ */
+@ApplicationScoped
+public class TestScaffoldTools {
+
+    // YAML endpoint extraction patterns
+    private static final Pattern YAML_FROM_URI
+            = Pattern.compile("from:\\s*\\n\\s+uri:\\s*[\"']?([^\"'\\s#]+)", 
Pattern.MULTILINE);
+    private static final Pattern YAML_FROM_INLINE
+            = 
Pattern.compile("from:\\s+[\"']?([a-zA-Z][a-zA-Z0-9+.-]*:[^\"'\\s#]+)", 
Pattern.MULTILINE);
+    private static final Pattern YAML_TO_URI
+            = 
Pattern.compile("-\\s+to:\\s*\\n\\s+uri:\\s*[\"']?([^\"'\\s#]+)", 
Pattern.MULTILINE);
+    private static final Pattern YAML_TO_INLINE
+            = 
Pattern.compile("-\\s+to:\\s+[\"']?([a-zA-Z][a-zA-Z0-9+.-]*:[^\"'\\s#]+)", 
Pattern.MULTILINE);
+    private static final Pattern YAML_TOD_URI
+            = 
Pattern.compile("-\\s+toD:\\s*\\n\\s+uri:\\s*[\"']?([^\"'\\s#]+)", 
Pattern.MULTILINE);
+    private static final Pattern YAML_TOD_INLINE
+            = 
Pattern.compile("-\\s+toD:\\s+[\"']?([a-zA-Z][a-zA-Z0-9+.-]*:[^\"'\\s#]+)", 
Pattern.MULTILINE);
+
+    // XML endpoint extraction patterns
+    private static final Pattern XML_FROM = 
Pattern.compile("<from\\s+uri=[\"']([^\"']+)[\"']", Pattern.CASE_INSENSITIVE);
+    private static final Pattern XML_TO = 
Pattern.compile("<to\\s+uri=[\"']([^\"']+)[\"']", Pattern.CASE_INSENSITIVE);
+    private static final Pattern XML_TOD = 
Pattern.compile("<toD\\s+uri=[\"']([^\"']+)[\"']", Pattern.CASE_INSENSITIVE);
+
+    /** Component schemes that are trivial and should not be replaced with 
mocks. */
+    private static final Set<String> TRIVIAL_SCHEMES = Set.of("log", "direct", 
"seda", "mock", "controlbus", "stub");
+
+    /** Component schemes that are internally triggered (user can send 
messages to them). */
+    private static final Set<String> SENDABLE_SCHEMES = Set.of("direct", 
"seda");
+
+    private static final Map<String, TestInfraInfo> TEST_INFRA_MAP = 
buildTestInfraMap();
+
+    private final CamelCatalog catalog;
+
+    public TestScaffoldTools() {
+        this.catalog = new DefaultCamelCatalog();
+    }
+
+    /**
+     * Tool to generate a JUnit 5 test skeleton for a Camel route.
+     */
+    @Tool(description = "Generate a JUnit 5 test skeleton for a Camel route. "
+                        + "Given a YAML or XML route definition, produces a 
test class with "
+                        + "CamelTestSupport or @CamelSpringBootTest 
boilerplate, "
+                        + "mock endpoints for producer endpoints, MockEndpoint 
assertions, "
+                        + "and @RegisterExtension stubs for infrastructure 
components (Kafka, AWS, etc.).")
+    public String camel_route_test_scaffold(
+            @ToolArg(description = "The Camel route definition (YAML or XML)") 
String route,
+            @ToolArg(description = "Route format: yaml or xml (default: 
yaml)") String format,
+            @ToolArg(description = "Target runtime: main or spring-boot 
(default: main)") String runtime) {
+
+        if (route == null || route.isBlank()) {
+            throw new ToolCallException("Route content is required", null);
+        }
+
+        try {
+            String resolvedFormat = resolveFormat(format);
+            String resolvedRuntime = resolveRuntime(runtime);
+
+            // Extract endpoints from route
+            List<String> fromEndpoints = extractFromEndpoints(route, 
resolvedFormat);
+            List<String> toEndpoints = extractToEndpoints(route, 
resolvedFormat);
+
+            // Collect all unique component schemes
+            List<String> allSchemes = collectSchemes(fromEndpoints, 
toEndpoints);
+
+            // Determine which to endpoints need mock replacements
+            List<String> mockEndpoints = toEndpoints.stream()
+                    .filter(uri -> {
+                        String scheme = extractScheme(uri);
+                        return scheme != null && 
!TRIVIAL_SCHEMES.contains(scheme);
+                    })
+                    .toList();
+
+            // Determine test-infra services needed (deduplicated)
+            List<TestInfraInfo> infraServices = 
resolveInfraServices(allSchemes);
+
+            // Generate test code
+            String testCode = "spring-boot".equals(resolvedRuntime)
+                    ? generateSpringBootTest(fromEndpoints, mockEndpoints, 
infraServices)
+                    : generateMainTest(fromEndpoints, mockEndpoints, 
infraServices);
+
+            // Build JSON result
+            return buildResult(testCode, resolvedFormat, resolvedRuntime,
+                    allSchemes, fromEndpoints, toEndpoints, mockEndpoints, 
infraServices);
+
+        } catch (ToolCallException e) {
+            throw e;
+        } catch (Throwable e) {
+            throw new ToolCallException(
+                    "Failed to generate test scaffold (" + 
e.getClass().getName() + "): " + e.getMessage(), null);
+        }
+    }
+
+    // ---- Endpoint extraction ----
+
+    List<String> extractFromEndpoints(String route, String format) {
+        List<String> endpoints = new ArrayList<>();
+        if ("xml".equals(format)) {
+            addMatches(endpoints, XML_FROM, route);
+        } else {
+            addMatches(endpoints, YAML_FROM_URI, route);
+            if (endpoints.isEmpty()) {
+                addMatches(endpoints, YAML_FROM_INLINE, route);
+            }
+        }
+        return endpoints;
+    }
+
+    List<String> extractToEndpoints(String route, String format) {
+        List<String> endpoints = new ArrayList<>();
+        if ("xml".equals(format)) {
+            addMatches(endpoints, XML_TO, route);
+            addMatches(endpoints, XML_TOD, route);
+        } else {
+            addMatches(endpoints, YAML_TO_URI, route);
+            addMatches(endpoints, YAML_TO_INLINE, route);
+            addMatches(endpoints, YAML_TOD_URI, route);
+            addMatches(endpoints, YAML_TOD_INLINE, route);
+        }
+        return endpoints;
+    }
+
+    private void addMatches(List<String> list, Pattern pattern, String input) {
+        Matcher m = pattern.matcher(input);
+        while (m.find()) {
+            String uri = m.group(1).trim();
+            // Remove trailing quotes if present
+            if (uri.endsWith("\"") || uri.endsWith("'")) {
+                uri = uri.substring(0, uri.length() - 1);
+            }
+            if (!uri.isEmpty()) {
+                list.add(uri);
+            }
+        }
+    }
+
+    // ---- Scheme extraction and mock naming ----
+
+    String extractScheme(String uri) {
+        if (uri == null || uri.isEmpty()) {
+            return null;
+        }
+        // Handle https:// and http:// specially
+        if (uri.startsWith("https://";)) {
+            return "https";
+        }
+        if (uri.startsWith("http://";)) {
+            return "http";
+        }
+        int idx = uri.indexOf(':');
+        if (idx > 0) {
+            return uri.substring(0, idx);
+        }
+        return null;
+    }
+
+    String toMockName(String uri) {
+        String scheme = extractScheme(uri);
+        if (scheme == null) {
+            return "mock:unknown";
+        }
+
+        // Extract path part (after scheme: and optional //)
+        String rest = uri.substring(scheme.length());
+        if (rest.startsWith("://")) {
+            rest = rest.substring(3);
+        } else if (rest.startsWith(":")) {
+            rest = rest.substring(1);
+        }
+
+        // Remove query parameters
+        int qIdx = rest.indexOf('?');
+        if (qIdx >= 0) {
+            rest = rest.substring(0, qIdx);
+        }
+
+        // Sanitize: replace non-alphanumeric with hyphens, collapse, trim
+        String sanitized = rest.replaceAll("[^a-zA-Z0-9]", "-")
+                .replaceAll("-+", "-")
+                .replaceAll("^-|-$", "");
+
+        if (sanitized.isEmpty()) {
+            return "mock:" + scheme;
+        }
+        return "mock:" + scheme + "-" + sanitized;
+    }
+
+    String toMockVariableName(String mockUri) {
+        // mock:kafka-orders -> mockKafkaOrders
+        String name = mockUri.replace("mock:", "");
+        StringBuilder sb = new StringBuilder("mock");
+        boolean capitalizeNext = true;
+        for (char c : name.toCharArray()) {
+            if (c == '-' || c == '_') {
+                capitalizeNext = true;
+            } else if (capitalizeNext) {
+                sb.append(Character.toUpperCase(c));
+                capitalizeNext = false;
+            } else {
+                sb.append(c);
+                capitalizeNext = false;
+            }
+        }
+        return sb.toString();
+    }
+
+    // ---- Test-infra resolution ----
+
+    private List<TestInfraInfo> resolveInfraServices(List<String> schemes) {
+        Set<TestInfraInfo> seen = new LinkedHashSet<>();
+        for (String scheme : schemes) {
+            TestInfraInfo info = TEST_INFRA_MAP.get(scheme);
+            if (info != null) {
+                seen.add(info);
+            }
+        }
+        return new ArrayList<>(seen);
+    }
+
+    // ---- Code generation ----
+
+    private String generateMainTest(
+            List<String> fromEndpoints, List<String> mockEndpoints,
+            List<TestInfraInfo> infraServices) {
+
+        StringBuilder sb = new StringBuilder();
+
+        // Imports
+        sb.append("import org.apache.camel.RoutesBuilder;\n");
+        sb.append("import org.apache.camel.builder.RouteBuilder;\n");
+        if (!mockEndpoints.isEmpty()) {
+            sb.append("import 
org.apache.camel.component.mock.MockEndpoint;\n");
+        }
+        sb.append("import org.apache.camel.test.junit5.CamelTestSupport;\n");
+        if (usesNotifyBuilder(fromEndpoints)) {
+            sb.append("import org.apache.camel.builder.NotifyBuilder;\n");
+            sb.append("import java.util.concurrent.TimeUnit;\n");
+        }
+        sb.append("import org.junit.jupiter.api.Test;\n");
+        for (TestInfraInfo info : infraServices) {
+            sb.append("import 
org.junit.jupiter.api.extension.RegisterExtension;\n");
+            break;
+        }
+        for (TestInfraInfo info : infraServices) {
+            sb.append("import 
").append(info.packageName).append('.').append(info.serviceClass).append(";\n");
+            sb.append("import 
").append(info.packageName).append('.').append(info.factoryClass).append(";\n");
+        }
+        sb.append("\n");
+        sb.append("import static 
org.junit.jupiter.api.Assertions.assertTrue;\n");
+        sb.append("\n");
+
+        // Class declaration
+        sb.append("class RouteTest extends CamelTestSupport {\n\n");
+
+        // @RegisterExtension fields
+        for (TestInfraInfo info : infraServices) {
+            String fieldName = 
Character.toLowerCase(info.serviceClass.charAt(0)) + 
info.serviceClass.substring(1);
+            sb.append("    @RegisterExtension\n");
+            sb.append("    static ").append(info.serviceClass).append(' 
').append(fieldName);
+            sb.append(" = 
").append(info.factoryClass).append(".createService();\n\n");
+        }
+
+        // createRouteBuilder
+        sb.append("    @Override\n");
+        sb.append("    protected RoutesBuilder createRouteBuilder() {\n");
+        sb.append("        return new RouteBuilder() {\n");
+        sb.append("            @Override\n");
+        sb.append("            public void configure() {\n");
+
+        String fromUri = fromEndpoints.isEmpty() ? "direct:start" : 
fromEndpoints.get(0);
+        String fromScheme = extractScheme(fromUri);
+        boolean isSendable = fromScheme != null && 
SENDABLE_SCHEMES.contains(fromScheme);
+        boolean isTimer = "timer".equals(fromScheme);
+
+        if (!isSendable && !isTimer && !fromEndpoints.isEmpty()) {
+            sb.append("                // TODO: Replace with actual consumer 
URI or use direct:start for unit testing\n");
+        }
+        sb.append("                
from(\"").append(escapeJava(fromUri)).append("\")\n");
+
+        if (mockEndpoints.isEmpty()) {
+            sb.append("                    .log(\"Route executed\");\n");
+        } else {
+            for (int i = 0; i < mockEndpoints.size(); i++) {
+                String mockName = toMockName(mockEndpoints.get(i));
+                String originalUri = mockEndpoints.get(i);
+                sb.append("                    
.to(\"").append(escapeJava(mockName)).append("\")");
+                sb.append(" // replaces 
").append(extractScheme(originalUri)).append(":...");
+                if (i == mockEndpoints.size() - 1) {
+                    sb.append(";\n");
+                } else {
+                    sb.append("\n");
+                }
+            }
+        }
+
+        sb.append("            }\n");
+        sb.append("        };\n");
+        sb.append("    }\n\n");
+
+        // Test method
+        sb.append("    @Test\n");
+        sb.append("    void testRoute() throws Exception {\n");
+
+        // Mock endpoint declarations
+        for (String mockUri : mockEndpoints) {
+            String mockName = toMockName(mockUri);
+            String varName = toMockVariableName(mockName);
+            sb.append("        MockEndpoint ").append(varName).append(" = 
getMockEndpoint(\"");
+            sb.append(escapeJava(mockName)).append("\");\n");
+            sb.append("        
").append(varName).append(".expectedMinimumMessageCount(1);\n\n");
+        }
+
+        // Send or wait
+        if (isTimer) {
+            sb.append("        // Timer route fires automatically; use 
NotifyBuilder to wait\n");
+            sb.append("        NotifyBuilder notify = new 
NotifyBuilder(context).whenDone(1).create();\n");
+            sb.append(
+                    "        assertTrue(notify.matches(10, TimeUnit.SECONDS), 
\"Route should complete within timeout\");\n\n");
+        } else if (isSendable) {
+            sb.append("        
template.sendBody(\"").append(escapeJava(fromUri)).append("\", \"test 
message\");\n\n");
+        } else {
+            sb.append("        // TODO: Send a test message to trigger the 
route\n");
+            sb.append("        // 
template.sendBody(\"").append(escapeJava(fromUri)).append("\", \"test 
message\");\n\n");
+        }
+
+        // Assert
+        if (!mockEndpoints.isEmpty()) {
+            sb.append("        MockEndpoint.assertIsSatisfied(context);\n");
+        }
+
+        sb.append("    }\n");
+        sb.append("}\n");
+
+        return sb.toString();
+    }
+
+    private String generateSpringBootTest(
+            List<String> fromEndpoints, List<String> mockEndpoints,
+            List<TestInfraInfo> infraServices) {
+
+        StringBuilder sb = new StringBuilder();
+
+        // Imports
+        sb.append("import org.apache.camel.CamelContext;\n");
+        sb.append("import org.apache.camel.ProducerTemplate;\n");
+        if (!mockEndpoints.isEmpty()) {
+            sb.append("import 
org.apache.camel.component.mock.MockEndpoint;\n");
+        }
+        sb.append("import 
org.apache.camel.test.spring.junit5.CamelSpringBootTest;\n");
+        if (usesNotifyBuilder(fromEndpoints)) {
+            sb.append("import org.apache.camel.builder.NotifyBuilder;\n");
+            sb.append("import java.util.concurrent.TimeUnit;\n");
+        }
+        sb.append("import org.junit.jupiter.api.Test;\n");
+        sb.append("import 
org.springframework.beans.factory.annotation.Autowired;\n");
+        sb.append("import 
org.springframework.boot.test.context.SpringBootTest;\n");
+        for (TestInfraInfo info : infraServices) {
+            sb.append("import 
org.junit.jupiter.api.extension.RegisterExtension;\n");
+            break;
+        }
+        for (TestInfraInfo info : infraServices) {
+            sb.append("import 
").append(info.packageName).append('.').append(info.serviceClass).append(";\n");
+            sb.append("import 
").append(info.packageName).append('.').append(info.factoryClass).append(";\n");
+        }
+        sb.append("\n");
+        sb.append("import static 
org.junit.jupiter.api.Assertions.assertTrue;\n");
+        sb.append("\n");
+
+        // Class declaration
+        sb.append("@CamelSpringBootTest\n");
+        sb.append("@SpringBootTest\n");
+        sb.append("class RouteTest {\n\n");
+
+        // Injected fields
+        sb.append("    @Autowired\n");
+        sb.append("    private CamelContext context;\n\n");
+        sb.append("    @Autowired\n");
+        sb.append("    private ProducerTemplate template;\n\n");
+
+        // @RegisterExtension fields
+        for (TestInfraInfo info : infraServices) {
+            String fieldName = 
Character.toLowerCase(info.serviceClass.charAt(0)) + 
info.serviceClass.substring(1);
+            sb.append("    @RegisterExtension\n");
+            sb.append("    static ").append(info.serviceClass).append(' 
').append(fieldName);
+            sb.append(" = 
").append(info.factoryClass).append(".createService();\n\n");
+        }
+
+        // Test method
+        sb.append("    @Test\n");
+        sb.append("    void testRoute() throws Exception {\n");
+
+        // Mock endpoint declarations
+        for (String mockUri : mockEndpoints) {
+            String mockName = toMockName(mockUri);
+            String varName = toMockVariableName(mockName);
+            sb.append("        // TODO: Use AdviceWith or 
@MockEndpointsAndSkip to intercept endpoints\n");
+            sb.append("        MockEndpoint ").append(varName);
+            sb.append(" = 
context.getEndpoint(\"").append(escapeJava(mockName));
+            sb.append("\", MockEndpoint.class);\n");
+            sb.append("        
").append(varName).append(".expectedMinimumMessageCount(1);\n\n");
+        }
+
+        // Send or wait
+        String fromUri = fromEndpoints.isEmpty() ? "direct:start" : 
fromEndpoints.get(0);
+        String fromScheme = extractScheme(fromUri);
+        boolean isSendable = fromScheme != null && 
SENDABLE_SCHEMES.contains(fromScheme);
+        boolean isTimer = "timer".equals(fromScheme);
+
+        if (isTimer) {
+            sb.append("        // Timer route fires automatically; use 
NotifyBuilder to wait\n");
+            sb.append("        NotifyBuilder notify = new 
NotifyBuilder(context).whenDone(1).create();\n");
+            sb.append(
+                    "        assertTrue(notify.matches(10, TimeUnit.SECONDS), 
\"Route should complete within timeout\");\n\n");
+        } else if (isSendable) {
+            sb.append("        
template.sendBody(\"").append(escapeJava(fromUri)).append("\", \"test 
message\");\n\n");
+        } else {
+            sb.append("        // TODO: Send a test message to trigger the 
route\n");
+            sb.append("        // template.sendBody(\"direct:start\", \"test 
message\");\n\n");
+        }
+
+        // Assert
+        if (!mockEndpoints.isEmpty()) {
+            sb.append("        MockEndpoint.assertIsSatisfied(context);\n");
+        }
+
+        sb.append("    }\n");
+        sb.append("}\n");
+
+        return sb.toString();
+    }
+
+    // ---- Helpers ----
+
+    private boolean usesNotifyBuilder(List<String> fromEndpoints) {
+        if (fromEndpoints.isEmpty()) {
+            return false;
+        }
+        String scheme = extractScheme(fromEndpoints.get(0));
+        return "timer".equals(scheme);
+    }
+
+    private List<String> collectSchemes(List<String> fromEndpoints, 
List<String> toEndpoints) {
+        List<String> schemes = new ArrayList<>();
+        for (String uri : fromEndpoints) {
+            String s = extractScheme(uri);
+            if (s != null && !schemes.contains(s)) {
+                schemes.add(s);
+            }
+        }
+        for (String uri : toEndpoints) {
+            String s = extractScheme(uri);
+            if (s != null && !schemes.contains(s)) {
+                schemes.add(s);
+            }
+        }
+        return schemes;
+    }
+
+    private String resolveFormat(String format) {
+        return format != null && !format.isBlank() ? format.toLowerCase() : 
"yaml";
+    }
+
+    private String resolveRuntime(String runtime) {
+        return runtime != null && !runtime.isBlank() ? runtime.toLowerCase() : 
"main";
+    }
+
+    private String escapeJava(String s) {
+        return s.replace("\\", "\\\\").replace("\"", "\\\"");
+    }
+
+    // ---- JSON result builder ----
+
+    private String buildResult(
+            String testCode, String format, String runtime,
+            List<String> allSchemes, List<String> fromEndpoints, List<String> 
toEndpoints,
+            List<String> mockEndpoints, List<TestInfraInfo> infraServices) {
+
+        JsonObject result = new JsonObject();
+        result.put("testCode", testCode);
+        result.put("runtime", runtime);
+        result.put("format", format);
+
+        // Detected components
+        JsonArray componentsJson = new JsonArray();
+        for (String scheme : allSchemes) {
+            JsonObject comp = new JsonObject();
+            comp.put("scheme", scheme);
+            ComponentModel model = catalog.componentModel(scheme);
+            if (model != null) {
+                comp.put("title", model.getTitle());
+                comp.put("producerOnly", model.isProducerOnly());
+                comp.put("consumerOnly", model.isConsumerOnly());
+            }
+            componentsJson.add(comp);
+        }
+        result.put("detectedComponents", componentsJson);
+
+        // Mock endpoints
+        JsonArray mocksJson = new JsonArray();
+        for (String uri : mockEndpoints) {
+            JsonObject mockObj = new JsonObject();
+            mockObj.put("originalUri", uri);
+            mockObj.put("mockUri", toMockName(uri));
+            mocksJson.add(mockObj);
+        }
+        result.put("mockEndpoints", mocksJson);
+
+        // Test-infra services
+        JsonArray infraJson = new JsonArray();
+        for (TestInfraInfo info : infraServices) {
+            JsonObject infraObj = new JsonObject();
+            infraObj.put("service", info.serviceClass);
+            infraObj.put("factory", info.factoryClass);
+            infraObj.put("artifactId", info.artifactId);
+            infraObj.put("package", info.packageName);
+            infraJson.add(infraObj);
+        }
+        result.put("testInfraServices", infraJson);
+
+        // Maven dependencies
+        JsonArray depsJson = new JsonArray();
+        addDependency(depsJson, "camel-test-junit5");
+        if (!mockEndpoints.isEmpty()) {
+            addDependency(depsJson, "camel-mock");
+        }
+        for (TestInfraInfo info : infraServices) {
+            addDependency(depsJson, info.artifactId);
+        }
+        if ("spring-boot".equals(runtime)) {
+            addDependency(depsJson, "camel-test-spring-junit5");
+        }
+        result.put("mavenDependencies", depsJson);
+
+        // Summary
+        JsonObject summary = new JsonObject();
+        summary.put("componentCount", allSchemes.size());
+        summary.put("mockEndpointCount", mockEndpoints.size());
+        summary.put("testInfraServiceCount", infraServices.size());
+        summary.put("fromEndpointCount", fromEndpoints.size());
+        summary.put("toEndpointCount", toEndpoints.size());
+        result.put("summary", summary);
+
+        return result.toJson();
+    }
+
+    private void addDependency(JsonArray array, String artifactId) {
+        JsonObject dep = new JsonObject();
+        dep.put("groupId", "org.apache.camel");
+        dep.put("artifactId", artifactId);
+        dep.put("scope", "test");
+        array.add(dep);
+    }
+
+    // ---- Test-infra mapping ----
+
+    private static Map<String, TestInfraInfo> buildTestInfraMap() {
+        Map<String, TestInfraInfo> map = new LinkedHashMap<>();
+
+        TestInfraInfo kafka = new TestInfraInfo(
+                "KafkaService", "KafkaServiceFactory",
+                "camel-test-infra-kafka", 
"org.apache.camel.test.infra.kafka.services");
+        map.put("kafka", kafka);
+
+        TestInfraInfo artemis = new TestInfraInfo(
+                "ArtemisService", "ArtemisServiceFactory",
+                "camel-test-infra-artemis", 
"org.apache.camel.test.infra.artemis.services");
+        map.put("jms", artemis);
+        map.put("activemq", artemis);
+        map.put("sjms", artemis);
+        map.put("sjms2", artemis);
+        map.put("amqp", artemis);
+
+        TestInfraInfo mongodb = new TestInfraInfo(
+                "MongoDBService", "MongoDBServiceFactory",
+                "camel-test-infra-mongodb", 
"org.apache.camel.test.infra.mongodb.services");
+        map.put("mongodb", mongodb);
+
+        TestInfraInfo postgres = new TestInfraInfo(
+                "PostgresService", "PostgresServiceFactory",
+                "camel-test-infra-postgres", 
"org.apache.camel.test.infra.postgres.services");
+        map.put("sql", postgres);
+        map.put("jdbc", postgres);
+
+        TestInfraInfo cassandra = new TestInfraInfo(
+                "CassandraService", "CassandraServiceFactory",
+                "camel-test-infra-cassandra", 
"org.apache.camel.test.infra.cassandra.services");
+        map.put("cql", cassandra);
+
+        TestInfraInfo elasticsearch = new TestInfraInfo(
+                "ElasticSearchService", "ElasticSearchServiceFactory",
+                "camel-test-infra-elasticsearch", 
"org.apache.camel.test.infra.elasticsearch.services");
+        map.put("elasticsearch", elasticsearch);
+        map.put("elasticsearch-rest", elasticsearch);
+
+        TestInfraInfo redis = new TestInfraInfo(
+                "RedisService", "RedisServiceFactory",
+                "camel-test-infra-redis", 
"org.apache.camel.test.infra.redis.services");
+        map.put("spring-redis", redis);
+
+        TestInfraInfo rabbitmq = new TestInfraInfo(
+                "RabbitMQService", "RabbitMQServiceFactory",
+                "camel-test-infra-rabbitmq", 
"org.apache.camel.test.infra.rabbitmq.services");
+        map.put("rabbitmq", rabbitmq);
+
+        TestInfraInfo ftp = new TestInfraInfo(
+                "FtpService", "FtpServiceFactory",
+                "camel-test-infra-ftp", 
"org.apache.camel.test.infra.ftp.services");
+        map.put("ftp", ftp);
+        map.put("sftp", ftp);
+        map.put("ftps", ftp);
+
+        TestInfraInfo consul = new TestInfraInfo(
+                "ConsulService", "ConsulServiceFactory",
+                "camel-test-infra-consul", 
"org.apache.camel.test.infra.consul.services");
+        map.put("consul", consul);
+
+        TestInfraInfo nats = new TestInfraInfo(
+                "NatsService", "NatsServiceFactory",
+                "camel-test-infra-nats", 
"org.apache.camel.test.infra.nats.services");
+        map.put("nats", nats);
+
+        TestInfraInfo pulsar = new TestInfraInfo(
+                "PulsarService", "PulsarServiceFactory",
+                "camel-test-infra-pulsar", 
"org.apache.camel.test.infra.pulsar.services");
+        map.put("pulsar", pulsar);
+
+        TestInfraInfo couchdb = new TestInfraInfo(
+                "CouchDbService", "CouchDbServiceFactory",
+                "camel-test-infra-couchdb", 
"org.apache.camel.test.infra.couchdb.services");
+        map.put("couchdb", couchdb);
+
+        TestInfraInfo infinispan = new TestInfraInfo(
+                "InfinispanService", "InfinispanServiceFactory",
+                "camel-test-infra-infinispan", 
"org.apache.camel.test.infra.infinispan.services");
+        map.put("infinispan", infinispan);
+
+        TestInfraInfo minio = new TestInfraInfo(
+                "MinioService", "MinioServiceFactory",
+                "camel-test-infra-minio", 
"org.apache.camel.test.infra.minio.services");
+        map.put("minio", minio);
+
+        TestInfraInfo solr = new TestInfraInfo(
+                "SolrService", "SolrServiceFactory",
+                "camel-test-infra-solr", 
"org.apache.camel.test.infra.solr.services");
+        map.put("solr", solr);
+
+        return map;
+    }
+
+    /**
+     * Holds test-infra service information for a component.
+     */
+    static final class TestInfraInfo {
+        final String serviceClass;
+        final String factoryClass;
+        final String artifactId;
+        final String packageName;
+
+        TestInfraInfo(String serviceClass, String factoryClass, String 
artifactId, String packageName) {
+            this.serviceClass = serviceClass;
+            this.factoryClass = factoryClass;
+            this.artifactId = artifactId;
+            this.packageName = packageName;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (!(o instanceof TestInfraInfo that)) {
+                return false;
+            }
+            return artifactId.equals(that.artifactId);
+        }
+
+        @Override
+        public int hashCode() {
+            return artifactId.hashCode();
+        }
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/TestScaffoldToolsTest.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/TestScaffoldToolsTest.java
new file mode 100644
index 000000000000..1f680d47aaf9
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/TestScaffoldToolsTest.java
@@ -0,0 +1,471 @@
+/*
+ * 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.mcp;
+
+import java.util.List;
+
+import io.quarkiverse.mcp.server.ToolCallException;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class TestScaffoldToolsTest {
+
+    private final TestScaffoldTools tools = new TestScaffoldTools();
+
+    // ---- Input validation ----
+
+    @Test
+    void nullRouteThrows() {
+        assertThatThrownBy(() -> tools.camel_route_test_scaffold(null, null, 
null))
+                .isInstanceOf(ToolCallException.class)
+                .hasMessageContaining("required");
+    }
+
+    @Test
+    void blankRouteThrows() {
+        assertThatThrownBy(() -> tools.camel_route_test_scaffold("   ", 
"yaml", "main"))
+                .isInstanceOf(ToolCallException.class)
+                .hasMessageContaining("required");
+    }
+
+    // ---- YAML endpoint extraction ----
+
+    @Test
+    void extractsFromEndpointFromYaml() {
+        String route = """
+                - route:
+                    from:
+                      uri: timer:tick?period=5000
+                      steps:
+                        - to: log:done
+                """;
+
+        List<String> from = tools.extractFromEndpoints(route, "yaml");
+        assertThat(from).containsExactly("timer:tick?period=5000");
+    }
+
+    @Test
+    void extractsToEndpointsFromYaml() {
+        String route = """
+                - route:
+                    from:
+                      uri: direct:start
+                      steps:
+                        - to: kafka:orders?brokers=localhost:9092
+                        - to: log:done
+                """;
+
+        List<String> to = tools.extractToEndpoints(route, "yaml");
+        assertThat(to).containsExactly("kafka:orders?brokers=localhost:9092", 
"log:done");
+    }
+
+    @Test
+    void extractsQuotedYamlEndpoints() {
+        String route = """
+                - route:
+                    from:
+                      uri: "direct:start"
+                      steps:
+                        - to: "kafka:orders?brokers=localhost:9092"
+                """;
+
+        List<String> from = tools.extractFromEndpoints(route, "yaml");
+        assertThat(from).hasSize(1);
+        assertThat(from.get(0)).startsWith("direct:start");
+
+        List<String> to = tools.extractToEndpoints(route, "yaml");
+        assertThat(to).hasSize(1);
+        assertThat(to.get(0)).startsWith("kafka:orders");
+    }
+
+    // ---- XML endpoint extraction ----
+
+    @Test
+    void extractsFromEndpointFromXml() {
+        String route = """
+                <route>
+                  <from uri="timer:tick?period=5000"/>
+                  <to uri="log:done"/>
+                </route>
+                """;
+
+        List<String> from = tools.extractFromEndpoints(route, "xml");
+        assertThat(from).containsExactly("timer:tick?period=5000");
+    }
+
+    @Test
+    void extractsToEndpointsFromXml() {
+        String route = """
+                <route>
+                  <from uri="direct:start"/>
+                  <to uri="kafka:orders?brokers=localhost:9092"/>
+                  <to uri="log:done"/>
+                </route>
+                """;
+
+        List<String> to = tools.extractToEndpoints(route, "xml");
+        assertThat(to).containsExactly("kafka:orders?brokers=localhost:9092", 
"log:done");
+    }
+
+    @Test
+    void extractsToDEndpointFromXml() {
+        String route = """
+                <route>
+                  <from uri="direct:start"/>
+                  <toD uri="http:api.example.com/notify"/>
+                </route>
+                """;
+
+        List<String> to = tools.extractToEndpoints(route, "xml");
+        assertThat(to).containsExactly("http:api.example.com/notify");
+    }
+
+    // ---- Scheme extraction ----
+
+    @Test
+    void extractsSchemeFromUri() {
+        
assertThat(tools.extractScheme("kafka:orders?brokers=localhost")).isEqualTo("kafka");
+        assertThat(tools.extractScheme("timer:tick")).isEqualTo("timer");
+        assertThat(tools.extractScheme("direct:start")).isEqualTo("direct");
+        
assertThat(tools.extractScheme("https://api.example.com";)).isEqualTo("https");
+        
assertThat(tools.extractScheme("http://api.example.com";)).isEqualTo("http");
+    }
+
+    @Test
+    void extractsSchemeReturnsNullForInvalid() {
+        assertThat(tools.extractScheme(null)).isNull();
+        assertThat(tools.extractScheme("")).isNull();
+        assertThat(tools.extractScheme("noscheme")).isNull();
+    }
+
+    // ---- Mock naming ----
+
+    @Test
+    void generatesMockNames() {
+        
assertThat(tools.toMockName("kafka:orders?brokers=localhost:9092")).isEqualTo("mock:kafka-orders");
+        
assertThat(tools.toMockName("http://api.example.com/notify";)).isEqualTo("mock:http-api-example-com-notify");
+        
assertThat(tools.toMockName("timer:tick")).isEqualTo("mock:timer-tick");
+    }
+
+    @Test
+    void generatesMockVariableNames() {
+        
assertThat(tools.toMockVariableName("mock:kafka-orders")).isEqualTo("mockKafkaOrders");
+        
assertThat(tools.toMockVariableName("mock:http-api")).isEqualTo("mockHttpApi");
+    }
+
+    // ---- Main runtime test generation ----
+
+    @Test
+    void generatesMainTestWithDirectFrom() throws Exception {
+        String route = """
+                - route:
+                    from:
+                      uri: direct:start
+                      steps:
+                        - to: kafka:orders?brokers=localhost:9092
+                        - to: log:done
+                """;
+
+        String json = tools.camel_route_test_scaffold(route, "yaml", "main");
+        JsonObject result = (JsonObject) Jsoner.deserialize(json);
+
+        assertThat(result.getString("runtime")).isEqualTo("main");
+
+        String testCode = result.getString("testCode");
+        assertThat(testCode).contains("extends CamelTestSupport");
+        assertThat(testCode).contains("createRouteBuilder");
+        assertThat(testCode).contains("mock:kafka-orders");
+        assertThat(testCode).contains("template.sendBody(\"direct:start\"");
+        
assertThat(testCode).contains("MockEndpoint.assertIsSatisfied(context)");
+    }
+
+    @Test
+    void generatesMainTestWithTimerFrom() throws Exception {
+        String route = """
+                - route:
+                    from:
+                      uri: timer:tick?period=5000
+                      steps:
+                        - to: kafka:orders
+                """;
+
+        String json = tools.camel_route_test_scaffold(route, "yaml", "main");
+        JsonObject result = (JsonObject) Jsoner.deserialize(json);
+
+        String testCode = result.getString("testCode");
+        assertThat(testCode).contains("NotifyBuilder");
+        assertThat(testCode).contains("whenDone(1)");
+        assertThat(testCode).doesNotContain("template.sendBody");
+    }
+
+    @Test
+    void doesNotMockLogEndpoint() throws Exception {
+        String route = """
+                - route:
+                    from:
+                      uri: direct:start
+                      steps:
+                        - to: log:done
+                """;
+
+        String json = tools.camel_route_test_scaffold(route, "yaml", "main");
+        JsonObject result = (JsonObject) Jsoner.deserialize(json);
+
+        JsonArray mocks = result.getCollection("mockEndpoints");
+        assertThat(mocks).isEmpty();
+    }
+
+    @Test
+    void doesNotMockDirectOrSedaEndpoints() throws Exception {
+        String route = """
+                - route:
+                    from:
+                      uri: direct:start
+                      steps:
+                        - to: direct:next
+                        - to: seda:async
+                """;
+
+        String json = tools.camel_route_test_scaffold(route, "yaml", "main");
+        JsonObject result = (JsonObject) Jsoner.deserialize(json);
+
+        JsonArray mocks = result.getCollection("mockEndpoints");
+        assertThat(mocks).isEmpty();
+    }
+
+    // ---- Spring Boot runtime test generation ----
+
+    @Test
+    void generatesSpringBootTest() throws Exception {
+        String route = """
+                - route:
+                    from:
+                      uri: direct:start
+                      steps:
+                        - to: kafka:orders
+                """;
+
+        String json = tools.camel_route_test_scaffold(route, "yaml", 
"spring-boot");
+        JsonObject result = (JsonObject) Jsoner.deserialize(json);
+
+        assertThat(result.getString("runtime")).isEqualTo("spring-boot");
+
+        String testCode = result.getString("testCode");
+        assertThat(testCode).contains("@CamelSpringBootTest");
+        assertThat(testCode).contains("@SpringBootTest");
+        assertThat(testCode).contains("@Autowired");
+        assertThat(testCode).doesNotContain("extends CamelTestSupport");
+    }
+
+    // ---- Test-infra detection ----
+
+    @Test
+    void detectsKafkaTestInfra() throws Exception {
+        String route = """
+                - route:
+                    from:
+                      uri: kafka:orders?brokers=localhost:9092
+                      steps:
+                        - to: log:done
+                """;
+
+        String json = tools.camel_route_test_scaffold(route, "yaml", "main");
+        JsonObject result = (JsonObject) Jsoner.deserialize(json);
+
+        JsonArray infra = result.getCollection("testInfraServices");
+        assertThat(infra).isNotEmpty();
+        assertThat(infra.getMap(0).get("service")).isEqualTo("KafkaService");
+        
assertThat(infra.getMap(0).get("factory")).isEqualTo("KafkaServiceFactory");
+        
assertThat(infra.getMap(0).get("artifactId")).isEqualTo("camel-test-infra-kafka");
+
+        String testCode = result.getString("testCode");
+        assertThat(testCode).contains("@RegisterExtension");
+        assertThat(testCode).contains("KafkaServiceFactory.createService()");
+    }
+
+    @Test
+    void detectsJmsTestInfra() throws Exception {
+        String route = """
+                <route>
+                  <from uri="jms:queue:orders"/>
+                  <to uri="log:done"/>
+                </route>
+                """;
+
+        String json = tools.camel_route_test_scaffold(route, "xml", "main");
+        JsonObject result = (JsonObject) Jsoner.deserialize(json);
+
+        JsonArray infra = result.getCollection("testInfraServices");
+        assertThat(infra).isNotEmpty();
+        assertThat(infra.getMap(0).get("service")).isEqualTo("ArtemisService");
+        
assertThat(infra.getMap(0).get("artifactId")).isEqualTo("camel-test-infra-artemis");
+    }
+
+    @Test
+    void detectsMultipleTestInfraServices() throws Exception {
+        String route = """
+                - route:
+                    from:
+                      uri: kafka:orders
+                      steps:
+                        - to: mongodb:myDb?collection=orders
+                """;
+
+        String json = tools.camel_route_test_scaffold(route, "yaml", "main");
+        JsonObject result = (JsonObject) Jsoner.deserialize(json);
+
+        JsonArray infra = result.getCollection("testInfraServices");
+        assertThat(infra.size()).isEqualTo(2);
+
+        List<String> services = infra.stream()
+                .map(i -> ((JsonObject) i).getString("service"))
+                .toList();
+        assertThat(services).contains("KafkaService", "MongoDBService");
+    }
+
+    @Test
+    void deduplicatesTestInfraForSameService() throws Exception {
+        String route = """
+                - route:
+                    from:
+                      uri: jms:queue:input
+                      steps:
+                        - to: activemq:queue:output
+                """;
+
+        String json = tools.camel_route_test_scaffold(route, "yaml", "main");
+        JsonObject result = (JsonObject) Jsoner.deserialize(json);
+
+        JsonArray infra = result.getCollection("testInfraServices");
+        // jms and activemq both map to ArtemisService, should be deduplicated
+        assertThat(infra.size()).isEqualTo(1);
+        assertThat(infra.getMap(0).get("service")).isEqualTo("ArtemisService");
+    }
+
+    // ---- Maven dependencies ----
+
+    @Test
+    void includesTestDependencies() throws Exception {
+        String route = """
+                - route:
+                    from:
+                      uri: direct:start
+                      steps:
+                        - to: kafka:orders
+                """;
+
+        String json = tools.camel_route_test_scaffold(route, "yaml", "main");
+        JsonObject result = (JsonObject) Jsoner.deserialize(json);
+
+        JsonArray deps = result.getCollection("mavenDependencies");
+        List<String> artifactIds = deps.stream()
+                .map(d -> ((JsonObject) d).getString("artifactId"))
+                .toList();
+        assertThat(artifactIds).contains("camel-test-junit5", "camel-mock", 
"camel-test-infra-kafka");
+    }
+
+    @Test
+    void includesSpringBootTestDependency() throws Exception {
+        String route = """
+                - route:
+                    from:
+                      uri: direct:start
+                      steps:
+                        - to: kafka:orders
+                """;
+
+        String json = tools.camel_route_test_scaffold(route, "yaml", 
"spring-boot");
+        JsonObject result = (JsonObject) Jsoner.deserialize(json);
+
+        JsonArray deps = result.getCollection("mavenDependencies");
+        List<String> artifactIds = deps.stream()
+                .map(d -> ((JsonObject) d).getString("artifactId"))
+                .toList();
+        assertThat(artifactIds).contains("camel-test-spring-junit5");
+    }
+
+    // ---- Summary ----
+
+    @Test
+    void resultContainsSummary() throws Exception {
+        String route = """
+                - route:
+                    from:
+                      uri: direct:start
+                      steps:
+                        - to: kafka:orders
+                        - to: log:done
+                """;
+
+        String json = tools.camel_route_test_scaffold(route, "yaml", "main");
+        JsonObject result = (JsonObject) Jsoner.deserialize(json);
+        JsonObject summary = result.getMap("summary");
+
+        assertThat(summary).isNotNull();
+        assertThat(summary.getInteger("fromEndpointCount")).isEqualTo(1);
+        assertThat(summary.getInteger("toEndpointCount")).isEqualTo(2);
+        assertThat(summary.getInteger("mockEndpointCount")).isEqualTo(1); // 
kafka only, log is trivial
+        assertThat(summary.getInteger("testInfraServiceCount")).isEqualTo(1); 
// kafka
+    }
+
+    // ---- Default format and runtime ----
+
+    @Test
+    void defaultsToYamlAndMain() throws Exception {
+        String route = """
+                - route:
+                    from:
+                      uri: direct:start
+                      steps:
+                        - to: log:done
+                """;
+
+        String json = tools.camel_route_test_scaffold(route, null, null);
+        JsonObject result = (JsonObject) Jsoner.deserialize(json);
+
+        assertThat(result.getString("format")).isEqualTo("yaml");
+        assertThat(result.getString("runtime")).isEqualTo("main");
+    }
+
+    // ---- XML full test ----
+
+    @Test
+    void generatesTestFromXmlRoute() throws Exception {
+        String route = """
+                <route>
+                  <from uri="timer:tick?period=5000"/>
+                  <to uri="kafka:orders?brokers=localhost:9092"/>
+                  <to uri="log:done"/>
+                </route>
+                """;
+
+        String json = tools.camel_route_test_scaffold(route, "xml", "main");
+        JsonObject result = (JsonObject) Jsoner.deserialize(json);
+
+        String testCode = result.getString("testCode");
+        assertThat(testCode).contains("extends CamelTestSupport");
+        assertThat(testCode).contains("mock:kafka-orders");
+        assertThat(testCode).contains("NotifyBuilder"); // timer from
+        assertThat(testCode).contains("@RegisterExtension");
+        assertThat(testCode).contains("KafkaServiceFactory");
+    }
+}

Reply via email to