use scx_utils::Topology;
use scxtop::mcp::{McpTools, SharedPerfProfiler};
use serde_json::json;
fn try_create_test_topology() -> Option<Topology> {
Topology::new().ok()
}
#[test]
fn test_mcp_tools_new() {
let tools = McpTools::new();
drop(tools);
}
#[test]
fn test_mcp_tools_default() {
let tools = McpTools::default();
drop(tools);
}
#[test]
fn test_mcp_tools_list() {
let tools = McpTools::new();
let result = tools.list();
assert!(result["tools"].is_array());
let tools_array = result["tools"].as_array().unwrap();
let tool_names: Vec<String> = tools_array
.iter()
.filter_map(|t| t["name"].as_str().map(|s| s.to_string()))
.collect();
assert!(tool_names.contains(&"query_stats".to_string()));
assert!(tool_names.contains(&"get_topology".to_string()));
assert!(tool_names.contains(&"list_events".to_string()));
assert!(tool_names.contains(&"start_perf_profiling".to_string()));
assert!(tool_names.contains(&"stop_perf_profiling".to_string()));
assert!(tool_names.contains(&"get_perf_results".to_string()));
}
#[test]
fn test_mcp_tools_list_has_proper_schema() {
let tools = McpTools::new();
let result = tools.list();
let tools_array = result["tools"].as_array().unwrap();
for tool in tools_array {
assert!(tool["name"].is_string());
assert!(tool["description"].is_string());
assert!(!tool["inputSchema"].is_null());
if tool["inputSchema"].is_object() {
assert!(
tool["inputSchema"]["type"].is_string() || tool["inputSchema"]["type"].is_null()
);
}
}
}
#[test]
fn test_tool_query_stats_no_args() {
let mut tools = McpTools::new();
let params = json!({
"name": "query_stats",
"arguments": {}
});
let result = tools.call(¶ms);
assert!(result.is_ok());
let value = result.unwrap();
assert!(value["content"].is_array());
let content = &value["content"][0];
assert_eq!(content["type"], "text");
let text = content["text"].as_str().unwrap();
assert!(text.contains("Available statistics resources"));
assert!(text.contains("stats://aggregated/cpu"));
assert!(text.contains("stats://aggregated/llc"));
}
#[test]
fn test_tool_query_stats_cpu() {
let mut tools = McpTools::new();
let params = json!({
"name": "query_stats",
"arguments": {
"stat_type": "cpu"
}
});
let result = tools.call(¶ms);
assert!(result.is_ok());
let value = result.unwrap();
let text = value["content"][0]["text"].as_str().unwrap();
assert!(text.contains("Statistics resources for cpu"));
assert!(text.contains("stats://aggregated/cpu"));
assert!(text.contains("stats://system/cpu"));
}
#[test]
fn test_tool_query_stats_llc() {
let mut tools = McpTools::new();
let params = json!({
"name": "query_stats",
"arguments": {
"stat_type": "llc"
}
});
let result = tools.call(¶ms);
assert!(result.is_ok());
let value = result.unwrap();
let text = value["content"][0]["text"].as_str().unwrap();
assert!(text.contains("Statistics resources for llc"));
assert!(text.contains("stats://aggregated/llc"));
}
#[test]
fn test_tool_query_stats_invalid_type() {
let mut tools = McpTools::new();
let params = json!({
"name": "query_stats",
"arguments": {
"stat_type": "invalid_type"
}
});
let result = tools.call(¶ms);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Unknown stat_type"));
}
#[test]
fn test_tool_get_topology_without_topology_set() {
let mut tools = McpTools::new();
let params = json!({
"name": "get_topology",
"arguments": {}
});
let result = tools.call(¶ms);
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "Topology not available");
}
#[test]
fn test_tool_get_topology_summary() {
let Some(topology) = try_create_test_topology() else {
return;
};
let mut tools = McpTools::new();
tools.set_topology(std::sync::Arc::new(topology));
let params = json!({
"name": "get_topology",
"arguments": {
"detail_level": "summary"
}
});
let result = tools.call(¶ms);
assert!(result.is_ok());
let value = result.unwrap();
let text = value["content"][0]["text"].as_str().unwrap();
let summary: serde_json::Value = serde_json::from_str(text).unwrap();
assert!(summary["summary"].is_object());
assert!(summary["summary"]["total_cpus"].is_number());
}
#[test]
fn test_tool_get_topology_full() {
let Some(topology) = try_create_test_topology() else {
return;
};
let mut tools = McpTools::new();
tools.set_topology(std::sync::Arc::new(topology));
let params = json!({
"name": "get_topology",
"arguments": {
"detail_level": "full"
}
});
let result = tools.call(¶ms);
assert!(result.is_ok());
let value = result.unwrap();
let text = value["content"][0]["text"].as_str().unwrap();
let topo: serde_json::Value = serde_json::from_str(text).unwrap();
assert!(topo["cpus"].is_array());
assert!(!topo["cpus"].as_array().unwrap().is_empty());
}
#[test]
fn test_tool_list_events_without_subsystem() {
let mut tools = McpTools::new();
let params = json!({
"name": "list_events",
"arguments": {
"event_type": "perf"
}
});
let result = tools.call(¶ms);
assert!(result.is_err());
}
#[test]
fn test_tool_start_perf_profiling_without_profiler() {
let mut tools = McpTools::new();
let params = json!({
"name": "start_perf_profiling",
"arguments": {}
});
let result = tools.call(¶ms);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Perf profiler not available"
);
}
#[test]
fn test_tool_start_perf_profiling_with_default_args() {
let mut tools = McpTools::new();
let profiler = SharedPerfProfiler::new();
tools.set_perf_profiler(profiler.clone());
let params = json!({
"name": "start_perf_profiling",
"arguments": {}
});
let result = tools.call(¶ms);
assert!(result.is_ok());
}
#[test]
fn test_tool_stop_perf_profiling_without_profiler() {
let mut tools = McpTools::new();
let params = json!({
"name": "stop_perf_profiling",
"arguments": {}
});
let result = tools.call(¶ms);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Perf profiler not available"
);
}
#[test]
fn test_tool_get_perf_results_without_profiler() {
let mut tools = McpTools::new();
let params = json!({
"name": "get_perf_results",
"arguments": {}
});
let result = tools.call(¶ms);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Perf profiler not available"
);
}
#[test]
fn test_tool_get_perf_results() {
let mut tools = McpTools::new();
let profiler = SharedPerfProfiler::new();
tools.set_perf_profiler(profiler.clone());
let params = json!({
"name": "get_perf_results",
"arguments": {
"limit": 10,
"include_stacks": false
}
});
let result = tools.call(¶ms);
assert!(result.is_ok());
let value = result.unwrap();
assert!(value["content"].is_array());
}
#[test]
fn test_tool_call_unknown_tool() {
let mut tools = McpTools::new();
let params = json!({
"name": "unknown_tool",
"arguments": {}
});
let result = tools.call(¶ms);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Unknown tool"));
}
#[test]
fn test_tool_call_missing_name() {
let mut tools = McpTools::new();
let params = json!({
"arguments": {}
});
let result = tools.call(¶ms);
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "Missing tool name");
}
#[test]
fn test_set_topology() {
let Some(topology) = try_create_test_topology() else {
return;
};
let mut tools = McpTools::new();
tools.set_topology(std::sync::Arc::new(topology));
let params = json!({
"name": "get_topology",
"arguments": {}
});
let result = tools.call(¶ms);
assert!(result.is_ok());
}
#[test]
fn test_detect_outliers_has_limit_parameter() {
let tools = McpTools::new();
let result = tools.list();
let tools_array = result["tools"].as_array().unwrap();
let detect_outliers_tool = tools_array
.iter()
.find(|t| t["name"] == "detect_outliers")
.expect("detect_outliers tool should exist");
let schema = &detect_outliers_tool["inputSchema"];
assert!(schema["properties"]["limit"].is_object());
let limit_schema = &schema["properties"]["limit"];
assert_eq!(limit_schema["type"], "integer");
assert_eq!(limit_schema["default"], 20);
assert_eq!(limit_schema["minimum"], 1);
assert_eq!(limit_schema["maximum"], 1000);
}
#[test]
fn test_detect_outliers_without_trace() {
let mut tools = McpTools::new();
let trace_cache = std::sync::Arc::new(std::sync::Mutex::new(std::collections::HashMap::new()));
tools.set_trace_cache(trace_cache);
let params = json!({
"name": "detect_outliers",
"arguments": {
"trace_id": "nonexistent"
}
});
let result = tools.call(¶ms);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not found"));
}
#[test]
fn test_detect_outliers_schema_categories() {
let tools = McpTools::new();
let result = tools.list();
let tools_array = result["tools"].as_array().unwrap();
let detect_outliers_tool = tools_array
.iter()
.find(|t| t["name"] == "detect_outliers")
.expect("detect_outliers tool should exist");
let schema = &detect_outliers_tool["inputSchema"];
let category_enum = &schema["properties"]["category"]["enum"];
assert!(category_enum
.as_array()
.unwrap()
.contains(&json!("latency")));
assert!(category_enum
.as_array()
.unwrap()
.contains(&json!("runtime")));
assert!(category_enum.as_array().unwrap().contains(&json!("cpu")));
assert!(category_enum.as_array().unwrap().contains(&json!("all")));
}
#[test]
fn test_detect_outliers_schema_methods() {
let tools = McpTools::new();
let result = tools.list();
let tools_array = result["tools"].as_array().unwrap();
let detect_outliers_tool = tools_array
.iter()
.find(|t| t["name"] == "detect_outliers")
.expect("detect_outliers tool should exist");
let schema = &detect_outliers_tool["inputSchema"];
let method_enum = &schema["properties"]["method"]["enum"];
assert!(method_enum.as_array().unwrap().contains(&json!("IQR")));
assert!(method_enum.as_array().unwrap().contains(&json!("MAD")));
assert!(method_enum.as_array().unwrap().contains(&json!("StdDev")));
assert!(method_enum
.as_array()
.unwrap()
.contains(&json!("Percentile")));
}