use tools_fs::{read_file, glob_list, grep, write_file, edit_file, multi_edit_file, list_directory, EditOperation}; use std::fs; use tempfile::tempdir; #[test] fn read_and_glob_respect_gitignore() { let dir = tempdir().unwrap(); let root = dir.path(); fs::write(root.join("a.txt"), "hello").unwrap(); fs::create_dir(root.join("secret")).unwrap(); fs::write(root.join("secret/secret.txt"), "token=123").unwrap(); fs::write(root.join(".gitignore"), "secret/\n").unwrap(); let pattern = format!("{}/**/*", root.display()); let files = glob_list(&pattern).unwrap(); assert!(files.iter().any(|p| p.ends_with("a.txt"))); assert!(!files.iter().any(|p| p.contains("secret.txt"))); assert_eq!(read_file(root.join("a.txt").to_str().unwrap()).unwrap(), "hello"); } #[test] fn grep_finds_lines() { let dir = tempdir().unwrap(); let root = dir.path(); fs::write(root.join("a.rs"), "fn main() { println!(\"hello\"); }").unwrap(); let hits = grep(root.to_str().unwrap(), "hello").unwrap(); assert!(hits.iter().any(|(_p, _ln, text)| text.contains("hello"))); } #[test] fn write_file_creates_new_file() { let dir = tempdir().unwrap(); let file_path = dir.path().join("new.txt"); write_file(file_path.to_str().unwrap(), "new content").unwrap(); assert_eq!(read_file(file_path.to_str().unwrap()).unwrap(), "new content"); } #[test] fn write_file_overwrites_existing() { let dir = tempdir().unwrap(); let file_path = dir.path().join("existing.txt"); fs::write(&file_path, "old content").unwrap(); write_file(file_path.to_str().unwrap(), "new content").unwrap(); assert_eq!(read_file(file_path.to_str().unwrap()).unwrap(), "new content"); } #[test] fn edit_file_replaces_exact_match() { let dir = tempdir().unwrap(); let file_path = dir.path().join("test.txt"); let original = "line 1\nline 2\nline 3\n"; fs::write(&file_path, original).unwrap(); edit_file(file_path.to_str().unwrap(), "line 2", "modified line 2").unwrap(); let result = read_file(file_path.to_str().unwrap()).unwrap(); assert_eq!(result, "line 1\nmodified line 2\nline 3\n"); } #[test] fn edit_file_replaces_multiline() { let dir = tempdir().unwrap(); let file_path = dir.path().join("test.txt"); let original = "line 1\nline 2\nline 3\nline 4\n"; fs::write(&file_path, original).unwrap(); edit_file(file_path.to_str().unwrap(), "line 2\nline 3", "new content").unwrap(); let result = read_file(file_path.to_str().unwrap()).unwrap(); assert_eq!(result, "line 1\nnew content\nline 4\n"); } #[test] fn edit_file_fails_on_ambiguous_match() { let dir = tempdir().unwrap(); let file_path = dir.path().join("test.txt"); let original = "duplicate\nsome text\nduplicate\n"; fs::write(&file_path, original).unwrap(); let result = edit_file(file_path.to_str().unwrap(), "duplicate", "changed"); assert!(result.is_err()); let err_msg = result.unwrap_err().to_string(); assert!(err_msg.contains("Ambiguous") || err_msg.contains("multiple") || err_msg.contains("occurrences")); } #[test] fn edit_file_fails_on_no_match() { let dir = tempdir().unwrap(); let file_path = dir.path().join("test.txt"); let original = "line 1\nline 2\n"; fs::write(&file_path, original).unwrap(); let result = edit_file(file_path.to_str().unwrap(), "nonexistent", "changed"); assert!(result.is_err()); let err_msg = result.unwrap_err().to_string(); assert!(err_msg.contains("not found") || err_msg.contains("String to replace")); } #[test] fn multi_edit_file_applies_multiple_edits() { let dir = tempdir().unwrap(); let file_path = dir.path().join("test.txt"); let original = "line 1\nline 2\nline 3\n"; fs::write(&file_path, original).unwrap(); let edits = vec![ EditOperation { old_string: "line 1".to_string(), new_string: "modified 1".to_string(), }, EditOperation { old_string: "line 2".to_string(), new_string: "modified 2".to_string(), }, ]; let result = multi_edit_file(file_path.to_str().unwrap(), edits).unwrap(); assert!(result.contains("Applied 2 edits")); let content = read_file(file_path.to_str().unwrap()).unwrap(); assert_eq!(content, "modified 1\nmodified 2\nline 3\n"); // Backup file should exist let backup_path = format!("{}.bak", file_path.display()); assert!(std::path::Path::new(&backup_path).exists()); } #[test] fn multi_edit_file_fails_on_missing_string() { let dir = tempdir().unwrap(); let file_path = dir.path().join("test.txt"); let original = "line 1\nline 2\n"; fs::write(&file_path, original).unwrap(); let edits = vec![ EditOperation { old_string: "line 1".to_string(), new_string: "modified 1".to_string(), }, EditOperation { old_string: "nonexistent".to_string(), new_string: "modified".to_string(), }, ]; let result = multi_edit_file(file_path.to_str().unwrap(), edits); assert!(result.is_err()); let err_msg = result.unwrap_err().to_string(); assert!(err_msg.contains("String not found")); } #[test] fn list_directory_shows_files_and_dirs() { let dir = tempdir().unwrap(); let root = dir.path(); // Create test structure fs::write(root.join("file1.txt"), "content").unwrap(); fs::write(root.join("file2.txt"), "content").unwrap(); fs::create_dir(root.join("subdir")).unwrap(); fs::write(root.join(".hidden"), "hidden content").unwrap(); let entries = list_directory(root.to_str().unwrap(), false).unwrap(); // Should find 2 files and 1 directory (hidden file excluded) assert_eq!(entries.len(), 3); // Verify directory appears first (sorted) assert_eq!(entries[0].name, "subdir"); assert!(entries[0].is_dir); // Verify files let file_names: Vec<_> = entries.iter().skip(1).map(|e| e.name.as_str()).collect(); assert!(file_names.contains(&"file1.txt")); assert!(file_names.contains(&"file2.txt")); // Hidden file should not be present assert!(!entries.iter().any(|e| e.name == ".hidden")); } #[test] fn list_directory_shows_hidden_when_requested() { let dir = tempdir().unwrap(); let root = dir.path(); fs::write(root.join("visible.txt"), "content").unwrap(); fs::write(root.join(".hidden"), "hidden content").unwrap(); let entries = list_directory(root.to_str().unwrap(), true).unwrap(); // Should find both files assert!(entries.iter().any(|e| e.name == "visible.txt")); assert!(entries.iter().any(|e| e.name == ".hidden")); } #[test] fn list_directory_includes_metadata() { let dir = tempdir().unwrap(); let root = dir.path(); fs::write(root.join("test.txt"), "hello world").unwrap(); fs::create_dir(root.join("testdir")).unwrap(); let entries = list_directory(root.to_str().unwrap(), false).unwrap(); // Directory entry should have no size let dir_entry = entries.iter().find(|e| e.name == "testdir").unwrap(); assert!(dir_entry.is_dir); assert!(dir_entry.size.is_none()); assert!(dir_entry.modified.is_some()); // File entry should have size let file_entry = entries.iter().find(|e| e.name == "test.txt").unwrap(); assert!(!file_entry.is_dir); assert_eq!(file_entry.size, Some(11)); // "hello world" is 11 bytes assert!(file_entry.modified.is_some()); }