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

robertlazarski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/axis-axis2-java-core.git

commit 95de3e4e0c9dc4d6444f5bd7384bcf44d557717c
Author: Robert Lazarski <[email protected]>
AuthorDate: Sat Apr 11 12:33:29 2026 -1000

    Add unit tests for MCP auto-schema reflection (6 tests)
    
    McpAutoSchemaTest covers:
    - Auto-schema from ServiceClass: int->integer, double->number,
      String->string, boolean->boolean, long->integer
    - Array type mapping: double[]->array of numbers,
      double[][]->array of arrays of numbers
    - Primitive parameter (String) skipped, falls back to empty schema
    - Explicit mcpInputSchema overrides auto-generation
    - SpringBeanName without HttpServletRequest falls back to empty
    - Boolean is-getter detection (isActive -> active: boolean)
    
    Total openapi tests: 244 -> 250, 0 failures.
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
 .../apache/axis2/openapi/McpAutoSchemaTest.java    | 278 +++++++++++++++++++++
 1 file changed, 278 insertions(+)

diff --git 
a/modules/openapi/src/test/java/org/apache/axis2/openapi/McpAutoSchemaTest.java 
b/modules/openapi/src/test/java/org/apache/axis2/openapi/McpAutoSchemaTest.java
new file mode 100644
index 0000000000..4f8ba007e9
--- /dev/null
+++ 
b/modules/openapi/src/test/java/org/apache/axis2/openapi/McpAutoSchemaTest.java
@@ -0,0 +1,278 @@
+/*
+ * 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.axis2.openapi;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.axis2.context.ConfigurationContext;
+import org.apache.axis2.context.ConfigurationContextFactory;
+import org.apache.axis2.description.AxisOperation;
+import org.apache.axis2.description.AxisService;
+import org.apache.axis2.description.InOutAxisOperation;
+import org.apache.axis2.description.Parameter;
+import org.apache.axis2.engine.AxisConfiguration;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.xml.namespace.QName;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests for MCP auto-schema generation from Java method parameter types.
+ * Covers the reflection-based fallback when mcpInputSchema is not set
+ * in services.xml.
+ */
+public class McpAutoSchemaTest {
+
+    private ConfigurationContext configurationContext;
+    private OpenApiSpecGenerator generator;
+    private ObjectMapper jackson;
+
+    // ── Test service class with typed POJO parameter ──
+    public static class SampleRequest {
+        private int count;
+        private double price;
+        private String name;
+        private boolean active;
+        private long timestamp;
+        private double[] values;
+        private double[][] matrix;
+        private String requestId;
+
+        public int getCount() { return count; }
+        public void setCount(int count) { this.count = count; }
+        public double getPrice() { return price; }
+        public void setPrice(double price) { this.price = price; }
+        public String getName() { return name; }
+        public void setName(String name) { this.name = name; }
+        public boolean isActive() { return active; }
+        public void setActive(boolean active) { this.active = active; }
+        public long getTimestamp() { return timestamp; }
+        public void setTimestamp(long timestamp) { this.timestamp = timestamp; 
}
+        public double[] getValues() { return values; }
+        public void setValues(double[] values) { this.values = values; }
+        public double[][] getMatrix() { return matrix; }
+        public void setMatrix(double[][] matrix) { this.matrix = matrix; }
+        public String getRequestId() { return requestId; }
+        public void setRequestId(String requestId) { this.requestId = 
requestId; }
+    }
+
+    public static class SampleResponse {
+        private String result;
+        public String getResult() { return result; }
+        public void setResult(String result) { this.result = result; }
+    }
+
+    public static class SampleService {
+        public SampleResponse calculate(SampleRequest request) {
+            return new SampleResponse();
+        }
+        public String echo(String message) {
+            return message;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        configurationContext = ConfigurationContextFactory
+                .createEmptyConfigurationContext();
+        generator = new OpenApiSpecGenerator(configurationContext);
+        jackson = io.swagger.v3.core.util.Json.mapper();
+    }
+
+    private AxisService createServiceWithClass(String serviceName, String 
className)
+            throws Exception {
+        AxisConfiguration axisConfig = 
configurationContext.getAxisConfiguration();
+        AxisService service = new AxisService(serviceName);
+        service.addParameter(new Parameter("ServiceClass", className));
+        axisConfig.addService(service);
+        return service;
+    }
+
+    private void addOperation(AxisService service, String opName) throws 
Exception {
+        AxisOperation op = new InOutAxisOperation(new QName(opName));
+        service.addOperation(op);
+    }
+
+    @Test
+    public void testAutoSchemaFromServiceClass() throws Exception {
+        AxisService service = createServiceWithClass("SampleService",
+                McpAutoSchemaTest.SampleService.class.getName());
+        addOperation(service, "calculate");
+
+        String catalog = generator.generateMcpCatalogJson(null);
+        assertNotNull(catalog);
+
+        JsonNode root = jackson.readTree(catalog);
+        JsonNode tools = root.get("tools");
+        assertNotNull(tools);
+
+        JsonNode calcTool = null;
+        for (JsonNode tool : tools) {
+            if ("calculate".equals(tool.get("name").asText())) {
+                calcTool = tool;
+                break;
+            }
+        }
+        assertNotNull("calculate tool should be in catalog", calcTool);
+
+        JsonNode schema = calcTool.get("inputSchema");
+        assertNotNull("inputSchema should be present", schema);
+        assertEquals("object", schema.get("type").asText());
+
+        JsonNode props = schema.get("properties");
+        assertNotNull("properties should be present", props);
+
+        // Verify type mappings
+        assertTrue("should have count property", props.has("count"));
+        assertEquals("integer", props.get("count").get("type").asText());
+
+        assertTrue("should have price property", props.has("price"));
+        assertEquals("number", props.get("price").get("type").asText());
+
+        assertTrue("should have name property", props.has("name"));
+        assertEquals("string", props.get("name").get("type").asText());
+
+        assertTrue("should have active property", props.has("active"));
+        assertEquals("boolean", props.get("active").get("type").asText());
+
+        assertTrue("should have timestamp property", props.has("timestamp"));
+        assertEquals("integer", props.get("timestamp").get("type").asText());
+    }
+
+    @Test
+    public void testAutoSchemaArrayTypes() throws Exception {
+        AxisService service = createServiceWithClass("SampleService",
+                McpAutoSchemaTest.SampleService.class.getName());
+        addOperation(service, "calculate");
+
+        String catalog = generator.generateMcpCatalogJson(null);
+        JsonNode root = jackson.readTree(catalog);
+        JsonNode calcTool = null;
+        for (JsonNode tool : root.get("tools")) {
+            if ("calculate".equals(tool.get("name").asText())) {
+                calcTool = tool;
+                break;
+            }
+        }
+        JsonNode props = calcTool.get("inputSchema").get("properties");
+
+        // double[] -> array of numbers
+        assertTrue("should have values property", props.has("values"));
+        assertEquals("array", props.get("values").get("type").asText());
+        assertEquals("number", 
props.get("values").get("items").get("type").asText());
+
+        // double[][] -> array of arrays of numbers
+        assertTrue("should have matrix property", props.has("matrix"));
+        assertEquals("array", props.get("matrix").get("type").asText());
+        assertEquals("array", 
props.get("matrix").get("items").get("type").asText());
+        assertEquals("number", 
props.get("matrix").get("items").get("items").get("type").asText());
+    }
+
+    @Test
+    public void testAutoSchemaSkipsPrimitiveParameter() throws Exception {
+        // echo(String) has a primitive parameter — should fall back to empty 
schema
+        AxisService service = createServiceWithClass("SampleService",
+                McpAutoSchemaTest.SampleService.class.getName());
+        addOperation(service, "echo");
+
+        String catalog = generator.generateMcpCatalogJson(null);
+        JsonNode root = jackson.readTree(catalog);
+        JsonNode echoTool = null;
+        for (JsonNode tool : root.get("tools")) {
+            if ("echo".equals(tool.get("name").asText())) {
+                echoTool = tool;
+                break;
+            }
+        }
+        assertNotNull("echo tool should be in catalog", echoTool);
+        JsonNode props = echoTool.get("inputSchema").get("properties");
+        assertEquals("String param should produce empty properties",
+                0, props.size());
+    }
+
+    @Test
+    public void testExplicitSchemaOverridesAutoGeneration() throws Exception {
+        AxisService service = createServiceWithClass("SampleService",
+                McpAutoSchemaTest.SampleService.class.getName());
+        AxisOperation op = new InOutAxisOperation(new QName("calculate"));
+        op.addParameter(new Parameter("mcpInputSchema",
+                
"{\"type\":\"object\",\"properties\":{\"custom\":{\"type\":\"string\"}}}"));
+        service.addOperation(op);
+
+        String catalog = generator.generateMcpCatalogJson(null);
+        JsonNode root = jackson.readTree(catalog);
+        JsonNode calcTool = null;
+        for (JsonNode tool : root.get("tools")) {
+            if ("calculate".equals(tool.get("name").asText())) {
+                calcTool = tool;
+                break;
+            }
+        }
+        JsonNode props = calcTool.get("inputSchema").get("properties");
+        assertTrue("explicit schema should have custom property", 
props.has("custom"));
+        assertFalse("explicit schema should NOT have auto-generated count 
property",
+                props.has("count"));
+    }
+
+    @Test
+    public void testNoServiceClassProducesEmptySchema() throws Exception {
+        // Service with SpringBeanName only, no ServiceClass, no 
HttpServletRequest
+        AxisConfiguration axisConfig = 
configurationContext.getAxisConfiguration();
+        AxisService service = new AxisService("SpringOnlyService");
+        service.addParameter(new Parameter("SpringBeanName", "myBean"));
+        axisConfig.addService(service);
+        addOperation(service, "doSomething");
+
+        String catalog = generator.generateMcpCatalogJson(null);
+        JsonNode root = jackson.readTree(catalog);
+        JsonNode tool = null;
+        for (JsonNode t : root.get("tools")) {
+            if ("doSomething".equals(t.get("name").asText())) {
+                tool = t;
+                break;
+            }
+        }
+        assertNotNull(tool);
+        // Without HttpServletRequest, Spring bean can't be resolved — empty 
schema
+        JsonNode props = tool.get("inputSchema").get("properties");
+        assertEquals("Should fall back to empty schema without request", 0, 
props.size());
+    }
+
+    @Test
+    public void testBooleanIsGetterDetected() throws Exception {
+        AxisService service = createServiceWithClass("SampleService",
+                McpAutoSchemaTest.SampleService.class.getName());
+        addOperation(service, "calculate");
+
+        String catalog = generator.generateMcpCatalogJson(null);
+        JsonNode root = jackson.readTree(catalog);
+        JsonNode calcTool = null;
+        for (JsonNode tool : root.get("tools")) {
+            if ("calculate".equals(tool.get("name").asText())) {
+                calcTool = tool;
+                break;
+            }
+        }
+        JsonNode props = calcTool.get("inputSchema").get("properties");
+        assertTrue("isActive() should produce active property", 
props.has("active"));
+        assertEquals("boolean", props.get("active").get("type").asText());
+    }
+}

Reply via email to