//! Integration tests for volume detection and filesystem-aware copy strategy selection //! //! These tests verify that the volume detection system correctly identifies volumes, //! resolves paths to their storage locations, and selects optimal copy strategies. use sd_core::{ domain::addressing::SdPath, infra::event::EventBus, ops::files::copy::{input::CopyMethod, routing::CopyStrategyRouter}, volume::{ types::{VolumeDetectionConfig, VolumeType}, VolumeManager, }, }; use std::{path::PathBuf, sync::Arc}; use tokio::fs; use uuid::Uuid; /// Test volume detection on macOS #[cfg(target_os = "macos")] #[tokio::test] async fn test_macos_volume_detection() { // Initialize volume manager let device_id = Uuid::new_v4(); let config = VolumeDetectionConfig::default(); let events = Arc::new(EventBus::default()); let volume_manager = Arc::new(VolumeManager::new(device_id, config, events)); // Initialize and detect volumes volume_manager .initialize() .await .expect("Failed to initialize volume manager"); // Get all detected volumes let volumes = volume_manager.get_all_volumes().await; println!("Detected {} volumes:", volumes.len()); for volume in &volumes { println!( " - {} ({}) at {} [{}] - {}", volume.name, volume.file_system, volume.mount_point.display(), volume.volume_type.display_name(), if volume.apfs_container.is_some() { "APFS Container" } else { "Standalone" } ); // Print APFS container info if available if let Some(container) = &volume.apfs_container { println!( " Container: {} ({} volumes)", container.container_id, container.volumes.len() ); } // Print path mappings if available if !volume.path_mappings.is_empty() { println!(" Path mappings:"); for mapping in &volume.path_mappings { println!( " {} -> {}", mapping.virtual_path.display(), mapping.actual_path.display() ); } } } // Verify we have at least one volume assert!(!volumes.is_empty(), "No volumes detected"); // On macOS, we should have APFS volumes let apfs_volumes: Vec<_> = volumes .iter() .filter(|v| matches!(v.file_system, sd_core::volume::types::FileSystem::APFS)) .collect(); assert!( !apfs_volumes.is_empty(), "No APFS volumes detected on macOS" ); println!("Found {} APFS volumes", apfs_volumes.len()); // Check for Data volume with path mappings let data_volumes: Vec<_> = apfs_volumes .iter() .filter(|v| matches!(v.volume_type, VolumeType::UserData) && !v.path_mappings.is_empty()) .collect(); if !data_volumes.is_empty() { println!( "Found {} APFS Data volumes with path mappings", data_volumes.len() ); } } /// Test path resolution for common macOS paths #[cfg(target_os = "macos")] #[tokio::test] async fn test_macos_path_resolution() { // Initialize volume manager let device_id = Uuid::new_v4(); let config = VolumeDetectionConfig::default(); let events = Arc::new(EventBus::default()); let volume_manager = Arc::new(VolumeManager::new(device_id, config, events)); // Initialize and detect volumes volume_manager .initialize() .await .expect("Failed to initialize volume manager"); // Test common macOS paths let test_paths = vec![ "/Users", "/Applications", "/Library", "/tmp", "/var", "/System/Volumes/Data/Users", "/System/Volumes/Data/Applications", ]; println!("Testing path resolution:"); for path_str in test_paths { let path = PathBuf::from(path_str); if path.exists() { if let Some(volume) = volume_manager.volume_for_path(&path).await { println!( " {} -> Volume: {} ({})", path_str, volume.name, volume.file_system ); } else { println!(" {} -> No volume found", path_str); } } else { println!(" {} -> Path does not exist", path_str); } } } /// Test same physical storage detection for common paths #[cfg(target_os = "macos")] #[tokio::test] async fn test_same_physical_storage_detection() { // Initialize volume manager let device_id = Uuid::new_v4(); let config = VolumeDetectionConfig::default(); let events = Arc::new(EventBus::default()); let volume_manager = Arc::new(VolumeManager::new(device_id, config, events)); // Initialize and detect volumes volume_manager .initialize() .await .expect("Failed to initialize volume manager"); // Test same-storage detection for common macOS paths let test_cases = vec![ ( "/Users", "/Applications", true, "Both should be on Data volume", ), ("/Users", "/tmp", true, "Both should be on Data volume"), ( "/Users", "/System/Volumes/Data/Users", true, "Virtual vs actual path", ), ( "/Applications", "/System/Volumes/Data/Applications", true, "Virtual vs actual path", ), ("/Users", "/Volumes", false, "Different storage locations"), ]; println!("Testing same physical storage detection:"); for (path1_str, path2_str, expected, description) in test_cases { let path1 = PathBuf::from(path1_str); let path2 = PathBuf::from(path2_str); // Only test if both paths exist if path1.exists() && path2.exists() { let same_storage = volume_manager.same_physical_storage(&path1, &path2).await; let status = if same_storage == expected { "" } else { "" }; println!( " {} {} <-> {} = {} (expected: {}) - {}", status, path1_str, path2_str, same_storage, expected, description ); // For critical paths, assert the result if path1_str == "/Users" && path2_str == "/Applications" { assert_eq!( same_storage, expected, "Users and Applications should be detected as same storage on macOS APFS" ); } } else { println!( " {} <-> {} - Skipped (paths don't exist)", path1_str, path2_str ); } } } /// Test copy strategy selection for same-storage vs cross-storage operations #[cfg(target_os = "macos")] #[tokio::test] async fn test_copy_strategy_selection() { // Initialize volume manager let device_id = Uuid::new_v4(); let config = VolumeDetectionConfig::default(); let events = Arc::new(EventBus::default()); let volume_manager = Arc::new(VolumeManager::new(device_id, config, events)); // Initialize and detect volumes volume_manager .initialize() .await .expect("Failed to initialize volume manager"); // Create test paths (using existing directories) let test_cases = vec![ ( "/Users", "/Applications", "Same APFS container - should use FastCopyStrategy", ), ( "/Users", "/tmp", "Same APFS container - should use FastCopyStrategy", ), ]; println!("Testing copy strategy selection:"); for (source_str, dest_str, expected_behavior) in test_cases { let source_path = PathBuf::from(source_str); let dest_path = PathBuf::from(dest_str); // Only test if both paths exist if source_path.exists() && dest_path.exists() { // Create SdPath instances (using current device ID) let source_sdpath = SdPath::new("test-device".to_string(), source_path.clone()); let dest_sdpath = SdPath::new("test-device".to_string(), dest_path.clone()); // Test strategy selection let strategy = CopyStrategyRouter::select_strategy( &source_sdpath, &dest_sdpath, false, // is_move = false &CopyMethod::Auto, Some(&*volume_manager), ) .await; // Get strategy description let description = CopyStrategyRouter::describe_strategy( &source_sdpath, &dest_sdpath, false, &CopyMethod::Auto, Some(&*volume_manager), ) .await; println!( " {} -> {} = {} ({})", source_str, dest_str, description, expected_behavior ); // For same-storage operations, we should get a fast copy strategy if source_str == "/Users" && dest_str == "/Applications" { assert!( description.contains("Fast copy") || description.contains("APFS clone"), "Same-storage copy should use fast copy strategy, got: {}", description ); } } else { println!( " {} -> {} - Skipped (paths don't exist)", source_str, dest_str ); } } } /// Test APFS container detection specifically #[cfg(target_os = "macos")] #[tokio::test] async fn test_apfs_container_detection() { use sd_core::volume::fs::apfs; println!("Testing APFS container detection:"); // Detect APFS containers directly let containers = apfs::detect_containers() .await .expect("Failed to detect APFS containers"); println!("Detected {} APFS containers:", containers.len()); for container in &containers { println!( " Container: {} ({})", container.container_id, container.uuid ); println!(" Physical Store: {}", container.physical_store); println!( " Total Capacity: {} GB", container.total_capacity / (1024 * 1024 * 1024) ); println!(" Volumes: {}", container.volumes.len()); for volume in &container.volumes { println!( " - {} ({}) at {:?} [{}]", volume.name, volume.disk_id, volume.mount_point, volume.role ); } } // Verify we have at least one container assert!(!containers.is_empty(), "No APFS containers detected"); // Check for Data volume let has_data_volume = containers.iter().any(|container| { container .volumes .iter() .any(|volume| matches!(volume.role, sd_core::volume::types::ApfsVolumeRole::Data)) }); assert!(has_data_volume, "No APFS Data volume found"); println!("Found APFS Data volume"); } /// Test filesystem handler selection #[tokio::test] async fn test_filesystem_handler_selection() { use sd_core::volume::{fs, types::FileSystem}; println!("Testing filesystem handler selection:"); let filesystems = vec![ FileSystem::APFS, FileSystem::NTFS, FileSystem::ExFAT, FileSystem::FAT32, FileSystem::Other("Unknown".to_string()), ]; for fs_type in filesystems { let handler = fs::get_filesystem_handler(&fs_type); let strategy = handler.get_copy_strategy(); println!( " {} -> Strategy type: {}", fs_type, std::any::type_name_of_val(&*strategy) .split("::") .last() .unwrap_or("Unknown") ); } } /// Integration test that simulates the full copy workflow #[cfg(target_os = "macos")] #[tokio::test] async fn test_full_copy_workflow_simulation() { // Initialize volume manager let device_id = Uuid::new_v4(); let config = VolumeDetectionConfig::default(); let events = Arc::new(EventBus::default()); let volume_manager = Arc::new(VolumeManager::new(device_id, config, events)); // Initialize and detect volumes volume_manager .initialize() .await .expect("Failed to initialize volume manager"); // Simulate common copy scenarios let scenarios = vec![ ("/Users/Shared", "/Applications", "Desktop to Applications"), ("/tmp", "/Users/Shared", "Temp to Desktop"), ]; println!("Testing full copy workflow simulation:"); for (source_str, dest_str, scenario_name) in scenarios { let source_path = PathBuf::from(source_str); let dest_path = PathBuf::from(dest_str); // Only test if both paths exist if source_path.exists() && dest_path.exists() { println!("\nScenario: {}", scenario_name); // Step 1: Check if paths are on same physical storage let same_storage = volume_manager .same_physical_storage(&source_path, &dest_path) .await; println!(" Same physical storage: {}", same_storage); // Step 2: Get volumes for both paths let source_volume = volume_manager.volume_for_path(&source_path).await; let dest_volume = volume_manager.volume_for_path(&dest_path).await; match (&source_volume, &dest_volume) { (Some(src_vol), Some(dst_vol)) => { println!( " Source volume: {} ({})", src_vol.name, src_vol.file_system ); println!(" Dest volume: {} ({})", dst_vol.name, dst_vol.file_system); // Step 3: Select copy strategy let source_sdpath = SdPath::new("test-device".to_string(), source_path.clone()); let dest_sdpath = SdPath::new("test-device".to_string(), dest_path.clone()); let description = CopyStrategyRouter::describe_strategy( &source_sdpath, &dest_sdpath, false, &CopyMethod::Auto, Some(&*volume_manager), ) .await; println!(" Selected strategy: {}", description); // Step 4: Verify expected behavior if same_storage && matches!( src_vol.file_system, sd_core::volume::types::FileSystem::APFS ) { assert!( description.contains("Fast copy") || description.contains("APFS clone"), "Same-storage APFS copy should use fast strategy, got: {}", description ); println!(" Correctly selected fast copy for same-storage APFS operation"); } } _ => { println!(" Could not find volumes for one or both paths"); } } } else { println!( " Scenario '{}' skipped (paths don't exist)", scenario_name ); } } }