An AI agent built to do Ralph loops - plan mode for planning and ralph mode for implementing.
at new-directions 249 lines 8.1 kB view raw
1use rustagent::tools::Tool; 2use rustagent::tools::search::CodeSearchTool; 3use serde_json::json; 4use tempfile::TempDir; 5 6/// Integration test for code_search functionality 7/// Verifies AC9.1: Search finds known pattern with file path and line number 8#[tokio::test] 9async fn code_search_integration_finds_pattern() { 10 let temp_dir = TempDir::new().unwrap(); 11 let project_root = temp_dir.path().to_path_buf(); 12 13 // Create test directory structure 14 std::fs::create_dir_all(project_root.join("src")).unwrap(); 15 std::fs::write( 16 project_root.join("src/main.rs"), 17 "fn main() {\n println!(\"hello\");\n}", 18 ) 19 .unwrap(); 20 std::fs::write( 21 project_root.join("src/lib.rs"), 22 "pub fn add(a: i32, b: i32) -> i32 { a + b }", 23 ) 24 .unwrap(); 25 26 let tool = CodeSearchTool::new(project_root); 27 let params = json!({ 28 "pattern": "fn main" 29 }); 30 31 let result = tool.execute(params).await.unwrap(); 32 assert!(result.contains("src/main.rs:1:")); 33 assert!(result.contains("fn main")); 34} 35 36/// Integration test for code_search with file glob filter 37/// Verifies AC9.2: File glob filter limits search to matching files 38#[tokio::test] 39async fn code_search_integration_with_file_glob() { 40 let temp_dir = TempDir::new().unwrap(); 41 let project_root = temp_dir.path().to_path_buf(); 42 43 // Create test directory structure 44 std::fs::create_dir_all(project_root.join("src")).unwrap(); 45 std::fs::write(project_root.join("src/main.rs"), "fn main() {}").unwrap(); 46 std::fs::write( 47 project_root.join("src/lib.rs"), 48 "pub fn add(a: i32, b: i32) -> i32 { a + b }", 49 ) 50 .unwrap(); 51 std::fs::write( 52 project_root.join("README.md"), 53 "# My Project\npub fn should_not_match", 54 ) 55 .unwrap(); 56 57 let tool = CodeSearchTool::new(project_root); 58 let params = json!({ 59 "pattern": "pub fn", 60 "file_glob": "*.rs" 61 }); 62 63 let result = tool.execute(params).await.unwrap(); 64 // Should find in lib.rs 65 assert!(result.contains("lib.rs")); 66 // Should NOT find in README.md 67 assert!(!result.contains("README.md")); 68} 69 70/// Integration test for result capping 71/// Verifies AC9.3: Results are capped at configurable limit 72#[tokio::test] 73async fn code_search_integration_result_capping() { 74 let temp_dir = TempDir::new().unwrap(); 75 let project_root = temp_dir.path().to_path_buf(); 76 77 // Create test file with multiple matches 78 std::fs::write( 79 project_root.join("test.rs"), 80 "fn one()\nfn two()\nfn three()\nfn four()\nfn five()", 81 ) 82 .unwrap(); 83 84 let tool = CodeSearchTool::new(project_root); 85 let params = json!({ 86 "pattern": "fn", 87 "max_results": 1 88 }); 89 90 let result = tool.execute(params).await.unwrap(); 91 let lines: Vec<&str> = result.lines().collect(); 92 // Should have 1 match line + 1 capped message line = 2 total 93 assert_eq!(lines.len(), 2); 94 assert!(result.contains("Found 2 matches (limited to max_results: 1)")); 95} 96 97/// Integration test for no matches 98/// Verifies that searching for non-existent pattern returns appropriate message 99#[tokio::test] 100async fn code_search_integration_no_matches() { 101 let temp_dir = TempDir::new().unwrap(); 102 let project_root = temp_dir.path().to_path_buf(); 103 104 std::fs::write(project_root.join("test.rs"), "fn main() {}").unwrap(); 105 106 let tool = CodeSearchTool::new(project_root); 107 let params = json!({ 108 "pattern": "nonexistent_pattern" 109 }); 110 111 let result = tool.execute(params).await.unwrap(); 112 assert_eq!(result, "No matches found"); 113} 114 115/// Integration test for directory scoping 116/// Verifies that directory parameter scopes search correctly 117#[tokio::test] 118async fn code_search_integration_with_directory_scope() { 119 let temp_dir = TempDir::new().unwrap(); 120 let project_root = temp_dir.path().to_path_buf(); 121 122 // Create directory structure 123 std::fs::create_dir_all(project_root.join("src/utils")).unwrap(); 124 std::fs::write(project_root.join("main.rs"), "fn main() {}").unwrap(); 125 std::fs::write( 126 project_root.join("src/utils/helper.rs"), 127 "pub fn helper() {}", 128 ) 129 .unwrap(); 130 131 let tool = CodeSearchTool::new(project_root); 132 let params = json!({ 133 "pattern": "fn", 134 "directory": "src" 135 }); 136 137 let result = tool.execute(params).await.unwrap(); 138 assert!(result.contains("utils/helper.rs")); 139 assert!(!result.contains("main.rs")); 140} 141 142/// Integration test for multiple patterns in multiple files 143/// Verifies comprehensive search functionality 144#[tokio::test] 145async fn code_search_integration_multiple_files() { 146 let temp_dir = TempDir::new().unwrap(); 147 let project_root = temp_dir.path().to_path_buf(); 148 149 std::fs::create_dir_all(project_root.join("src")).unwrap(); 150 std::fs::write( 151 project_root.join("src/main.rs"), 152 "fn main() {\n println!(\"hello\");\n}", 153 ) 154 .unwrap(); 155 std::fs::write( 156 project_root.join("src/lib.rs"), 157 "pub fn add(a: i32, b: i32) -> i32 { a + b }", 158 ) 159 .unwrap(); 160 161 let tool = CodeSearchTool::new(project_root); 162 let params = json!({ 163 "pattern": "pub fn" 164 }); 165 166 let result = tool.execute(params).await.unwrap(); 167 // Should find pub fn in lib.rs 168 assert!(result.contains("lib.rs")); 169 // Should not find in main.rs (no pub fn there) 170 assert!(!result.contains("main.rs") || !result.contains("fn main")); 171} 172 173/// Verify code_search tool is properly registered in V2 registry 174/// Verifies AC10.1: Tool is available in V2 registry 175#[tokio::test] 176async fn code_search_tool_is_registered() { 177 use rustagent::config::{SecurityConfig, ShellPolicy}; 178 use rustagent::db::Database; 179 use rustagent::graph::store::SqliteGraphStore; 180 use rustagent::security::SecurityValidator; 181 use rustagent::security::permission::AutoApproveHandler; 182 use rustagent::tools::factory::create_v2_registry; 183 use std::path::Path; 184 use std::sync::Arc; 185 186 // Create an in-memory database 187 let db = Database::open(Path::new(":memory:")) 188 .await 189 .expect("Failed to create database"); 190 let graph_store = SqliteGraphStore::new(db); 191 192 let security_config = SecurityConfig { 193 shell_policy: ShellPolicy::Unrestricted, 194 allowed_commands: vec![], 195 blocked_patterns: vec![], 196 max_file_size_mb: 100, 197 allowed_paths: vec![], 198 }; 199 let validator = Arc::new(SecurityValidator::new(security_config).unwrap()); 200 let permission_handler = Arc::new(AutoApproveHandler); 201 let project_root = tempfile::TempDir::new().unwrap().path().to_path_buf(); 202 203 let registry = create_v2_registry( 204 validator, 205 permission_handler, 206 Arc::new(graph_store), 207 None, 208 None, 209 project_root, 210 ); 211 212 let tools = registry.list(); 213 assert!( 214 tools.contains(&"code_search".to_string()), 215 "code_search tool not found in registry. Available tools: {:?}", 216 tools 217 ); 218} 219 220/// Verify code_search tool JSON schema 221/// Verifies AC10.2: Tool schema describes all parameters 222#[tokio::test] 223async fn code_search_tool_parameters_schema() { 224 let temp_dir = TempDir::new().unwrap(); 225 let project_root = temp_dir.path().to_path_buf(); 226 227 let tool = CodeSearchTool::new(project_root); 228 let schema = tool.parameters(); 229 230 // Verify schema structure 231 assert_eq!(schema["type"], "object"); 232 233 // Verify required parameter 234 let required = &schema["required"]; 235 assert!(required.is_array()); 236 let required_array = required.as_array().unwrap(); 237 assert!(required_array.contains(&serde_json::Value::String("pattern".to_string()))); 238 239 // Verify properties exist 240 let properties = &schema["properties"]; 241 assert!(properties.get("pattern").is_some()); 242 assert!(properties.get("file_glob").is_some()); 243 assert!(properties.get("directory").is_some()); 244 assert!(properties.get("max_results").is_some()); 245 246 // Verify pattern is required 247 assert_eq!(required_array.len(), 1); 248 assert_eq!(required_array[0], "pattern"); 249}