#[cfg(test)]
mod tests {
use crate::mcp::dev::ast_grep::execute_ast_grep_command;
use crate::mcp::dev::shell::execute_shell_command;
use crate::mcp::{extract_mcp_content, McpToolCall};
use serde_json::json;
use tokio;
fn create_shell_call(command: &str, background: Option<bool>) -> McpToolCall {
let mut params = json!({
"command": command
});
if let Some(bg) = background {
params["background"] = json!(bg);
}
McpToolCall {
tool_name: "shell".to_string(),
parameters: params,
tool_id: "test-call-id".to_string(),
}
}
fn create_ast_grep_call(pattern: &str, language: Option<&str>) -> McpToolCall {
let mut params = json!({
"pattern": pattern
});
if let Some(lang) = language {
params["language"] = json!(lang);
}
McpToolCall {
tool_name: "ast_grep".to_string(),
parameters: params,
tool_id: "test-call-id".to_string(),
}
}
#[tokio::test]
async fn test_shell_foreground_simple_command() {
let call = create_shell_call("echo 'Hello, World!'", Some(false));
let result = execute_shell_command(&call).await;
assert!(result.is_ok());
let result = result.unwrap();
assert_eq!(result.tool_name, "shell");
let output = result.result.as_object().unwrap();
assert_eq!(output["isError"], false);
let content = extract_mcp_content(&result.result);
assert!(content.contains("Hello, World!"));
assert!(!output.contains_key("background"));
assert!(!output.contains_key("pid"));
}
#[tokio::test]
async fn test_shell_foreground_command_with_error() {
let call = create_shell_call("ls /nonexistent/directory/path", Some(false));
let result = execute_shell_command(&call).await;
assert!(result.is_ok());
let result = result.unwrap();
let output = result.result.as_object().unwrap();
assert_eq!(output["isError"], true);
let content = extract_mcp_content(&result.result);
assert!(content.contains("Command failed with exit code"));
}
#[tokio::test]
async fn test_shell_background_simple_command() {
let call = create_shell_call("sleep 2", Some(true));
let result = execute_shell_command(&call).await;
assert!(result.is_ok());
let result = result.unwrap();
assert_eq!(result.tool_name, "shell");
let output = result.result.as_object().unwrap();
assert_eq!(output["success"], true);
assert_eq!(output["background"], true);
assert!(output.contains_key("pid"));
assert!(output["message"].as_str().unwrap().contains("background"));
let pid = output["pid"].as_u64().unwrap();
assert!(pid > 0);
let kill_call = create_shell_call(&format!("kill {}", pid), Some(false));
let kill_result = execute_shell_command(&kill_call).await;
assert!(kill_result.is_ok());
}
#[tokio::test]
async fn test_shell_background_long_running_process() {
let call = create_shell_call("sleep 10", Some(true));
let result = execute_shell_command(&call).await;
assert!(result.is_ok());
let result = result.unwrap();
let output = result.result.as_object().unwrap();
assert_eq!(output["success"], true);
assert_eq!(output["background"], true);
let pid = output["pid"].as_u64().unwrap();
let kill_call = create_shell_call(&format!("kill {}", pid), Some(false));
let kill_result = execute_shell_command(&kill_call).await;
assert!(kill_result.is_ok());
}
#[tokio::test]
async fn test_shell_cancellation_foreground() {
let call = create_shell_call("echo 'test'", Some(false));
let result = execute_shell_command(&call).await;
assert!(result.is_ok());
let result = result.unwrap();
let output = result.result.as_object().unwrap();
assert_eq!(output["isError"], false);
let content = extract_mcp_content(&result.result);
assert!(content.contains("test"));
}
#[tokio::test]
async fn test_centralized_cancellation_architecture() {
let shell_call = create_shell_call("echo 'centralized cancellation test'", Some(false));
let shell_result = execute_shell_command(&shell_call).await;
assert!(shell_result.is_ok());
let shell_result = shell_result.unwrap();
let output = shell_result.result.as_object().unwrap();
assert_eq!(output["isError"], false);
let content = extract_mcp_content(&shell_result.result);
assert!(content.contains("centralized cancellation test"));
}
#[tokio::test]
async fn test_shell_default_background_parameter() {
let call = create_shell_call("echo 'test'", None);
let result = execute_shell_command(&call).await;
assert!(result.is_ok());
let result = result.unwrap();
let output = result.result.as_object().unwrap();
assert_eq!(output["isError"], false);
assert!(!output.contains_key("background"));
let content = extract_mcp_content(&result.result);
assert!(content.contains("test"));
}
#[tokio::test]
async fn test_ast_grep_simple_search() {
let temp_content = r#"
function testFunction() {
console.log("test");
return true;
}
"#;
let temp_file = "test_ast_grep_temp.js";
std::fs::write(temp_file, temp_content).unwrap();
let call = create_ast_grep_call("console.log($$$)", Some("javascript"));
let result = execute_ast_grep_command(&call).await;
let _ = std::fs::remove_file(temp_file);
assert!(result.is_ok());
let result = result.unwrap();
assert_eq!(result.tool_name, "ast_grep");
let output = result.result.as_object().unwrap();
assert!(output.contains_key("success"));
}
#[tokio::test]
async fn test_ast_grep_with_invalid_pattern() {
let call = create_ast_grep_call("", Some("javascript"));
let result = execute_ast_grep_command(&call).await;
assert!(result.is_ok());
let result = result.unwrap();
let output = result.result.as_object().unwrap();
assert_eq!(output["isError"], true);
let error_content = &output["content"][0]["text"];
assert!(error_content
.as_str()
.unwrap()
.contains("Pattern parameter cannot be empty"));
}
#[tokio::test]
async fn test_ast_grep_glob_pattern_expansion() {
let temp_dir = tempfile::tempdir().unwrap();
let temp_dir_path = temp_dir.path();
let src_dir = temp_dir_path.join("src");
std::fs::create_dir_all(&src_dir).unwrap();
let test_file1 = src_dir.join("test1.rs");
let test_file2 = src_dir.join("test2.rs");
let test_file3 = temp_dir_path.join("other.txt");
std::fs::write(&test_file1, "fn test_function() { println!(\"test1\"); }").unwrap();
std::fs::write(
&test_file2,
"fn another_function() { println!(\"test2\"); }",
)
.unwrap();
std::fs::write(&test_file3, "not rust code").unwrap();
let glob_pattern = format!("{}/**/*.rs", temp_dir_path.display());
let params = json!({
"pattern": "fn $NAME($ARGS) { $$$ }",
"language": "rust",
"paths": [glob_pattern]
});
let call = McpToolCall {
tool_name: "ast_grep".to_string(),
parameters: params,
tool_id: "test-glob-call-id".to_string(),
};
let result = execute_ast_grep_command(&call).await;
assert!(result.is_ok());
let result = result.unwrap();
assert_eq!(result.tool_name, "ast_grep");
let output = result.result.as_object().unwrap();
assert!(output.contains_key("success"));
}
#[tokio::test]
async fn test_shell_command_history_integration() {
let call = create_shell_call("echo 'history test'", Some(false));
let result = execute_shell_command(&call).await;
assert!(result.is_ok());
let result = result.unwrap();
let output = result.result.as_object().unwrap();
assert_eq!(output["isError"], false);
let content = extract_mcp_content(&result.result);
assert!(content.contains("history test"));
}
#[tokio::test]
async fn test_multiple_background_processes() {
let mut pids = Vec::new();
for i in 1..=3 {
let call = create_shell_call(&format!("sleep {}", i * 2), Some(true));
let result = execute_shell_command(&call).await;
assert!(result.is_ok());
let result = result.unwrap();
let output = result.result.as_object().unwrap();
assert_eq!(output["background"], true);
let pid = output["pid"].as_u64().unwrap();
pids.push(pid);
}
for pid in pids {
let kill_call = create_shell_call(&format!("kill {}", pid), Some(false));
let _ = execute_shell_command(&kill_call).await;
}
}
#[tokio::test]
async fn test_shell_missing_command_parameter() {
let call = McpToolCall {
tool_name: "shell".to_string(),
parameters: json!({}), tool_id: "test-call-id".to_string(),
};
let result = execute_shell_command(&call).await;
assert!(result.is_ok());
let result = result.unwrap();
let output = result.result.as_object().unwrap();
assert_eq!(output["isError"], true);
let error_content = &output["content"][0]["text"];
assert!(error_content
.as_str()
.unwrap()
.contains("Missing required 'command' parameter"));
}
#[tokio::test]
async fn test_ast_grep_missing_pattern_parameter() {
let call = McpToolCall {
tool_name: "ast_grep".to_string(),
parameters: json!({}), tool_id: "test-call-id".to_string(),
};
let result = execute_ast_grep_command(&call).await;
assert!(result.is_ok());
let result = result.unwrap();
let output = result.result.as_object().unwrap();
assert_eq!(output["isError"], true);
let error_content = &output["content"][0]["text"];
assert!(error_content
.as_str()
.unwrap()
.contains("Missing required 'pattern' parameter"));
}
#[tokio::test]
async fn test_ast_grep_output_grouping() {
let temp_content1 = r#"
fn main() {
println!("Hello from main");
}
fn helper() {
println!("Helper function");
}
"#;
let temp_content2 = r#"
pub fn public_func() {
println!("Public function");
}
fn private_func() {
println!("Private function");
}
"#;
let temp_file1 = "test_ast_grep_group1.rs";
let temp_file2 = "test_ast_grep_group2.rs";
std::fs::write(temp_file1, temp_content1).unwrap();
std::fs::write(temp_file2, temp_content2).unwrap();
let params = json!({
"pattern": "fn $NAME($ARGS) { $$$ }",
"language": "rust",
"paths": [temp_file1, temp_file2]
});
let call = McpToolCall {
tool_name: "ast_grep".to_string(),
parameters: params,
tool_id: "test-call-id".to_string(),
};
let result = execute_ast_grep_command(&call).await;
let _ = std::fs::remove_file(temp_file1);
let _ = std::fs::remove_file(temp_file2);
assert!(result.is_ok());
let result = result.unwrap();
assert_eq!(result.tool_name, "ast_grep");
let output = result.result.as_object().unwrap();
assert!(output.contains_key("success"));
let output_text = output["output"].as_str().unwrap_or("");
assert!(
!output_text.contains("syntax error near unexpected token"),
"Shell parsing error detected: {}",
output_text
);
assert!(
!output_text.contains("sh: -c: line 0:"),
"Shell command line error detected: {}",
output_text
);
println!("ast_grep grouped output:\n{}", output_text);
}
#[tokio::test]
async fn test_ast_grep_basic_functionality() {
let temp_content = r#"
fn function1() { println!("1"); }
fn function2() { println!("2"); }
fn function3() { println!("3"); }
fn function4() { println!("4"); }
fn function5() { println!("5"); }
fn function6() { println!("6"); }
fn function7() { println!("7"); }
fn function8() { println!("8"); }
fn function9() { println!("9"); }
fn function10() { println!("10"); }
fn function11() { println!("11"); }
fn function12() { println!("12"); }
fn function13() { println!("13"); }
fn function14() { println!("14"); }
fn function15() { println!("15"); }
"#;
let temp_file = "test_ast_grep_basic.rs";
std::fs::write(temp_file, temp_content).unwrap();
let params = json!({
"pattern": "fn $NAME() { $$$ }",
"language": "rust",
"paths": [temp_file]
});
let call = McpToolCall {
tool_name: "ast_grep".to_string(),
parameters: params,
tool_id: "test-call-id".to_string(),
};
let result = execute_ast_grep_command(&call).await;
let _ = std::fs::remove_file(temp_file);
assert!(result.is_ok());
let result = result.unwrap();
let output = result.result.as_object().unwrap();
let params = &output["parameters"];
assert!(params.get("max_lines").is_none()); assert_eq!(params["pattern"], "fn $NAME() { $$$ }");
assert_eq!(params["language"], "rust");
assert_eq!(output["success"], true);
assert_eq!(output["operation"], "search");
let output_text = output["output"].as_str().unwrap_or("");
println!("ast_grep basic test output:\n{}", output_text);
let params_with_context = json!({
"pattern": "fn $NAME() { $$$ }",
"language": "rust",
"paths": [temp_file],
"context": 1
});
std::fs::write(temp_file, temp_content).unwrap();
let call_with_context = McpToolCall {
tool_name: "ast_grep".to_string(),
parameters: params_with_context,
tool_id: "test-call-id".to_string(),
};
let result_with_context = execute_ast_grep_command(&call_with_context).await;
let _ = std::fs::remove_file(temp_file);
assert!(result_with_context.is_ok());
let result_with_context = result_with_context.unwrap();
let output_with_context = result_with_context.result.as_object().unwrap();
assert_eq!(output_with_context["parameters"]["context"], 1);
}
#[tokio::test]
async fn test_ast_grep_complex_pattern_with_special_characters() {
let temp_content = r#"
async fn process(input: String) -> Result<String, Error> {
println!("Processing: {}", input);
Ok(format!("Processed: {}", input))
}
pub async fn handle_request(req: Request) -> Response {
let result = process(req.body).await;
Response::new(result)
}
"#;
let temp_file = "test_ast_grep_complex.rs";
std::fs::write(temp_file, temp_content).unwrap();
let params = json!({
"pattern": "async fn process($ARGS) { $$$ }",
"language": "rust",
"paths": [temp_file]
});
let call = McpToolCall {
tool_name: "ast_grep".to_string(),
parameters: params,
tool_id: "test-call-id".to_string(),
};
let result = execute_ast_grep_command(&call).await;
let _ = std::fs::remove_file(temp_file);
assert!(result.is_ok());
let result = result.unwrap();
assert_eq!(result.tool_name, "ast_grep");
let output = result.result.as_object().unwrap();
assert!(output.contains_key("success"));
let output_text = output["output"].as_str().unwrap_or("");
assert!(
!output_text.contains("syntax error near unexpected token"),
"Shell parsing error detected: {}",
output_text
);
assert!(
!output_text.contains("sh: -c: line 0:"),
"Shell command line error detected: {}",
output_text
);
println!("ast_grep output: {}", output_text);
}
}