use tokio; use chrono::Utc; use std::collections::HashMap; use readur::{models::FileIngestionInfo, services::webdav::{WebDAVService, WebDAVConfig}}; // Helper function to create test WebDAV service for smart scanning fn create_nextcloud_webdav_service() -> WebDAVService { let config = WebDAVConfig { server_url: "https://nextcloud.example.com".to_string(), username: "testuser".to_string(), password: "testpass".to_string(), watch_folders: vec!["/Documents".to_string()], file_extensions: vec!["pdf".to_string(), "txt".to_string()], timeout_seconds: 30, server_type: Some("nextcloud".to_string()), }; WebDAVService::new(config).unwrap() } fn create_generic_webdav_service() -> WebDAVService { let config = WebDAVConfig { server_url: "https://generic-webdav.example.com".to_string(), username: "testuser".to_string(), password: "testpass".to_string(), watch_folders: vec!["/Documents".to_string()], file_extensions: vec!["pdf".to_string(), "txt".to_string()], timeout_seconds: 30, server_type: Some("generic".to_string()), }; WebDAVService::new(config).unwrap() } // Mock directory structure with subdirectories for testing fn create_mock_directory_structure() -> Vec { vec![ // Root directory FileIngestionInfo { relative_path: "/Documents".to_string(), full_path: "/Documents".to_string(), #[allow(deprecated)] path: "/Documents".to_string(), name: "Documents".to_string(), size: 0, mime_type: "".to_string(), last_modified: Some(Utc::now()), etag: "root-etag-changed".to_string(), // Changed ETag is_directory: true, created_at: Some(Utc::now()), permissions: Some(755), owner: Some("admin".to_string()), group: Some("admin".to_string()), metadata: None, }, // Subdirectory 1 - Changed FileIngestionInfo { relative_path: "/Documents/Projects".to_string(), full_path: "/Documents/Projects".to_string(), #[allow(deprecated)] path: "/Documents/Projects".to_string(), name: "Projects".to_string(), size: 0, mime_type: "".to_string(), last_modified: Some(Utc::now()), etag: "projects-etag-new".to_string(), // Changed ETag is_directory: true, created_at: Some(Utc::now()), permissions: Some(755), owner: Some("admin".to_string()), group: Some("admin".to_string()), metadata: None, }, // File in changed subdirectory FileIngestionInfo { relative_path: "/Documents/Projects/report.pdf".to_string(), full_path: "/Documents/Projects/report.pdf".to_string(), #[allow(deprecated)] path: "/Documents/Projects/report.pdf".to_string(), name: "report.pdf".to_string(), size: 1024000, mime_type: "application/pdf".to_string(), last_modified: Some(Utc::now()), etag: "report-etag".to_string(), is_directory: false, created_at: Some(Utc::now()), permissions: Some(644), owner: Some("admin".to_string()), group: Some("admin".to_string()), metadata: None, }, // Subdirectory 2 - Unchanged FileIngestionInfo { relative_path: "/Documents/Archive".to_string(), full_path: "/Documents/Archive".to_string(), #[allow(deprecated)] path: "/Documents/Archive".to_string(), name: "Archive".to_string(), size: 0, mime_type: "".to_string(), last_modified: Some(Utc::now()), etag: "archive-etag-stable".to_string(), // Unchanged ETag is_directory: true, created_at: Some(Utc::now()), permissions: Some(755), owner: Some("admin".to_string()), group: Some("admin".to_string()), metadata: None, }, ] } #[tokio::test] async fn test_smart_scan_service_creation() { let nextcloud_service = create_nextcloud_webdav_service(); let generic_service = create_generic_webdav_service(); // Test that both services can be created successfully // In the real implementation, Nextcloud would use smart scanning, generic would use traditional assert!(true); // Services created successfully } #[tokio::test] async fn test_smart_scan_etag_change_detection_logic() { // Test the core logic for determining which directories need scanning // This simulates what happens inside smart_directory_scan let current_dirs = create_mock_directory_structure(); // Simulate known ETags from database let known_etags = HashMap::from([ ("/Documents".to_string(), "root-etag-old".to_string()), // Changed ("/Documents/Projects".to_string(), "projects-etag-old".to_string()), // Changed ("/Documents/Archive".to_string(), "archive-etag-stable".to_string()), // Unchanged ]); // Test the logic that determines which directories need scanning let mut directories_to_scan = Vec::new(); let mut directories_to_skip = Vec::new(); for current_dir in ¤t_dirs { if !current_dir.is_directory { continue; } if let Some(known_etag) = known_etags.get(¤t_dir.path) { if known_etag != ¤t_dir.etag { directories_to_scan.push(current_dir.path.clone()); } else { directories_to_skip.push(current_dir.path.clone()); } } else { // New directory directories_to_scan.push(current_dir.path.clone()); } } // Verify smart scanning logic correctly identifies changed directories assert_eq!(directories_to_scan.len(), 2); // Root and Projects changed assert_eq!(directories_to_skip.len(), 1); // Archive unchanged assert!(directories_to_scan.contains(&"/Documents".to_string())); assert!(directories_to_scan.contains(&"/Documents/Projects".to_string())); assert!(directories_to_skip.contains(&"/Documents/Archive".to_string())); } #[tokio::test] async fn test_smart_scan_handles_new_directories() { let current_dirs = create_mock_directory_structure(); // Simulate empty known ETags (first-time scan scenario) let known_etags: HashMap = HashMap::new(); // Test logic for handling new directories (should scan all) let mut new_directories = Vec::new(); for current_dir in ¤t_dirs { if !current_dir.is_directory { continue; } if !known_etags.contains_key(¤t_dir.path) { // New directory - needs scan new_directories.push(current_dir.path.clone()); } } // All directories should be considered new assert_eq!(new_directories.len(), 3); assert!(new_directories.contains(&"/Documents".to_string())); assert!(new_directories.contains(&"/Documents/Projects".to_string())); assert!(new_directories.contains(&"/Documents/Archive".to_string())); } #[tokio::test] async fn test_smart_scan_depth_1_traversal_efficiency() { // Test the efficiency of depth-1 traversal // This simulates the logic in smart_directory_scan function let parent_path = "/Documents"; let known_subdirs = HashMap::from([ ("/Documents/Projects".to_string(), "projects-etag-old".to_string()), ("/Documents/Archive".to_string(), "archive-etag-stable".to_string()), ]); // Simulate getting current directory ETags with depth-1 scan let current_subdirs = HashMap::from([ ("/Documents/Projects".to_string(), "projects-etag-new".to_string()), // Changed ("/Documents/Archive".to_string(), "archive-etag-stable".to_string()), // Unchanged ("/Documents/NewFolder".to_string(), "new-folder-etag".to_string()), // New ]); // Test the logic that determines which subdirectories need deep scanning let mut subdirs_needing_scan = Vec::new(); let mut subdirs_skipped = Vec::new(); for (current_path, current_etag) in ¤t_subdirs { if let Some(known_etag) = known_subdirs.get(current_path) { if current_etag != known_etag { subdirs_needing_scan.push(current_path.clone()); } else { subdirs_skipped.push(current_path.clone()); } } else { // New subdirectory subdirs_needing_scan.push(current_path.clone()); } } // Verify efficiency: only changed/new directories are scanned assert_eq!(subdirs_needing_scan.len(), 2); // Projects (changed) + NewFolder (new) assert_eq!(subdirs_skipped.len(), 1); // Archive (unchanged) assert!(subdirs_needing_scan.contains(&"/Documents/Projects".to_string())); assert!(subdirs_needing_scan.contains(&"/Documents/NewFolder".to_string())); assert!(subdirs_skipped.contains(&"/Documents/Archive".to_string())); } #[tokio::test] async fn test_smart_scan_recursive_etag_detection() { let service = create_nextcloud_webdav_service(); // Test that recursive ETag support detection can be called // In real implementation, this would check server capabilities let result = service.test_recursive_etag_support().await; // Should complete without panicking (actual result depends on server) assert!(result.is_ok() || result.is_err()); // Either way is fine for this test } #[tokio::test] async fn test_smart_scan_fallback_logic() { // Test that smart scan gracefully falls back to traditional scanning // when recursive ETag detection fails or isn't supported let supports_recursive_nextcloud = true; // Nextcloud typically supports this let supports_recursive_generic = false; // Generic WebDAV may not // Test the decision logic for choosing scan method let nextcloud_should_use_smart = supports_recursive_nextcloud; let generic_should_use_smart = supports_recursive_generic; assert!(nextcloud_should_use_smart); // Nextcloud uses smart scan assert!(!generic_should_use_smart); // Generic uses traditional scan // This tests the fallback logic that ensures scanning still works // even when smart optimizations aren't available } #[tokio::test] async fn test_smart_scan_performance_characteristics() { // Test performance characteristics of smart scanning vs traditional scanning // Simulate a large directory structure let total_directories = 100; let changed_directories = 10; // Only 10% changed // Simulate known ETags for all directories let mut known_etags = HashMap::new(); for i in 0..total_directories { let path = format!("/Documents/Folder{:03}", i); let etag = format!("etag-{:03}-old", i); known_etags.insert(path, etag); } // Simulate checking which directories need scanning let mut scan_count = 0; let mut skip_count = 0; for i in 0..total_directories { let path = format!("/Documents/Folder{:03}", i); let current_etag = if i < changed_directories { format!("etag-{:03}-new", i) // Changed } else { format!("etag-{:03}-old", i) // Unchanged }; if let Some(stored_etag) = known_etags.get(&path) { if stored_etag != ¤t_etag { scan_count += 1; } else { skip_count += 1; } } } // Verify smart scanning efficiency assert_eq!(scan_count, changed_directories); // Only changed directories scanned assert_eq!(skip_count, total_directories - changed_directories); // Others skipped // Smart scanning should scan 10% of directories vs 100% for traditional scanning let efficiency_ratio = (skip_count as f64) / (total_directories as f64); assert!(efficiency_ratio >= 0.9); // 90% efficiency improvement } #[tokio::test] async fn test_smart_scan_etag_update_logic() { // Test the logic for updating directory ETags after scanning let original_etag = "old-etag-123".to_string(); let new_etag = "new-etag-456".to_string(); // Simulate the comparison logic let etag_changed = original_etag != new_etag; assert!(etag_changed); // Simulate updating tracking after scan let updated_etag = new_etag.clone(); let scan_timestamp = Utc::now(); // Verify that subsequent scans would see this as unchanged let would_need_scan = updated_etag != new_etag; assert!(!would_need_scan); // Test timestamp is recent let time_since_scan = Utc::now() - scan_timestamp; assert!(time_since_scan.num_seconds() < 5); // Within 5 seconds } #[tokio::test] async fn test_smart_scan_server_type_optimization_routing() { // Test that the correct optimization is chosen based on server type let nextcloud_service = create_nextcloud_webdav_service(); let generic_service = create_generic_webdav_service(); // In real implementation, this would determine which scanning method to use: // - Nextcloud/ownCloud: smart_directory_scan with recursive ETag detection // - Generic WebDAV: traditional discover_files_in_folder_impl // Test service creation succeeds for both types assert!(true); // Both services created successfully // The actual routing logic would be tested in integration tests with mock servers }