use crate::mcp_server::tools::analyze_symbols::{AnalyzeSymbolContextTool, AnalyzerResult};
use crate::project::{ProjectScanner, WorkspaceSession};
use crate::test_utils::integration::TestProject;
use tracing::info;
#[cfg(feature = "clangd-integration-tests")]
#[tokio::test]
async fn test_analyzer_members_math() {
let test_project = TestProject::new().await.unwrap();
test_project.cmake_configure().await.unwrap();
let scanner = ProjectScanner::with_default_providers();
let workspace = scanner
.scan_project(&test_project.project_root, 3, None)
.expect("Failed to scan test project");
let clangd_path = crate::test_utils::get_test_clangd_path();
let workspace_session = WorkspaceSession::new(workspace.clone(), clangd_path)
.expect("Failed to create workspace session");
let tool = AnalyzeSymbolContextTool {
symbol: "Math".to_string(),
build_directory: None,
max_examples: Some(2),
location_hint: None,
wait_timeout: None,
};
let component_session = workspace_session
.get_component_session(test_project.build_dir.clone())
.await
.unwrap();
let result = tool.call_tool(component_session, &workspace).await;
assert!(result.is_ok());
let call_result = result.unwrap();
let text = if let Some(rust_mcp_sdk::schema::ContentBlock::TextContent(
rust_mcp_sdk::schema::TextContent { text, .. },
)) = call_result.content.first()
{
text
} else {
panic!("Expected TextContent in call_result");
};
let analyzer_result: AnalyzerResult = serde_json::from_str(text).unwrap();
assert_eq!(analyzer_result.symbol.name, "Math");
assert_eq!(analyzer_result.symbol.kind, lsp_types::SymbolKind::CLASS);
assert!(analyzer_result.members.is_some());
let members = analyzer_result.members.unwrap();
info!("Math class members analysis:");
info!(
" Total: {} methods, {} constructors, {} destructors, {} operators",
members.methods.len(),
members.constructors.len(),
members.destructors.len(),
members.operators.len()
);
info!(" Methods ({}):", members.methods.len());
for (i, method) in members.methods.iter().enumerate() {
info!(
" {}: {} (type: {}, signature: {})",
i + 1,
method.name,
method.member_type,
method.signature
);
}
info!(" Constructors ({}):", members.constructors.len());
for (i, constructor) in members.constructors.iter().enumerate() {
info!(
" {}: {} (type: {}, signature: {})",
i + 1,
constructor.name,
constructor.member_type,
constructor.signature
);
}
info!(" Destructors ({}):", members.destructors.len());
for (i, destructor) in members.destructors.iter().enumerate() {
info!(
" {}: {} (type: {}, signature: {})",
i + 1,
destructor.name,
destructor.member_type,
destructor.signature
);
}
info!(" Operators ({}):", members.operators.len());
for (i, operator) in members.operators.iter().enumerate() {
info!(
" {}: {} (type: {}, signature: {})",
i + 1,
operator.name,
operator.member_type,
operator.signature
);
}
assert!(!members.methods.is_empty());
let method_names: Vec<&String> = members.methods.iter().map(|m| &m.name).collect();
assert!(method_names.iter().any(|name| name.contains("factorial")));
}
#[cfg(feature = "clangd-integration-tests")]
#[tokio::test]
async fn test_analyzer_members_interface() {
let test_project = TestProject::new().await.unwrap();
test_project.cmake_configure().await.unwrap();
let scanner = ProjectScanner::with_default_providers();
let workspace = scanner
.scan_project(&test_project.project_root, 3, None)
.expect("Failed to scan test project");
let clangd_path = crate::test_utils::get_test_clangd_path();
let workspace_session = WorkspaceSession::new(workspace.clone(), clangd_path)
.expect("Failed to create workspace session");
let tool = AnalyzeSymbolContextTool {
symbol: "IStorageBackend".to_string(),
build_directory: None,
max_examples: Some(2),
location_hint: None,
wait_timeout: None,
};
let component_session = workspace_session
.get_component_session(test_project.build_dir.clone())
.await
.unwrap();
let result = tool.call_tool(component_session, &workspace).await;
assert!(result.is_ok());
let call_result = result.unwrap();
let text = if let Some(rust_mcp_sdk::schema::ContentBlock::TextContent(
rust_mcp_sdk::schema::TextContent { text, .. },
)) = call_result.content.first()
{
text
} else {
panic!("Expected TextContent in call_result");
};
let analyzer_result: AnalyzerResult = serde_json::from_str(text).unwrap();
assert_eq!(analyzer_result.symbol.name, "IStorageBackend");
assert_eq!(analyzer_result.symbol.kind, lsp_types::SymbolKind::CLASS);
assert!(analyzer_result.members.is_some());
let members = analyzer_result.members.unwrap();
info!("IStorageBackend interface members analysis:");
info!(
" Total: {} methods, {} constructors, {} destructors, {} operators",
members.methods.len(),
members.constructors.len(),
members.destructors.len(),
members.operators.len()
);
info!(" Methods ({}):", members.methods.len());
for (i, method) in members.methods.iter().enumerate() {
info!(
" {}: {} (type: {}, signature: {})",
i + 1,
method.name,
method.member_type,
method.signature
);
}
info!(" Constructors ({}):", members.constructors.len());
for (i, constructor) in members.constructors.iter().enumerate() {
info!(
" {}: {} (type: {}, signature: {})",
i + 1,
constructor.name,
constructor.member_type,
constructor.signature
);
}
info!(" Destructors ({}):", members.destructors.len());
for (i, destructor) in members.destructors.iter().enumerate() {
info!(
" {}: {} (type: {}, signature: {})",
i + 1,
destructor.name,
destructor.member_type,
destructor.signature
);
}
info!(" Operators ({}):", members.operators.len());
for (i, operator) in members.operators.iter().enumerate() {
info!(
" {}: {} (type: {}, signature: {})",
i + 1,
operator.name,
operator.member_type,
operator.signature
);
}
assert!(!members.methods.is_empty());
let method_names: Vec<&String> = members.methods.iter().map(|m| &m.name).collect();
assert!(method_names.iter().any(|name| name.contains("store")
|| name.contains("retrieve")
|| name.contains("remove")));
}
#[cfg(feature = "clangd-integration-tests")]
#[tokio::test]
async fn test_analyzer_members_non_class() {
let test_project = TestProject::new().await.unwrap();
test_project.cmake_configure().await.unwrap();
let scanner = ProjectScanner::with_default_providers();
let workspace = scanner
.scan_project(&test_project.project_root, 3, None)
.expect("Failed to scan test project");
let clangd_path = crate::test_utils::get_test_clangd_path();
let workspace_session = WorkspaceSession::new(workspace.clone(), clangd_path)
.expect("Failed to create workspace session");
let tool = AnalyzeSymbolContextTool {
symbol: "factorial".to_string(),
build_directory: None,
max_examples: Some(2),
location_hint: None,
wait_timeout: None,
};
let component_session = workspace_session
.get_component_session(test_project.build_dir.clone())
.await
.unwrap();
let result = tool.call_tool(component_session, &workspace).await;
assert!(result.is_ok());
let call_result = result.unwrap();
let text = if let Some(rust_mcp_sdk::schema::ContentBlock::TextContent(
rust_mcp_sdk::schema::TextContent { text, .. },
)) = call_result.content.first()
{
text
} else {
panic!("Expected TextContent in call_result");
};
let analyzer_result: AnalyzerResult = serde_json::from_str(text).unwrap();
assert_eq!(analyzer_result.symbol.name, "factorial");
assert_eq!(analyzer_result.symbol.kind, lsp_types::SymbolKind::METHOD);
assert!(analyzer_result.members.is_none());
assert!(analyzer_result.call_hierarchy.is_some());
info!(
"factorial function - has members: {}, has call hierarchy: {}",
analyzer_result.members.is_some(),
analyzer_result.call_hierarchy.is_some()
);
}