An AI agent built to do Ralph loops - plan mode for planning and ralph mode for implementing.
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}