Skip to main content
Glama
8b-is
by 8b-is
test_tools_st_only.rs•14.5 kB
// Test suite for Smart Tree Only Tools // "One tool to test them all!" - Testy McTesterson đź§Ş use anyhow::Result; use st::tools_st_only::{ListOptions, SearchOptions, StOnlyTools, StToolsConfig}; use std::fs; use std::path::{Path, PathBuf}; use tempfile::TempDir; fn create_complex_test_directory() -> Result<TempDir> { let temp_dir = TempDir::new()?; // Create a more complex structure for thorough testing fs::create_dir_all(temp_dir.path().join("src/modules/core"))?; fs::create_dir_all(temp_dir.path().join("src/modules/utils"))?; fs::create_dir_all(temp_dir.path().join("tests/unit"))?; fs::create_dir_all(temp_dir.path().join("tests/integration"))?; fs::create_dir_all(temp_dir.path().join("docs/api"))?; fs::create_dir_all(temp_dir.path().join("examples"))?; fs::create_dir_all(temp_dir.path().join(".git/hooks"))?; // Create various file types fs::write( temp_dir.path().join("README.md"), "# Complex Project\n\nWith multiple sections", )?; fs::write( temp_dir.path().join("Cargo.toml"), "[package]\nname = \"complex\"\nversion = \"0.1.0\"", )?; fs::write( temp_dir.path().join("src/main.rs"), "fn main() {\n // TODO: Implement\n println!(\"Complex app\");\n}", )?; fs::write( temp_dir.path().join("src/lib.rs"), "//! Library root\n\npub mod modules;", )?; fs::write( temp_dir.path().join("src/modules/core/engine.rs"), "pub struct Engine {\n // TODO: Add fields\n}", )?; fs::write( temp_dir.path().join("src/modules/utils/helpers.rs"), "pub fn helper() -> bool {\n true\n}", )?; fs::write( temp_dir.path().join("tests/unit/test_engine.rs"), "#[test]\nfn test_engine() {\n // TODO: Write test\n}", )?; fs::write( temp_dir.path().join("docs/api/engine.md"), "# Engine API\n\nTODO: Document", )?; fs::write( temp_dir.path().join(".gitignore"), "target/\n*.tmp\n.DS_Store", )?; Ok(temp_dir) } #[test] fn test_config_default() { // Testing default configuration - where assumptions go to die! let config = StToolsConfig::default(); assert_eq!(config.default_mode, "ai", "Default mode should be 'ai'"); assert!(!config.use_emoji, "Emoji should be disabled by default"); assert!( !config.compress, "Compression should be disabled by default" ); assert!( config.st_binary.to_string_lossy().contains("st"), "Binary path should contain 'st'" ); } #[test] fn test_config_custom() { // Custom config - because one size never fits all! let config = StToolsConfig { st_binary: PathBuf::from("/custom/path/st"), default_mode: "quantum".to_string(), use_emoji: true, compress: true, }; let _tools = StOnlyTools::with_config(config.clone()); // The config should be stored internally // We can't directly access it, but we can test behavior assert_eq!(config.st_binary.to_str().unwrap(), "/custom/path/st"); } #[test] fn test_list_basic() -> Result<()> { // Basic listing - the foundation of file exploration! let temp_dir = create_complex_test_directory()?; let tools = StOnlyTools::new(); let options = ListOptions::default(); let result = tools.list(temp_dir.path(), options)?; // Should list top-level items assert!( result.contains("README.md") || result.contains("readme"), "Should list README.md" ); assert!( result.contains("src") || result.contains("SRC"), "Should list src directory" ); Ok(()) } #[test] fn test_list_with_pattern() -> Result<()> { // Pattern filtering - where wildcards meet their match! let temp_dir = create_complex_test_directory()?; let tools = StOnlyTools::new(); let options = ListOptions { pattern: Some("*.md".to_string()), ..Default::default() }; let result = tools.list(temp_dir.path(), options)?; assert!( result.contains("README.md") || result.contains("readme"), "Should find README.md with *.md pattern" ); Ok(()) } #[test] fn test_list_with_file_type() -> Result<()> { // File type filtering - segregation for better organization! let temp_dir = create_complex_test_directory()?; let tools = StOnlyTools::new(); let options = ListOptions { file_type: Some("rs".to_string()), ..Default::default() }; let result = tools.list(temp_dir.path(), options)?; // Should only show Rust files assert!( result.contains(".rs") || result.contains("main") || result.contains("lib"), "Should filter for Rust files" ); Ok(()) } #[test] fn test_list_with_sort() -> Result<()> { // Sorting - because chaos is not a feature! let temp_dir = create_complex_test_directory()?; let tools = StOnlyTools::new(); let sort_options = vec!["name", "size", "date"]; for sort in sort_options { let options = ListOptions { sort: Some(sort.to_string()), ..Default::default() }; let result = tools.list(temp_dir.path(), options)?; assert!(!result.is_empty(), "Sort by {} should produce output", sort); } Ok(()) } #[test] fn test_list_with_limit() -> Result<()> { // Limiting results - pagination's best friend! let temp_dir = create_complex_test_directory()?; let tools = StOnlyTools::new(); let options = ListOptions { limit: Some(3), ..Default::default() }; let result = tools.list(temp_dir.path(), options)?; // Can't easily count lines in output, but should have content assert!(!result.is_empty(), "Limited list should still have output"); Ok(()) } #[test] fn test_search_basic() -> Result<()> { // Search functionality - finding needles in haystacks! let temp_dir = create_complex_test_directory()?; let tools = StOnlyTools::new(); let options = SearchOptions::default(); let result = tools.search("TODO", temp_dir.path(), options)?; assert!( result.contains("TODO") || result.contains("main.rs") || result.contains("engine"), "Should find TODO comments" ); Ok(()) } #[test] fn test_search_with_file_type() -> Result<()> { // Type-specific search - because context matters! let temp_dir = create_complex_test_directory()?; let tools = StOnlyTools::new(); let options = SearchOptions { file_type: Some("md".to_string()), ..Default::default() }; let result = tools.search("TODO", temp_dir.path(), options)?; // Should only search in markdown files assert!( result.contains("TODO") || result.contains("api") || result.contains("Document"), "Should find TODO in markdown files" ); Ok(()) } #[test] fn test_search_case_sensitivity() -> Result<()> { // Case sensitivity - the eternal debate! let temp_dir = TempDir::new()?; fs::write(temp_dir.path().join("test.txt"), "TODO\ntodo\nToDo")?; let tools = StOnlyTools::new(); let options = SearchOptions { case_sensitive: true, ..Default::default() }; let result = tools.search("TODO", temp_dir.path(), options)?; // Should handle case-sensitive search assert!(!result.is_empty(), "Should find case-sensitive matches"); Ok(()) } #[test] fn test_overview() -> Result<()> { // Project overview - the bird's eye view! let temp_dir = create_complex_test_directory()?; let tools = StOnlyTools::new(); let result = tools.overview(temp_dir.path(), None)?; assert!( result.contains("STATS") || result.contains("F") || result.contains("D"), "Overview should contain statistics" ); Ok(()) } #[test] fn test_overview_with_depth() -> Result<()> { // Depth control - how deep is your directory tree? let temp_dir = create_complex_test_directory()?; let tools = StOnlyTools::new(); let depths = vec![1, 3, 5, 10]; for depth in depths { let result = tools.overview(temp_dir.path(), Some(depth))?; assert!( !result.is_empty(), "Overview with depth {} should produce output", depth ); } Ok(()) } #[test] fn test_stats() -> Result<()> { // Statistics - numbers that tell stories! let temp_dir = create_complex_test_directory()?; let tools = StOnlyTools::new(); let result = tools.stats(temp_dir.path())?; assert!( result.contains("Files") || result.contains("files") || result.contains("F:"), "Stats should show file count" ); assert!( result.contains("Directories") || result.contains("directories") || result.contains("D:"), "Stats should show directory count" ); Ok(()) } #[test] fn test_semantic() -> Result<()> { // Semantic analysis - understanding beyond syntax! let temp_dir = create_complex_test_directory()?; let tools = StOnlyTools::new(); let result = tools.semantic(temp_dir.path())?; assert!( !result.is_empty(), "Semantic analysis should produce output" ); Ok(()) } #[test] fn test_run_st_error_handling() -> Result<()> { // Error handling - where resilience meets reality! let config = StToolsConfig { st_binary: PathBuf::from("/definitely/not/a/real/binary"), ..Default::default() }; let tools = StOnlyTools::with_config(config); let options = ListOptions::default(); let result = tools.list(Path::new("."), options); assert!(result.is_err(), "Should error with invalid binary path"); Ok(()) } #[test] fn test_empty_directory_handling() -> Result<()> { // Empty directories - the void stares back! let temp_dir = TempDir::new()?; let tools = StOnlyTools::new(); let result = tools.list(temp_dir.path(), ListOptions::default())?; assert!( result.is_empty() || result.trim().is_empty(), "Empty directory should produce minimal output" ); let result = tools.stats(temp_dir.path())?; assert!( result.contains("0") || result.contains("empty"), "Stats should show zero files" ); Ok(()) } #[test] fn test_nested_directory_operations() -> Result<()> { // Deep nesting - testing the depths of recursion! let temp_dir = TempDir::new()?; let mut path = temp_dir.path().to_path_buf(); // Create deeply nested structure for i in 0..10 { path = path.join(format!("level{}", i)); fs::create_dir(&path)?; fs::write( path.join(format!("file{}.txt", i)), format!("Content at level {}", i), )?; } let tools = StOnlyTools::new(); let result = tools.overview(temp_dir.path(), Some(15))?; assert!( result.contains("level9") || result.contains("9") || result.contains("file"), "Should handle deeply nested directories" ); Ok(()) } #[test] fn test_all_list_options_combined() -> Result<()> { // The ultimate combination test - when all options collide! let temp_dir = create_complex_test_directory()?; let tools = StOnlyTools::new(); let options = ListOptions { pattern: Some("*.rs".to_string()), file_type: Some("rs".to_string()), sort: Some("size".to_string()), limit: Some(5), }; let result = tools.list(temp_dir.path(), options)?; assert!( !result.is_empty(), "Combined options should still produce output" ); Ok(()) } #[test] fn test_special_characters_in_filenames() -> Result<()> { // Special characters - the parser's nightmare! let temp_dir = TempDir::new()?; // Create files with special characters let special_names = vec![ "file with spaces.txt", "file-with-dashes.txt", "file_with_underscores.txt", "file.multiple.dots.txt", "file(with)parens.txt", "file[with]brackets.txt", "file{with}braces.txt", "file'with'quotes.txt", ]; for name in &special_names { fs::write(temp_dir.path().join(name), "content")?; } let tools = StOnlyTools::new(); let result = tools.list(temp_dir.path(), ListOptions::default())?; // Should handle all special characters gracefully assert!( !result.is_empty(), "Should list files with special characters" ); Ok(()) } #[test] fn test_large_file_handling() -> Result<()> { // Large files - testing memory limits! let temp_dir = TempDir::new()?; let large_file = temp_dir.path().join("large.txt"); // Create a 1MB file let content = "x".repeat(1024 * 1024); fs::write(&large_file, content)?; let tools = StOnlyTools::new(); let result = tools.stats(temp_dir.path())?; assert!( result.contains("1") || result.contains("MB") || result.contains("MiB"), "Stats should show large file size" ); Ok(()) } #[test] fn test_concurrent_operations() -> Result<()> { // Concurrency - where race conditions come to play! use std::sync::Arc; use std::thread; let temp_dir = Arc::new(create_complex_test_directory()?); let mut handles = vec![]; // Spawn multiple threads doing different operations for i in 0..5 { let temp_dir = Arc::clone(&temp_dir); let handle = thread::spawn(move || { let tools = StOnlyTools::new(); match i % 3 { 0 => tools.list(temp_dir.path(), ListOptions::default()), 1 => tools.stats(temp_dir.path()), _ => tools.overview(temp_dir.path(), None), } }); handles.push(handle); } // All operations should complete without panic for handle in handles { let result = handle.join().unwrap(); assert!(result.is_ok(), "Concurrent operation should succeed"); } Ok(()) } #[test] fn test_invalid_path_handling() -> Result<()> { // Invalid paths - the universal constant of file systems! let tools = StOnlyTools::new(); let invalid_paths = vec![ Path::new("/definitely/not/a/real/path"), Path::new(""), Path::new("\0"), // Null byte ]; for path in invalid_paths { // Should handle gracefully without panic let _ = tools.list(path, ListOptions::default()); let _ = tools.stats(path); let _ = tools.overview(path, None); } Ok(()) }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/8b-is/smart-tree'

If you have feedback or need assistance with the MCP directory API, please join our Discord server