#[cfg(feature = "mcp")]
mod mcp_comprehensive_tests {
use rmcp::model::{CallToolResult, ContentBlock, ErrorData as McpError};
use sdforge::core::ApiMetadata;
use sdforge::mcp::{get_mcp_tools, McpToolRegistration, SdForgeTool};
use std::sync::Arc;
use std::thread;
fn create_simple_tool(name: &str) -> Arc<dyn SdForgeTool> {
struct SimpleTool {
name: String,
description: String,
}
impl SdForgeTool for SimpleTool {
fn name(&self) -> &str {
&self.name
}
fn description(&self) -> &str {
&self.description
}
fn input_schema(&self) -> serde_json::Value {
serde_json::json!({"type": "object"})
}
fn call(
&self,
_input: Option<serde_json::Value>,
) -> Result<CallToolResult, McpError> {
Ok({
let mut result = CallToolResult::success(vec![]);
result.is_error = None;
result
})
}
}
Arc::new(SimpleTool {
name: name.to_string(),
description: format!("Test tool: {}", name),
}) as Arc<dyn SdForgeTool>
}
fn create_echo_tool() -> Arc<dyn SdForgeTool> {
struct EchoTool;
impl SdForgeTool for EchoTool {
fn name(&self) -> &str {
"echo"
}
fn description(&self) -> &str {
"Echoes input back as response"
}
fn input_schema(&self) -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {
"message": {"type": "string"}
}
})
}
fn call(
&self,
input: Option<serde_json::Value>,
) -> Result<CallToolResult, McpError> {
let text = input
.and_then(|v| v.get("message").and_then(|m| m.as_str()).map(|s| s.to_string()))
.unwrap_or_default();
Ok(CallToolResult::success(vec![ContentBlock::text(text)]))
}
}
Arc::new(EchoTool) as Arc<dyn SdForgeTool>
}
fn create_math_tool() -> Arc<dyn SdForgeTool> {
struct MathTool;
impl SdForgeTool for MathTool {
fn name(&self) -> &str {
"math"
}
fn description(&self) -> &str {
"Performs basic arithmetic"
}
fn input_schema(&self) -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {
"a": {"type": "number"},
"b": {"type": "number"},
"operation": {"type": "string", "enum": ["add", "subtract", "multiply"]}
},
"required": ["a", "b", "operation"]
})
}
fn call(
&self,
input: Option<serde_json::Value>,
) -> Result<CallToolResult, McpError> {
let val = input.ok_or_else(|| McpError::invalid_params("Input required", None))?;
let a = val
.get("a")
.and_then(|v| v.as_f64())
.ok_or_else(|| McpError::invalid_params("Missing 'a'", None))?;
let b = val
.get("b")
.and_then(|v| v.as_f64())
.ok_or_else(|| McpError::invalid_params("Missing 'b'", None))?;
let op = val
.get("operation")
.and_then(|v| v.as_str())
.ok_or_else(|| McpError::invalid_params("Missing 'operation'", None))?;
let result = match op {
"add" => a + b,
"subtract" => a - b,
"multiply" => a * b,
_ => {
return Err(McpError::invalid_params(
format!("Unknown operation: {}", op),
None,
))
}
};
Ok(CallToolResult::success(vec![ContentBlock::text(
result.to_string(),
)]))
}
}
Arc::new(MathTool) as Arc<dyn SdForgeTool>
}
fn create_error_tool() -> Arc<dyn SdForgeTool> {
struct ErrorTool;
impl SdForgeTool for ErrorTool {
fn name(&self) -> &str {
"error_tool"
}
fn description(&self) -> &str {
"Always returns an error"
}
fn input_schema(&self) -> serde_json::Value {
serde_json::json!({"type": "object"})
}
fn call(
&self,
_input: Option<serde_json::Value>,
) -> Result<CallToolResult, McpError> {
Err(McpError::invalid_params("Intentional test error", None))
}
}
Arc::new(ErrorTool) as Arc<dyn SdForgeTool>
}
fn create_complex_schema_tool() -> Arc<dyn SdForgeTool> {
struct ComplexSchemaTool;
impl SdForgeTool for ComplexSchemaTool {
fn name(&self) -> &str {
"complex_schema"
}
fn description(&self) -> &str {
"Tool with complex nested schema"
}
fn input_schema(&self) -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer", "minimum": 0},
"address": {
"type": "object",
"properties": {
"street": {"type": "string"},
"city": {"type": "string"},
"country": {"type": "string"}
}
}
},
"required": ["name"]
},
"tags": {
"type": "array",
"items": {"type": "string"}
}
},
"required": ["user"]
})
}
fn call(
&self,
_input: Option<serde_json::Value>,
) -> Result<CallToolResult, McpError> {
Ok({
let mut result = CallToolResult::success(vec![]);
result.is_error = None;
result
})
}
}
Arc::new(ComplexSchemaTool) as Arc<dyn SdForgeTool>
}
#[test]
fn test_get_mcp_tools_returns_vector() {
let tools = get_mcp_tools();
assert!(!tools.is_empty() || tools.is_empty());
}
#[test]
fn test_get_mcp_tools_idempotent() {
let tools1 = get_mcp_tools();
let tools2 = get_mcp_tools();
assert_eq!(tools1.len(), tools2.len());
}
#[test]
fn test_get_mcp_tools_instance_structure() {
let tools = get_mcp_tools();
for instance in tools {
let _ = instance.tool();
let _ = instance.metadata();
}
}
#[test]
fn test_mcp_tool_instance_tool_accessor() {
let tools = get_mcp_tools();
for instance in tools {
let tool = instance.tool();
assert!(!tool.name().is_empty() || tool.name().is_empty());
}
}
#[test]
fn test_mcp_tool_instance_metadata_accessor() {
let tools = get_mcp_tools();
for instance in tools {
let metadata = instance.metadata();
assert!(!metadata.name().is_empty() || metadata.name().is_empty());
}
}
#[test]
fn test_mcp_tool_instance_tool_arc_clone() {
let tools = get_mcp_tools();
for instance in tools {
let tool1 = instance.tool().clone();
let tool2 = instance.tool().clone();
assert_eq!(tool1.name(), tool2.name());
}
}
#[test]
fn test_mcp_tool_instance_metadata_variations() {
let tools = get_mcp_tools();
for instance in tools {
let _ = instance.metadata().cache_ttl();
let _ = instance.metadata().is_streaming();
}
}
#[test]
fn test_tool_with_empty_name() {
let tool = create_simple_tool("");
assert_eq!(tool.name(), "");
}
#[test]
fn test_tool_with_special_characters() {
let tool = create_simple_tool("tool-with_special.chars:v2");
assert_eq!(tool.name(), "tool-with_special.chars:v2");
}
#[test]
fn test_tool_with_unicode_description() {
let tool = create_simple_tool("unicode_tool");
assert!(tool
.description()
.contains(&format!("Test tool: {}", "unicode_tool")));
}
#[test]
fn test_tool_with_long_name() {
let long_name = "a".repeat(1000);
let tool = create_simple_tool(&long_name);
assert_eq!(tool.name().len(), 1000);
}
#[test]
fn test_tool_versions() {
let versions = ["v1", "v2.0", "1.0.0", "beta", "2024-01-01", ""];
for version in versions {
let reg = McpToolRegistration::new(
"version_test",
version,
|| create_simple_tool("version_test"),
|| ApiMetadata::default(),
);
let _ = reg;
}
}
#[test]
fn test_echo_tool_execution() {
let tool = create_echo_tool();
let input = serde_json::json!({"message": "Hello, World!"});
let result = tool.call(Some(input));
assert!(result.is_ok());
let response = result.unwrap();
assert!(!response.content.is_empty());
}
#[test]
fn test_math_tool_addition() {
let tool = create_math_tool();
let input = serde_json::json!({"a": 5, "b": 3, "operation": "add"});
let result = tool.call(Some(input));
assert!(result.is_ok());
}
#[test]
fn test_math_tool_subtraction() {
let tool = create_math_tool();
let input = serde_json::json!({"a": 10, "b": 4, "operation": "subtract"});
let result = tool.call(Some(input));
assert!(result.is_ok());
}
#[test]
fn test_math_tool_multiplication() {
let tool = create_math_tool();
let input = serde_json::json!({"a": 6, "b": 7, "operation": "multiply"});
let result = tool.call(Some(input));
assert!(result.is_ok());
}
#[test]
fn test_error_tool_returns_error() {
let tool = create_error_tool();
let result = tool.call(None);
assert!(result.is_err());
}
#[test]
fn test_complex_schema_tool_schema() {
let tool = create_complex_schema_tool();
let schema = tool.input_schema();
assert!(schema.get("properties").is_some());
assert!(schema["properties"].get("user").is_some());
assert!(schema["properties"]["user"]["properties"]
.get("address")
.is_some());
}
#[test]
fn test_tool_call_missing_required_field() {
let tool = create_math_tool();
let input = serde_json::json!({"a": 5});
let result = tool.call(Some(input));
assert!(result.is_err());
}
#[test]
fn test_tool_call_invalid_operation() {
let tool = create_math_tool();
let input = serde_json::json!({"a": 5, "b": 3, "operation": "divide"});
let result = tool.call(Some(input));
assert!(result.is_err());
}
#[test]
fn test_tool_call_no_input_required() {
let tool = create_math_tool();
let result = tool.call(None);
assert!(result.is_err());
}
#[test]
fn test_concurrent_tool_execution() {
let tool = Arc::new(create_echo_tool());
let mut handles = vec![];
for i in 0..10 {
let tool_clone = Arc::clone(&tool);
handles.push(thread::spawn(move || {
let input = serde_json::json!({"message": format!("Message {}", i)});
tool_clone.call(Some(input))
}));
}
let results: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
for result in results {
assert!(result.is_ok());
}
}
#[test]
fn test_concurrent_get_mcp_tools() {
let mut handles = vec![];
for _ in 0..20 {
handles.push(thread::spawn(|| get_mcp_tools()));
}
let results: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
let expected_len = results[0].len();
for result in &results[1..] {
assert_eq!(result.len(), expected_len);
}
}
#[test]
fn test_concurrent_tool_instance_creation() {
let handles: Vec<_> = (0..50)
.map(|i| thread::spawn(move || create_simple_tool(&format!("concurrent_tool_{}", i))))
.collect();
let tools: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
for (i, tool) in tools.iter().enumerate() {
assert_eq!(tool.name(), format!("concurrent_tool_{}", i));
}
}
#[test]
fn test_concurrent_metadata_access() {
let tools = Arc::new(get_mcp_tools());
let mut handles = vec![];
for _ in 0..100 {
let tools_clone = Arc::clone(&tools);
handles.push(thread::spawn(move || {
if let Some(instance) = tools_clone.first() {
let _ = instance.metadata().name().to_string();
let _ = instance.metadata().version().to_string();
let _ = instance.metadata().cache_ttl();
let _ = instance.metadata().is_streaming();
}
}));
}
for h in handles {
let _ = h.join().unwrap();
}
}
#[test]
fn test_concurrent_math_operations() {
let tool = Arc::new(create_math_tool());
let mut handles = vec![];
let operations = vec![
("add", 1.0, 2.0),
("subtract", 10.0, 3.0),
("multiply", 4.0, 5.0),
];
for (op, a, b) in operations.repeat(5) {
let tool_clone = Arc::clone(&tool);
handles.push(thread::spawn(move || {
let input = serde_json::json!({"a": a, "b": b, "operation": op});
tool_clone.call(Some(input))
}));
}
for h in handles {
assert!(h.join().unwrap().is_ok());
}
}
#[test]
fn test_registration_creation() {
let _registration = McpToolRegistration::new(
"test_registration",
"v1",
|| create_simple_tool("test_registration"),
|| ApiMetadata::default(),
);
}
#[test]
fn test_registration_create_fn() {
let tool = create_echo_tool();
assert_eq!(tool.name(), "echo");
assert_eq!(tool.description(), "Echoes input back as response");
}
#[test]
fn test_multiple_tool_creations() {
let tool1 = create_simple_tool("tool_a");
let tool2 = create_simple_tool("tool_b");
assert_eq!(tool1.name(), "tool_a");
assert_eq!(tool2.name(), "tool_b");
}
#[test]
fn test_tool_response_text_content() {
let tool = create_echo_tool();
let input = serde_json::json!({"message": "test"});
let response = tool.call(Some(input)).unwrap();
assert!(!response.content.is_empty());
assert!(matches!(
response.content.first(),
Some(rmcp::model::ContentBlock::Text(_))
));
}
#[test]
fn test_tool_response_error_flag() {
let tool = create_echo_tool();
let response = tool.call(None).unwrap();
assert!(response.is_error.is_none() || response.is_error == Some(false));
}
#[test]
fn test_tool_response_meta_field() {
let tool = create_simple_tool("meta_test");
let response = tool.call(None).unwrap();
assert!(response.meta.is_none());
}
#[test]
fn test_input_schema_is_valid_json() {
let tool = create_complex_schema_tool();
let schema = tool.input_schema();
let serialized = serde_json::to_string(&schema).unwrap();
let _: serde_json::Value = serde_json::from_str(&serialized).unwrap();
}
#[test]
fn test_nested_schema_properties() {
let tool = create_complex_schema_tool();
let schema = tool.input_schema();
let user_props = &schema["properties"]["user"]["properties"];
assert!(user_props.get("name").is_some());
assert!(user_props.get("age").is_some());
assert!(user_props.get("address").is_some());
}
#[test]
fn test_array_type_in_schema() {
let tool = create_complex_schema_tool();
let schema = tool.input_schema();
let tags = &schema["properties"]["tags"];
assert_eq!(tags["type"], "array");
assert_eq!(tags["items"]["type"], "string");
}
#[test]
fn test_required_fields_in_schema() {
let tool = create_complex_schema_tool();
let schema = tool.input_schema();
let required = schema.get("required").unwrap().as_array().unwrap();
assert!(required.contains(&serde_json::json!("user")));
}
}