@@ -172,3 +172,303 @@ pub async fn execute_tool_calls(
172172
173173 ( tools_used, test_source_path, compile_path, messages)
174174}
175+
176+ #[ cfg( test) ]
177+ mod tests {
178+ use super :: * ;
179+ use crate :: agent:: sandbox:: ToolSandbox ;
180+ use crate :: agent:: tool_schema:: ToolRegistry ;
181+ use crate :: agent:: ToolCall ;
182+ use std:: sync:: Arc ;
183+
184+ #[ test]
185+ fn test_create_empty_finding ( ) {
186+ let finding = create_empty_finding ( "test.rs" , 1 , vec ! [ "tool1" . to_string( ) ] , Some ( "test-model" . to_string ( ) ) ) ;
187+
188+ assert ! ( finding. finding. id. is_empty( ) ) ;
189+ assert ! ( finding. finding. title. is_empty( ) ) ;
190+ assert ! ( finding. finding. description. is_empty( ) ) ;
191+ assert_eq ! ( finding. finding. severity, Severity :: Low ) ;
192+ assert_eq ! ( finding. finding. confidence_score, 0.0 ) ;
193+ assert_eq ! ( finding. finding. file_path, "test.rs" ) ;
194+ assert ! ( finding. finding. line_number. is_none( ) ) ;
195+ assert_eq ! ( finding. agent_turns, 1 ) ;
196+ assert_eq ! ( finding. tools_used, vec![ "tool1" . to_string( ) ] ) ;
197+ assert_eq ! ( finding. finding. llm_model, Some ( "test-model" . to_string( ) ) ) ;
198+ assert ! ( finding. finding. agent_mode) ;
199+ }
200+
201+ #[ test]
202+ fn test_create_empty_finding_no_model ( ) {
203+ let finding = create_empty_finding ( "test.rs" , 1 , vec ! [ ] , None ) ;
204+
205+ assert ! ( finding. finding. llm_model. is_none( ) ) ;
206+ assert_eq ! ( finding. agent_turns, 1 ) ;
207+ }
208+
209+ #[ test]
210+ fn test_create_audit_finding ( ) {
211+ let reasoning = "No vulnerabilities found after thorough analysis" ;
212+ let finding = create_audit_finding ( "test.rs" , 2 , vec ! [ "file_read" . to_string( ) ] , reasoning. to_string ( ) , Some ( "model" . to_string ( ) ) ) ;
213+
214+ assert ! ( finding. finding. id. starts_with( "agent-" ) ) ;
215+ assert_eq ! ( finding. finding. title, "Security Audit - No Critical Vulnerabilities Detected" ) ;
216+ assert_eq ! ( finding. finding. description, reasoning) ;
217+ assert_eq ! ( finding. finding. severity, Severity :: Medium ) ;
218+ assert_eq ! ( finding. finding. confidence_score, 0.7 ) ;
219+ assert_eq ! ( finding. finding. file_path, "test.rs" ) ;
220+ assert_eq ! ( finding. agent_turns, 2 ) ;
221+ assert_eq ! ( finding. tools_used, vec![ "file_read" . to_string( ) ] ) ;
222+ }
223+
224+ #[ test]
225+ fn test_create_audit_finding_no_model ( ) {
226+ let finding = create_audit_finding ( "test.rs" , 1 , vec ! [ ] , "reasoning" . to_string ( ) , None ) ;
227+
228+ assert ! ( finding. finding. llm_model. is_none( ) ) ;
229+ }
230+
231+ #[ tokio:: test]
232+ async fn test_execute_tool_calls_single_tool ( ) {
233+ let mut registry = ToolRegistry :: new ( ) ;
234+ registry. register ( Box :: new ( crate :: agent:: tools:: FileReadTool ) ) ;
235+
236+ let tmpdir = tempfile:: tempdir ( ) . unwrap ( ) ;
237+ let path = tmpdir. path ( ) . join ( "test.txt" ) ;
238+ std:: fs:: write ( & path, "hello" ) . unwrap ( ) ;
239+
240+ let sandbox = ToolSandbox :: new ( tmpdir. path ( ) . to_path_buf ( ) , 30 ) ;
241+ let progress_cb: ProgressCallback = Arc :: new ( |_| { } ) ;
242+
243+ let response = ChatResponse {
244+ content : "" . to_string ( ) ,
245+ tool_calls : vec ! [ ToolCall {
246+ id: Some ( "call_1" . to_string( ) ) ,
247+ name: "file_read" . to_string( ) ,
248+ arguments: serde_json:: json!( { "path" : "test.txt" } ) ,
249+ } ] ,
250+ raw : serde_json:: json!( { } ) ,
251+ model_used : "test" . to_string ( ) ,
252+ } ;
253+
254+ let messages = vec ! [ ChatMessage :: user( "test" ) ] ;
255+
256+ let ( tools_used, test_path, compile_path, messages) = execute_tool_calls (
257+ & registry,
258+ & sandbox,
259+ & response,
260+ messages,
261+ & progress_cb,
262+ tmpdir. path ( ) ,
263+ 1 ,
264+ 10 ,
265+ "Turn" ,
266+ ) . await ;
267+
268+ assert_eq ! ( tools_used, vec![ "file_read" . to_string( ) ] ) ;
269+ assert ! ( test_path. is_none( ) ) ;
270+ assert ! ( compile_path. is_none( ) ) ;
271+ assert_eq ! ( messages. len( ) , 3 ) ; // original + assistant call + user result
272+ }
273+
274+ #[ tokio:: test]
275+ async fn test_execute_tool_calls_file_write_path_capture ( ) {
276+ let mut registry = ToolRegistry :: new ( ) ;
277+ registry. register ( Box :: new ( crate :: agent:: tools:: FileWriteTool ) ) ;
278+
279+ let tmpdir = tempfile:: tempdir ( ) . unwrap ( ) ;
280+ let sandbox = ToolSandbox :: new ( tmpdir. path ( ) . to_path_buf ( ) , 30 ) ;
281+ let progress_cb: ProgressCallback = Arc :: new ( |_| { } ) ;
282+
283+ let response = ChatResponse {
284+ content : "" . to_string ( ) ,
285+ tool_calls : vec ! [ ToolCall {
286+ id: Some ( "call_1" . to_string( ) ) ,
287+ name: "file_write" . to_string( ) ,
288+ arguments: serde_json:: json!( { "path" : "test.rs" , "content" : "fn main() {}" } ) ,
289+ } ] ,
290+ raw : serde_json:: json!( { } ) ,
291+ model_used : "test" . to_string ( ) ,
292+ } ;
293+
294+ let messages = vec ! [ ChatMessage :: user( "test" ) ] ;
295+
296+ let ( tools_used, test_path, compile_path, _) = execute_tool_calls (
297+ & registry,
298+ & sandbox,
299+ & response,
300+ messages,
301+ & progress_cb,
302+ tmpdir. path ( ) ,
303+ 1 ,
304+ 10 ,
305+ "Turn" ,
306+ ) . await ;
307+
308+ assert_eq ! ( tools_used, vec![ "file_write" . to_string( ) ] ) ;
309+ assert ! ( test_path. is_some( ) ) ;
310+ assert ! ( compile_path. is_none( ) ) ;
311+ }
312+
313+ #[ tokio:: test]
314+ async fn test_execute_tool_calls_test_compile_path_capture ( ) {
315+ let mut registry = ToolRegistry :: new ( ) ;
316+ registry. register ( Box :: new ( crate :: agent:: tools:: TestCompileTool ) ) ;
317+
318+ let tmpdir = tempfile:: tempdir ( ) . unwrap ( ) ;
319+ let sandbox = ToolSandbox :: new ( tmpdir. path ( ) . to_path_buf ( ) , 30 ) ;
320+ let progress_cb: ProgressCallback = Arc :: new ( |_| { } ) ;
321+
322+ let response = ChatResponse {
323+ content : "" . to_string ( ) ,
324+ tool_calls : vec ! [ ToolCall {
325+ id: Some ( "call_1" . to_string( ) ) ,
326+ name: "test_compile" . to_string( ) ,
327+ arguments: serde_json:: json!( { "source_path" : "test.rs" , "language" : "rust" } ) ,
328+ } ] ,
329+ raw : serde_json:: json!( { } ) ,
330+ model_used : "test" . to_string ( ) ,
331+ } ;
332+
333+ let messages = vec ! [ ChatMessage :: user( "test" ) ] ;
334+
335+ let ( tools_used, test_path, compile_path, _) = execute_tool_calls (
336+ & registry,
337+ & sandbox,
338+ & response,
339+ messages,
340+ & progress_cb,
341+ tmpdir. path ( ) ,
342+ 1 ,
343+ 10 ,
344+ "Turn" ,
345+ ) . await ;
346+
347+ assert_eq ! ( tools_used, vec![ "test_compile" . to_string( ) ] ) ;
348+ assert ! ( test_path. is_none( ) ) ;
349+ assert ! ( compile_path. is_some( ) ) ;
350+ }
351+
352+ #[ tokio:: test]
353+ async fn test_execute_tool_calls_unknown_tool ( ) {
354+ let registry = ToolRegistry :: new ( ) ;
355+ let tmpdir = tempfile:: tempdir ( ) . unwrap ( ) ;
356+ let sandbox = ToolSandbox :: new ( tmpdir. path ( ) . to_path_buf ( ) , 30 ) ;
357+ let progress_cb: ProgressCallback = Arc :: new ( |_| { } ) ;
358+
359+ let response = ChatResponse {
360+ content : "" . to_string ( ) ,
361+ tool_calls : vec ! [ ToolCall {
362+ id: Some ( "call_1" . to_string( ) ) ,
363+ name: "unknown_tool" . to_string( ) ,
364+ arguments: serde_json:: json!( { } ) ,
365+ } ] ,
366+ raw : serde_json:: json!( { } ) ,
367+ model_used : "test" . to_string ( ) ,
368+ } ;
369+
370+ let messages = vec ! [ ChatMessage :: user( "test" ) ] ;
371+
372+ let ( tools_used, _, _, messages) = execute_tool_calls (
373+ & registry,
374+ & sandbox,
375+ & response,
376+ messages,
377+ & progress_cb,
378+ tmpdir. path ( ) ,
379+ 1 ,
380+ 10 ,
381+ "Turn" ,
382+ ) . await ;
383+
384+ assert ! ( tools_used. is_empty( ) ) ;
385+ assert_eq ! ( messages. len( ) , 1 ) ; // No new messages since tool not found
386+ }
387+
388+ #[ tokio:: test]
389+ async fn test_execute_tool_calls_tool_error ( ) {
390+ let mut registry = ToolRegistry :: new ( ) ;
391+ registry. register ( Box :: new ( crate :: agent:: tools:: FileReadTool ) ) ;
392+
393+ let tmpdir = tempfile:: tempdir ( ) . unwrap ( ) ;
394+ let sandbox = ToolSandbox :: new ( tmpdir. path ( ) . to_path_buf ( ) , 30 ) ;
395+ let progress_cb: ProgressCallback = Arc :: new ( |_| { } ) ;
396+
397+ let response = ChatResponse {
398+ content : "" . to_string ( ) ,
399+ tool_calls : vec ! [ ToolCall {
400+ id: Some ( "call_1" . to_string( ) ) ,
401+ name: "file_read" . to_string( ) ,
402+ arguments: serde_json:: json!( { "path" : "nonexistent.txt" } ) ,
403+ } ] ,
404+ raw : serde_json:: json!( { } ) ,
405+ model_used : "test" . to_string ( ) ,
406+ } ;
407+
408+ let messages = vec ! [ ChatMessage :: user( "test" ) ] ;
409+
410+ let ( _, _, _, messages) = execute_tool_calls (
411+ & registry,
412+ & sandbox,
413+ & response,
414+ messages,
415+ & progress_cb,
416+ tmpdir. path ( ) ,
417+ 1 ,
418+ 10 ,
419+ "Turn" ,
420+ ) . await ;
421+
422+ // Should have assistant call + user error message
423+ assert_eq ! ( messages. len( ) , 3 ) ;
424+ assert ! ( messages[ 2 ] . content. contains( "error" ) ) ;
425+ }
426+
427+ #[ tokio:: test]
428+ async fn test_execute_tool_calls_multiple_tools ( ) {
429+ let mut registry = ToolRegistry :: new ( ) ;
430+ registry. register ( Box :: new ( crate :: agent:: tools:: FileReadTool ) ) ;
431+ registry. register ( Box :: new ( crate :: agent:: tools:: FileWriteTool ) ) ;
432+
433+ let tmpdir = tempfile:: tempdir ( ) . unwrap ( ) ;
434+ let sandbox = ToolSandbox :: new ( tmpdir. path ( ) . to_path_buf ( ) , 30 ) ;
435+ let progress_cb: ProgressCallback = Arc :: new ( |_| { } ) ;
436+
437+ let response = ChatResponse {
438+ content : "" . to_string ( ) ,
439+ tool_calls : vec ! [
440+ ToolCall {
441+ id: Some ( "call_1" . to_string( ) ) ,
442+ name: "file_read" . to_string( ) ,
443+ arguments: serde_json:: json!( { "path" : "test.txt" } ) ,
444+ } ,
445+ ToolCall {
446+ id: Some ( "call_2" . to_string( ) ) ,
447+ name: "file_write" . to_string( ) ,
448+ arguments: serde_json:: json!( { "path" : "out.txt" , "content" : "test" } ) ,
449+ } ,
450+ ] ,
451+ raw : serde_json:: json!( { } ) ,
452+ model_used : "test" . to_string ( ) ,
453+ } ;
454+
455+ let messages = vec ! [ ChatMessage :: user( "test" ) ] ;
456+
457+ let ( tools_used, test_path, _, _) = execute_tool_calls (
458+ & registry,
459+ & sandbox,
460+ & response,
461+ messages,
462+ & progress_cb,
463+ tmpdir. path ( ) ,
464+ 1 ,
465+ 10 ,
466+ "Turn" ,
467+ ) . await ;
468+
469+ assert_eq ! ( tools_used. len( ) , 2 ) ;
470+ assert ! ( tools_used. contains( & "file_read" . to_string( ) ) ) ;
471+ assert ! ( tools_used. contains( & "file_write" . to_string( ) ) ) ;
472+ assert ! ( test_path. is_some( ) ) ;
473+ }
474+ }
0 commit comments