use pmat::mcp_integration::tools::*;
use pmat::mcp_integration::McpTool;
use pmat::services::semantic::HybridSearchEngine;
use serde_json::json;
use std::sync::Arc;
async fn create_test_engine() -> Option<Arc<HybridSearchEngine>> {
let api_key = std::env::var("OPENAI_API_KEY").ok()?;
let temp_dir = tempfile::tempdir().ok()?;
let db_path = temp_dir.path().join("test.db");
let workspace = temp_dir.path();
HybridSearchEngine::new(&api_key, &db_path.to_string_lossy(), workspace)
.await
.ok()
.map(Arc::new)
}
#[tokio::test]
async fn test_semantic_search_adapter_metadata() {
let engine = match create_test_engine().await {
Some(e) => e,
None => {
eprintln!("Skipping test: OPENAI_API_KEY not set");
return;
}
};
let tool = SemanticSearchToolAdapter::new(engine);
let metadata = tool.metadata();
assert_eq!(metadata.name, "semantic_search");
assert!(metadata.description.contains("Search") || metadata.description.contains("search"));
assert_eq!(metadata.input_schema["type"], "object");
assert!(metadata.input_schema["properties"]["query"].is_object());
}
#[tokio::test]
async fn test_find_similar_adapter_metadata() {
let engine = match create_test_engine().await {
Some(e) => e,
None => {
eprintln!("Skipping test: OPENAI_API_KEY not set");
return;
}
};
let tool = FindSimilarCodeToolAdapter::new(engine);
let metadata = tool.metadata();
assert_eq!(metadata.name, "find_similar_code");
assert!(metadata.description.contains("similar") || metadata.description.contains("Similar"));
assert_eq!(metadata.input_schema["type"], "object");
}
#[tokio::test]
async fn test_cluster_adapter_metadata() {
let engine = match create_test_engine().await {
Some(e) => e,
None => {
eprintln!("Skipping test: OPENAI_API_KEY not set");
return;
}
};
let tool = ClusterCodeToolAdapter::new(engine);
let metadata = tool.metadata();
assert_eq!(metadata.name, "cluster_code");
assert!(metadata.description.contains("Cluster") || metadata.description.contains("cluster"));
assert_eq!(metadata.input_schema["type"], "object");
}
#[tokio::test]
async fn test_analyze_topics_adapter_metadata() {
let engine = match create_test_engine().await {
Some(e) => e,
None => {
eprintln!("Skipping test: OPENAI_API_KEY not set");
return;
}
};
let tool = AnalyzeTopicsToolAdapter::new(engine);
let metadata = tool.metadata();
assert_eq!(metadata.name, "analyze_topics");
assert!(metadata.description.contains("topic") || metadata.description.contains("Topic"));
assert_eq!(metadata.input_schema["type"], "object");
}
#[tokio::test]
async fn test_semantic_search_missing_query() {
let engine = match create_test_engine().await {
Some(e) => e,
None => {
eprintln!("Skipping test: OPENAI_API_KEY not set");
return;
}
};
let tool = SemanticSearchToolAdapter::new(engine);
let params = json!({});
let result = tool.execute(params).await;
assert!(result.is_err());
if let Err(err) = result {
assert!(err.message.contains("query") || err.message.contains("required"));
}
}
#[tokio::test]
async fn test_semantic_search_empty_query() {
let engine = match create_test_engine().await {
Some(e) => e,
None => {
eprintln!("Skipping test: OPENAI_API_KEY not set");
return;
}
};
let tool = SemanticSearchToolAdapter::new(engine);
let params = json!({
"query": ""
});
let result = tool.execute(params).await;
assert!(result.is_err());
if let Err(err) = result {
assert!(err.message.contains("empty") || err.message.contains("Query"));
}
}
#[tokio::test]
async fn test_semantic_search_invalid_mode() {
let engine = match create_test_engine().await {
Some(e) => e,
None => {
eprintln!("Skipping test: OPENAI_API_KEY not set");
return;
}
};
let tool = SemanticSearchToolAdapter::new(engine);
let params = json!({
"query": "test query",
"mode": "invalid_mode"
});
let result = tool.execute(params).await;
assert!(result.is_err());
if let Err(err) = result {
assert!(err.message.contains("Invalid") || err.message.contains("mode"));
}
}
#[tokio::test]
async fn test_find_similar_missing_file_path() {
let engine = match create_test_engine().await {
Some(e) => e,
None => {
eprintln!("Skipping test: OPENAI_API_KEY not set");
return;
}
};
let tool = FindSimilarCodeToolAdapter::new(engine);
let params = json!({});
let result = tool.execute(params).await;
assert!(result.is_err());
if let Err(err) = result {
assert!(
err.message.contains("file")
|| err.message.contains("required")
|| err.message.contains("path")
);
}
}
#[tokio::test]
async fn test_cluster_missing_method() {
let engine = match create_test_engine().await {
Some(e) => e,
None => {
eprintln!("Skipping test: OPENAI_API_KEY not set");
return;
}
};
let tool = ClusterCodeToolAdapter::new(engine);
let params = json!({});
let result = tool.execute(params).await;
assert!(result.is_err());
if let Err(err) = result {
assert!(err.message.contains("method") || err.message.contains("required"));
}
}
#[tokio::test]
async fn test_cluster_invalid_method() {
let engine = match create_test_engine().await {
Some(e) => e,
None => {
eprintln!("Skipping test: OPENAI_API_KEY not set");
return;
}
};
let tool = ClusterCodeToolAdapter::new(engine);
let params = json!({
"method": "invalid_method"
});
let result = tool.execute(params).await;
assert!(result.is_err());
if let Err(err) = result {
assert!(err.message.contains("Invalid") || err.message.contains("method"));
}
}
#[tokio::test]
async fn test_analyze_topics_missing_num_topics() {
let engine = match create_test_engine().await {
Some(e) => e,
None => {
eprintln!("Skipping test: OPENAI_API_KEY not set");
return;
}
};
let tool = AnalyzeTopicsToolAdapter::new(engine);
let params = json!({});
let result = tool.execute(params).await;
assert!(result.is_err());
if let Err(err) = result {
assert!(
err.message.contains("topics")
|| err.message.contains("required")
|| err.message.contains("num")
);
}
}
#[tokio::test]
async fn test_error_conversion() {
let engine = match create_test_engine().await {
Some(e) => e,
None => {
eprintln!("Skipping test: OPENAI_API_KEY not set");
return;
}
};
let tool = SemanticSearchToolAdapter::new(engine);
let params = json!({
"query": "test",
"limit": 999 });
let result = tool.execute(params).await;
assert!(result.is_err());
if let Err(err) = result {
assert_eq!(err.code, pmat::mcp_integration::error_codes::INTERNAL_ERROR);
assert!(!err.message.is_empty());
}
}
#[tokio::test]
async fn test_semantic_search_schema_structure() {
let engine = match create_test_engine().await {
Some(e) => e,
None => {
eprintln!("Skipping test: OPENAI_API_KEY not set");
return;
}
};
let tool = SemanticSearchToolAdapter::new(engine);
let metadata = tool.metadata();
let properties = &metadata.input_schema["properties"];
assert!(properties["query"].is_object());
assert!(properties["mode"].is_object());
assert!(properties["limit"].is_object());
let required = metadata.input_schema["required"].as_array();
assert!(required.is_some());
let required = required.unwrap();
assert!(required.iter().any(|v| v.as_str() == Some("query")));
}
#[tokio::test]
async fn test_all_adapters_implement_mcp_tool() {
let engine = match create_test_engine().await {
Some(e) => e,
None => {
eprintln!("Skipping test: OPENAI_API_KEY not set");
return;
}
};
let tools: Vec<Box<dyn McpTool>> = vec![
Box::new(SemanticSearchToolAdapter::new(engine.clone())),
Box::new(FindSimilarCodeToolAdapter::new(engine.clone())),
Box::new(ClusterCodeToolAdapter::new(engine.clone())),
Box::new(AnalyzeTopicsToolAdapter::new(engine)),
];
for tool in tools.iter() {
let metadata = tool.metadata();
assert!(!metadata.name.is_empty());
assert!(!metadata.description.is_empty());
assert!(metadata.input_schema["type"] == "object");
}
let names: Vec<String> = tools.iter().map(|t| t.metadata().name).collect();
assert_eq!(names.len(), 4);
assert!(names.contains(&"semantic_search".to_string()));
assert!(names.contains(&"find_similar_code".to_string()));
assert!(names.contains(&"cluster_code".to_string()));
assert!(names.contains(&"analyze_topics".to_string()));
}