@@ -342,3 +342,141 @@ impl FileLineTracker {
342342 ( self . lines_added_by_agent + self . lines_removed_by_agent ) as isize
343343 }
344344}
345+ #[ cfg( test) ]
346+ mod tests {
347+ use super :: * ;
348+ use crate :: agent:: util:: test:: TestDir ;
349+
350+ #[ tokio:: test]
351+ async fn test_create_file ( ) {
352+ let test_dir = TestDir :: new ( ) ;
353+ let tool = FsWrite :: Create ( FileCreate {
354+ path : test_dir. path ( "new.txt" ) . to_string_lossy ( ) . to_string ( ) ,
355+ content : "hello world" . to_string ( ) ,
356+ } ) ;
357+
358+ assert ! ( tool. validate( ) . await . is_ok( ) ) ;
359+ assert ! ( tool. execute( None ) . await . is_ok( ) ) ;
360+
361+ let content = tokio:: fs:: read_to_string ( test_dir. path ( "new.txt" ) ) . await . unwrap ( ) ;
362+ assert_eq ! ( content, "hello world" ) ;
363+ }
364+
365+ #[ tokio:: test]
366+ async fn test_create_file_with_parent_dirs ( ) {
367+ let test_dir = TestDir :: new ( ) ;
368+ let tool = FsWrite :: Create ( FileCreate {
369+ path : test_dir. path ( "nested/dir/file.txt" ) . to_string_lossy ( ) . to_string ( ) ,
370+ content : "nested content" . to_string ( ) ,
371+ } ) ;
372+
373+ assert ! ( tool. execute( None ) . await . is_ok( ) ) ;
374+
375+ let content = tokio:: fs:: read_to_string ( test_dir. path ( "nested/dir/file.txt" ) )
376+ . await
377+ . unwrap ( ) ;
378+ assert_eq ! ( content, "nested content" ) ;
379+ }
380+
381+ #[ tokio:: test]
382+ async fn test_str_replace_single_occurrence ( ) {
383+ let test_dir = TestDir :: new ( ) . with_file ( ( "test.txt" , "hello world" ) ) . await ;
384+
385+ let tool = FsWrite :: StrReplace ( StrReplace {
386+ path : test_dir. path ( "test.txt" ) . to_string_lossy ( ) . to_string ( ) ,
387+ old_str : "world" . to_string ( ) ,
388+ new_str : "rust" . to_string ( ) ,
389+ replace_all : false ,
390+ } ) ;
391+
392+ assert ! ( tool. execute( None ) . await . is_ok( ) ) ;
393+
394+ let content = tokio:: fs:: read_to_string ( test_dir. path ( "test.txt" ) ) . await . unwrap ( ) ;
395+ assert_eq ! ( content, "hello rust" ) ;
396+ }
397+
398+ #[ tokio:: test]
399+ async fn test_str_replace_multiple_occurrences ( ) {
400+ let test_dir = TestDir :: new ( ) . with_file ( ( "test.txt" , "foo bar foo" ) ) . await ;
401+
402+ let tool = FsWrite :: StrReplace ( StrReplace {
403+ path : test_dir. path ( "test.txt" ) . to_string_lossy ( ) . to_string ( ) ,
404+ old_str : "foo" . to_string ( ) ,
405+ new_str : "baz" . to_string ( ) ,
406+ replace_all : true ,
407+ } ) ;
408+
409+ assert ! ( tool. execute( None ) . await . is_ok( ) ) ;
410+
411+ let content = tokio:: fs:: read_to_string ( test_dir. path ( "test.txt" ) ) . await . unwrap ( ) ;
412+ assert_eq ! ( content, "baz bar baz" ) ;
413+ }
414+
415+ #[ tokio:: test]
416+ async fn test_str_replace_no_match ( ) {
417+ let test_dir = TestDir :: new ( ) . with_file ( ( "test.txt" , "hello world" ) ) . await ;
418+
419+ let tool = FsWrite :: StrReplace ( StrReplace {
420+ path : test_dir. path ( "test.txt" ) . to_string_lossy ( ) . to_string ( ) ,
421+ old_str : "missing" . to_string ( ) ,
422+ new_str : "replacement" . to_string ( ) ,
423+ replace_all : false ,
424+ } ) ;
425+
426+ assert ! ( tool. execute( None ) . await . is_err( ) ) ;
427+ }
428+
429+ #[ tokio:: test]
430+ async fn test_insert_at_line ( ) {
431+ let test_dir = TestDir :: new ( ) . with_file ( ( "test.txt" , "line1\n line2\n line3" ) ) . await ;
432+
433+ let tool = FsWrite :: Insert ( Insert {
434+ path : test_dir. path ( "test.txt" ) . to_string_lossy ( ) . to_string ( ) ,
435+ content : "inserted" . to_string ( ) ,
436+ insert_line : Some ( 1 ) ,
437+ } ) ;
438+
439+ assert ! ( tool. execute( None ) . await . is_ok( ) ) ;
440+
441+ let content = tokio:: fs:: read_to_string ( test_dir. path ( "test.txt" ) ) . await . unwrap ( ) ;
442+ assert_eq ! ( content, "line1\n inserted\n line2\n line3" ) ;
443+ }
444+
445+ #[ tokio:: test]
446+ async fn test_insert_append ( ) {
447+ let test_dir = TestDir :: new ( ) . with_file ( ( "test.txt" , "existing" ) ) . await ;
448+
449+ let tool = FsWrite :: Insert ( Insert {
450+ path : test_dir. path ( "test.txt" ) . to_string_lossy ( ) . to_string ( ) ,
451+ content : "appended" . to_string ( ) ,
452+ insert_line : None ,
453+ } ) ;
454+
455+ assert ! ( tool. execute( None ) . await . is_ok( ) ) ;
456+
457+ let content = tokio:: fs:: read_to_string ( test_dir. path ( "test.txt" ) ) . await . unwrap ( ) ;
458+ assert_eq ! ( content, "existing\n appended" ) ;
459+ }
460+
461+ #[ tokio:: test]
462+ async fn test_fs_write_validate_empty_path ( ) {
463+ let tool = FsWrite :: Create ( FileCreate {
464+ path : "" . to_string ( ) ,
465+ content : "content" . to_string ( ) ,
466+ } ) ;
467+
468+ assert ! ( tool. validate( ) . await . is_err( ) ) ;
469+ }
470+
471+ #[ tokio:: test]
472+ async fn test_fs_write_validate_nonexistent_file_for_replace ( ) {
473+ let tool = FsWrite :: StrReplace ( StrReplace {
474+ path : "/nonexistent/file.txt" . to_string ( ) ,
475+ old_str : "old" . to_string ( ) ,
476+ new_str : "new" . to_string ( ) ,
477+ replace_all : false ,
478+ } ) ;
479+
480+ assert ! ( tool. validate( ) . await . is_err( ) ) ;
481+ }
482+ }
0 commit comments