refactor(volumes): Rename Volume struct and update migration for consistency

- Renamed Volume struct to Volumes for clarity and consistency across the codebase.
- Updated database migration to reflect the new naming convention in table and column definitions.
- Adjusted related index creation to use the new Volumes struct.
- Enhanced test cases for volume tracking to ensure compatibility with the updated struct.

This change improves code readability and aligns with the overall naming strategy in the project.
This commit is contained in:
Jamie Pine
2025-07-24 16:21:25 -07:00
parent f087320ceb
commit 84e474aa2c
7 changed files with 355 additions and 1218 deletions

View File

@@ -12,44 +12,44 @@ impl MigrationTrait for Migration {
manager
.create_table(
Table::create()
.table(Volume::Table)
.table(Volumes::Table)
.if_not_exists()
.col(
ColumnDef::new(Volume::Id)
ColumnDef::new(Volumes::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(Volume::Uuid).text().not_null().unique_key())
.col(ColumnDef::new(Volume::Fingerprint).text().not_null())
.col(ColumnDef::new(Volume::DisplayName).text())
.col(ColumnDef::new(Volumes::Uuid).text().not_null().unique_key())
.col(ColumnDef::new(Volumes::Fingerprint).text().not_null())
.col(ColumnDef::new(Volumes::DisplayName).text())
.col(
ColumnDef::new(Volume::TrackedAt)
ColumnDef::new(Volumes::TrackedAt)
.timestamp()
.not_null(),
)
.col(
ColumnDef::new(Volume::LastSeenAt)
ColumnDef::new(Volumes::LastSeenAt)
.timestamp()
.not_null(),
)
.col(
ColumnDef::new(Volume::IsOnline)
ColumnDef::new(Volumes::IsOnline)
.boolean()
.not_null()
.default(true),
)
.col(ColumnDef::new(Volume::TotalCapacity).big_integer())
.col(ColumnDef::new(Volume::AvailableCapacity).big_integer())
.col(ColumnDef::new(Volume::ReadSpeedMbps).integer())
.col(ColumnDef::new(Volume::WriteSpeedMbps).integer())
.col(ColumnDef::new(Volume::LastSpeedTestAt).timestamp())
.col(ColumnDef::new(Volume::FileSystem).text())
.col(ColumnDef::new(Volume::MountPoint).text())
.col(ColumnDef::new(Volume::IsRemovable).boolean())
.col(ColumnDef::new(Volume::IsNetworkDrive).boolean())
.col(ColumnDef::new(Volume::DeviceModel).text())
.col(ColumnDef::new(Volumes::TotalCapacity).big_integer())
.col(ColumnDef::new(Volumes::AvailableCapacity).big_integer())
.col(ColumnDef::new(Volumes::ReadSpeedMbps).integer())
.col(ColumnDef::new(Volumes::WriteSpeedMbps).integer())
.col(ColumnDef::new(Volumes::LastSpeedTestAt).timestamp())
.col(ColumnDef::new(Volumes::FileSystem).text())
.col(ColumnDef::new(Volumes::MountPoint).text())
.col(ColumnDef::new(Volumes::IsRemovable).boolean())
.col(ColumnDef::new(Volumes::IsNetworkDrive).boolean())
.col(ColumnDef::new(Volumes::DeviceModel).text())
.to_owned(),
)
.await?;
@@ -59,8 +59,8 @@ impl MigrationTrait for Migration {
.create_index(
Index::create()
.name("idx_volume_fingerprint_unique")
.table(Volume::Table)
.col(Volume::Fingerprint)
.table(Volumes::Table)
.col(Volumes::Fingerprint)
.unique()
.to_owned(),
)
@@ -71,8 +71,8 @@ impl MigrationTrait for Migration {
.create_index(
Index::create()
.name("idx_volume_last_seen_at")
.table(Volume::Table)
.col(Volume::LastSeenAt)
.table(Volumes::Table)
.col(Volumes::LastSeenAt)
.to_owned(),
)
.await?;
@@ -82,8 +82,8 @@ impl MigrationTrait for Migration {
.create_index(
Index::create()
.name("idx_volume_is_online")
.table(Volume::Table)
.col(Volume::IsOnline)
.table(Volumes::Table)
.col(Volumes::IsOnline)
.to_owned(),
)
.await?;
@@ -93,13 +93,13 @@ impl MigrationTrait for Migration {
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(Volume::Table).to_owned())
.drop_table(Table::drop().table(Volumes::Table).to_owned())
.await
}
}
#[derive(DeriveIden)]
enum Volume {
enum Volumes {
Table,
Id,
Uuid,

View File

@@ -21,8 +21,8 @@ use std::{
};
use tempfile::TempDir;
use tokio::{fs, time::timeout};
use uuid::Uuid;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
use uuid::Uuid;
/// Create a large test file with specified size
async fn create_large_test_file(
@@ -66,8 +66,10 @@ async fn test_copy_progress_monitoring_large_file() {
// Initialize tracing subscriber for debug logs
let _guard = tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer())
.with(tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")))
.with(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
)
.set_default();
// Setup test environment
@@ -149,9 +151,14 @@ async fn test_copy_progress_monitoring_large_file() {
.expect("Action dispatch should succeed");
// Extract job ID from output
let job_id_value = action_output.data.get("job_id").unwrap();
let job_id_str = job_id_value.as_str().expect("job_id should be a string");
let job_id = Uuid::parse_str(job_id_str).expect("job_id should be valid UUID");
let job_id = match &action_output {
sd_core_new::infrastructure::actions::output::ActionOutput::Custom { data, .. } => {
let job_id_value = data.get("job_id").unwrap();
let job_id_str = job_id_value.as_str().expect("job_id should be a string");
Uuid::parse_str(job_id_str).expect("job_id should be valid UUID")
}
_ => panic!("Expected Custom ActionOutput variant"),
};
println!("Monitoring job ID: {}", job_id);
// Start monitoring task
@@ -165,22 +172,26 @@ async fn test_copy_progress_monitoring_large_file() {
loop {
poll_count += 1;
// Get job info from the job manager
let job_info_result = library_clone.jobs().get_job_info(job_id).await.unwrap();
if let Some(job_info) = job_info_result {
let current_progress = job_info.progress * 100.0;
// Only show debug output every 100 polls to reduce noise
if poll_count % 100 == 0 && poll_count > 0 {
println!("Poll #{}: Status={:?}, Progress={:.1}%",
poll_count, job_info.status, current_progress);
println!(
"Poll #{}: Status={:?}, Progress={:.1}%",
poll_count, job_info.status, current_progress
);
}
// Debug log when we're near completion
if current_progress > 99.0 {
println!("Near completion - Poll #{}: Status={:?}, Progress={:.1}%",
poll_count, job_info.status, current_progress);
println!(
"Near completion - Poll #{}: Status={:?}, Progress={:.1}%",
poll_count, job_info.status, current_progress
);
}
// Record snapshot if progress changed
@@ -191,8 +202,8 @@ async fn test_copy_progress_monitoring_large_file() {
let snapshot = ProgressSnapshot {
timestamp: std::time::Instant::now(),
percentage: current_progress,
bytes_copied: (expected_size_clone as f64 * (current_progress as f64 / 100.0))
as u64,
bytes_copied: (expected_size_clone as f64
* (current_progress as f64 / 100.0)) as u64,
message: format!("{:.1}%", current_progress),
};
@@ -238,16 +249,22 @@ async fn test_copy_progress_monitoring_large_file() {
current_progress
);
}
// Fail fast if progress is stuck at 0% for too long
if consecutive_same_progress > 200 && current_progress == 0.0 {
println!("ERROR: Progress stuck at 0% for {} polls. Aborting test.", consecutive_same_progress);
println!(
"ERROR: Progress stuck at 0% for {} polls. Aborting test.",
consecutive_same_progress
);
break;
}
}
}
} else {
println!("Job info returned None for job {} (poll #{})", job_id, poll_count);
println!(
"Job info returned None for job {} (poll #{})",
job_id, poll_count
);
// Job might have been removed from running jobs after completion
// Let's assume it completed successfully
break;
@@ -256,7 +273,7 @@ async fn test_copy_progress_monitoring_large_file() {
// Poll every 50ms to catch fine-grained progress updates
tokio::time::sleep(Duration::from_millis(50)).await;
}
has_seen_progress
});
@@ -366,8 +383,10 @@ async fn test_copy_progress_multiple_files() {
// Initialize tracing subscriber for debug logs
let _guard = tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer())
.with(tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")))
.with(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
)
.try_init();
// This test verifies progress tracking across multiple files
@@ -438,8 +457,13 @@ async fn test_copy_progress_multiple_files() {
.await
.expect("Action dispatch should succeed");
let job_id_str = action_output.data.get("job_id").unwrap().as_str().unwrap();
let job_id = Uuid::parse_str(job_id_str).unwrap();
let job_id = match &action_output {
sd_core_new::infrastructure::actions::output::ActionOutput::Custom { data, .. } => {
let job_id_str = data.get("job_id").unwrap().as_str().unwrap();
Uuid::parse_str(job_id_str).unwrap()
}
_ => panic!("Expected Custom ActionOutput variant"),
};
// Monitor progress
let library_clone = library.clone();
@@ -470,7 +494,10 @@ async fn test_copy_progress_multiple_files() {
panic!("Multi-file job failed!");
}
} else {
println!("Job info returned None for multi-file job {}. Job likely completed.", job_id);
println!(
"Job info returned None for multi-file job {}. Job likely completed.",
job_id
);
break;
}

View File

@@ -1,197 +0,0 @@
//! Test automatic volume tracking functionality
use sd_core_new::{
context::CoreContext,
infrastructure::events::EventBus,
library::{LibraryConfig, LibraryManager, LibrarySettings},
volume::{VolumeDetectionConfig, VolumeManager},
};
use std::sync::Arc;
use tempfile::TempDir;
use uuid::Uuid;
#[tokio::test]
async fn test_auto_track_system_volumes_on_library_open() {
// Setup test environment
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let data_dir = temp_dir.path().join("data");
std::fs::create_dir_all(&data_dir).expect("Failed to create data dir");
// Initialize volume manager
let events = Arc::new(EventBus::default());
let volume_config = VolumeDetectionConfig::default();
let volume_manager = Arc::new(VolumeManager::new(volume_config, events.clone()));
// Initialize volume manager to detect volumes
volume_manager
.initialize()
.await
.expect("Failed to initialize volume manager");
// Get system volumes before library creation
let system_volumes = volume_manager.get_system_volumes().await;
println!("Found {} system volumes", system_volumes.len());
// Create library manager
let library_manager = Arc::new(LibraryManager::new_with_dir(
temp_dir.path().join("libraries"),
events.clone(),
));
// Create core context
let context = CoreContext::test_with_volume_manager(
data_dir,
volume_manager.clone(),
)
.await
.expect("Failed to create test context");
let context = Arc::new(context);
// Create a library with auto-tracking enabled (default)
let library = library_manager
.create_library("Test Library", None, context.clone())
.await
.expect("Failed to create library");
// Verify system volumes were auto-tracked
let tracked_volumes = volume_manager
.get_tracked_volumes(&library)
.await
.expect("Failed to get tracked volumes");
// Should have tracked all system volumes
assert_eq!(
tracked_volumes.len(),
system_volumes.len(),
"Should have auto-tracked all system volumes"
);
// Verify each system volume is tracked
for sys_vol in &system_volumes {
let is_tracked = tracked_volumes
.iter()
.any(|tv| tv.fingerprint.0 == sys_vol.fingerprint.0);
assert!(
is_tracked,
"System volume '{}' should be tracked",
sys_vol.name
);
}
}
#[tokio::test]
async fn test_auto_track_disabled() {
// Setup test environment
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let data_dir = temp_dir.path().join("data");
std::fs::create_dir_all(&data_dir).expect("Failed to create data dir");
// Initialize volume manager
let events = Arc::new(EventBus::default());
let volume_config = VolumeDetectionConfig::default();
let volume_manager = Arc::new(VolumeManager::new(volume_config, events.clone()));
// Initialize volume manager
volume_manager
.initialize()
.await
.expect("Failed to initialize volume manager");
// Create library manager
let library_manager = Arc::new(LibraryManager::new_with_dir(
temp_dir.path().join("libraries"),
events.clone(),
));
// Create library path manually
let library_path = temp_dir.path().join("libraries").join("test.sdlibrary");
std::fs::create_dir_all(&library_path).expect("Failed to create library dir");
// Create config with auto-tracking disabled
let mut settings = LibrarySettings::default();
settings.auto_track_system_volumes = false;
let config = LibraryConfig {
version: 1,
id: Uuid::new_v4(),
name: "Test Library".to_string(),
description: None,
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
settings,
statistics: sd_core_new::library::LibraryStatistics::default(),
};
// Save config
let config_json = serde_json::to_string_pretty(&config).expect("Failed to serialize config");
std::fs::write(library_path.join("library.json"), config_json).expect("Failed to write config");
// Create database
let db_path = library_path.join("database.db");
let db = sd_core_new::infrastructure::database::Database::create(&db_path)
.await
.expect("Failed to create database");
db.migrate().await.expect("Failed to run migrations");
// Create context
let context = CoreContext::test_with_volume_manager(
data_dir,
volume_manager.clone(),
)
.await
.expect("Failed to create test context");
let context = Arc::new(context);
// Open library with auto-tracking disabled
let library = library_manager
.open_library_with_context(&library_path, context.clone())
.await
.expect("Failed to open library");
// Verify no volumes were auto-tracked
let tracked_volumes = volume_manager
.get_tracked_volumes(&library)
.await
.expect("Failed to get tracked volumes");
assert_eq!(
tracked_volumes.len(),
0,
"Should not have auto-tracked any volumes"
);
}
#[tokio::test]
async fn test_system_volume_properties() {
// Initialize volume manager
let events = Arc::new(EventBus::default());
let volume_config = VolumeDetectionConfig::default();
let volume_manager = Arc::new(VolumeManager::new(volume_config, events.clone()));
// Initialize to detect volumes
volume_manager
.initialize()
.await
.expect("Failed to initialize volume manager");
// Get system volumes
let system_volumes = volume_manager.get_system_volumes().await;
// Verify system volume properties
for volume in system_volumes {
// System volumes should be mounted
assert!(volume.is_mounted, "System volume should be mounted");
// System volumes should have capacity info
assert!(volume.total_bytes_capacity > 0, "System volume should have capacity");
// System volumes typically have specific names
println!(
"System volume: {} ({}), FS: {}, Capacity: {} GB",
volume.name,
volume.mount_point.display(),
volume.file_system,
volume.total_bytes_capacity / (1024 * 1024 * 1024)
);
}
}

View File

@@ -1,234 +0,0 @@
//! Integration test for volume management with action system
//!
//! This test validates volume operations including:
//! - Volume detection and properties
//! - Volume tracking/untracking via actions
//! - Speed testing via actions
//! - Action execution and output validation
use sd_core_new::{
infrastructure::actions::{manager::ActionManager, Action},
operations::volumes::{
speed_test::action::VolumeSpeedTestAction, track::action::VolumeTrackAction,
untrack::action::VolumeUntrackAction,
},
volume::VolumeExt,
Core,
};
use std::sync::Arc;
use tempfile::tempdir;
use tracing::{info, warn};
const TEST_VOLUME_NAME: &str = "TestVolume";
#[tokio::test]
async fn test_volume_actions_integration() {
// Initialize logging
let _ = tracing_subscriber::fmt::try_init();
// Create test data directory
let data_dir = tempdir().unwrap();
let data_path = data_dir.path().to_path_buf();
// Initialize core
let core = Arc::new(
Core::new_with_config(data_path.clone())
.await
.expect("Failed to create core"),
);
// Create a test library
let library = core
.libraries
.create_library(
"Test Library",
Some(data_path.join("libraries").join("test-library")),
core.context.clone(),
)
.await
.expect("Failed to create library");
let library_id = library.id();
info!("Created test library: {}", library_id);
// Get volume manager
let volume_manager = core.volumes.clone();
// Refresh volumes to ensure we have the latest
volume_manager
.refresh_volumes()
.await
.expect("Failed to refresh volumes");
// Get all volumes
let all_volumes = volume_manager.get_all_volumes().await;
info!("Detected {} volumes:", all_volumes.len());
for volume in &all_volumes {
info!(
" - {} ({}) at {} - {} {} [{}]",
volume.name,
volume.fingerprint,
volume.mount_point.display(),
volume.file_system,
volume.disk_type,
if volume.is_mounted {
"mounted"
} else {
"unmounted"
}
);
}
// Find TestVolume if it exists
let test_volume = all_volumes
.iter()
.find(|v| v.name == TEST_VOLUME_NAME)
.cloned();
if let Some(test_volume) = test_volume {
info!("Found TestVolume! Running action tests...");
// Test volume properties
assert!(test_volume.is_mounted, "TestVolume should be mounted");
assert!(
test_volume.is_available().await,
"TestVolume should be accessible"
);
let fingerprint = test_volume.fingerprint.clone();
// Create action manager with core context
let context = core.context.clone();
let action_manager = ActionManager::new(context);
// Test 1: Track volume action
info!("Testing volume tracking action...");
let track_action = Action::VolumeTrack {
action: VolumeTrackAction {
fingerprint: fingerprint.clone(),
library_id,
name: Some("Test Volume Tracked".to_string()),
},
};
let track_result = action_manager.dispatch(track_action).await;
match track_result {
Ok(output) => {
info!("Volume tracked successfully: {}", output);
match output {
sd_core_new::infrastructure::actions::output::ActionOutput::VolumeTracked {
volume_name,
..
} => {
assert_eq!(volume_name, test_volume.name);
}
_ => panic!("Unexpected output type for track action"),
}
}
Err(e) => {
warn!(
"Volume tracking failed (may not be fully implemented): {}",
e
);
}
}
// Test 2: Speed test action
info!("Testing volume speed test action...");
let speed_test_action = Action::VolumeSpeedTest {
action: VolumeSpeedTestAction {
fingerprint: fingerprint.clone(),
},
};
let speed_test_result = action_manager.dispatch(speed_test_action).await;
match speed_test_result {
Ok(output) => {
info!("Speed test completed: {}", output);
match output {
sd_core_new::infrastructure::actions::output::ActionOutput::VolumeSpeedTested {
read_speed_mbps,
write_speed_mbps,
..
} => {
if let (Some(read), Some(write)) = (read_speed_mbps, write_speed_mbps) {
info!("Speed test results: {} MB/s read, {} MB/s write", read, write);
assert!(read > 0, "Read speed should be positive");
assert!(write > 0, "Write speed should be positive");
}
}
_ => panic!("Unexpected output type for speed test action"),
}
}
Err(e) => {
warn!("Speed test failed (this is okay in CI): {}", e);
}
}
// Test 3: Untrack volume action
info!("Testing volume untracking action...");
let untrack_action = Action::VolumeUntrack {
action: VolumeUntrackAction {
fingerprint: fingerprint.clone(),
library_id,
},
};
let untrack_result = action_manager.dispatch(untrack_action).await;
match untrack_result {
Ok(output) => {
info!("Volume untracked successfully: {}", output);
match output {
sd_core_new::infrastructure::actions::output::ActionOutput::VolumeUntracked {
..
} => {
// Success
}
_ => panic!("Unexpected output type for untrack action"),
}
}
Err(e) => {
warn!(
"Volume untracking failed (may not be fully implemented): {}",
e
);
}
}
info!("All volume action tests completed!");
} else {
warn!(
"TestVolume not found. Available volumes: {:?}",
all_volumes.iter().map(|v| &v.name).collect::<Vec<_>>()
);
println!("SKIPPING TEST: TestVolume not mounted on system");
// Still test that we can detect volumes
assert!(!all_volumes.is_empty(), "Should detect at least one volume");
}
// Test volume statistics
let stats = volume_manager.get_statistics().await;
info!("Volume statistics:");
info!(" Total volumes: {}", stats.total_volumes);
info!(" Mounted volumes: {}", stats.mounted_volumes);
info!(
" Total capacity: {} TB",
stats.total_capacity / (1024 * 1024 * 1024 * 1024)
);
info!(
" Total available: {} TB",
stats.total_available / (1024 * 1024 * 1024 * 1024)
);
assert_eq!(stats.total_volumes, all_volumes.len());
assert!(stats.mounted_volumes <= stats.total_volumes);
assert!(stats.total_available <= stats.total_capacity);
// Clean up
let _ = core.services.stop_all().await;
info!("Volume integration test completed successfully");
}

View File

@@ -1,361 +0,0 @@
// //! Volume system integration tests
// use sd_core_new::{
// infrastructure::events::{EventBus, EventFilter},
// volume::{
// types::{DiskType, FileSystem, MountType, VolumeDetectionConfig},
// VolumeExt, VolumeManager,
// },
// };
// use std::sync::Arc;
// use std::time::Duration;
// use tempfile::TempDir;
// use tokio::time::timeout;
// #[tokio::test]
// async fn test_volume_manager_initialization() {
// let events = Arc::new(EventBus::default());
// let config = VolumeDetectionConfig::default();
// let manager = VolumeManager::new(config, events);
// // Should initialize without error
// let result = manager.initialize().await;
// assert!(result.is_ok());
// // Should have detected some volumes (unless running in very minimal environment)
// let volumes = manager.get_all_volumes().await;
// println!("Detected {} volumes", volumes.len());
// for volume in &volumes {
// println!(
// "Volume: {} - {} - {} ({:?})",
// volume.name,
// volume.mount_point.display(),
// volume.file_system,
// volume.disk_type
// );
// }
// }
// #[tokio::test]
// async fn test_volume_detection_config() {
// let events = Arc::new(EventBus::default());
// // Test with system volumes excluded
// let config = VolumeDetectionConfig {
// include_system: false,
// include_virtual: false,
// run_speed_test: false,
// refresh_interval_secs: 0, // No monitoring
// };
// let manager = VolumeManager::new(config, events.clone());
// manager.initialize().await.unwrap();
// let volumes = manager.get_all_volumes().await;
// // Verify no system volumes are included
// for volume in &volumes {
// assert_ne!(volume.mount_type, MountType::System);
// assert_ne!(volume.mount_type, MountType::Virtual);
// }
// }
// #[tokio::test]
// async fn test_volume_path_lookup() {
// let events = Arc::new(EventBus::default());
// let config = VolumeDetectionConfig::default();
// let manager = VolumeManager::new(config, events);
// manager.initialize().await.unwrap();
// // Test looking up volume for a common path
// let test_paths = [
// std::path::PathBuf::from("/"),
// std::path::PathBuf::from("/tmp"),
// std::path::PathBuf::from("/usr"),
// std::env::temp_dir(),
// std::env::current_dir().unwrap_or_default(),
// ];
// for path in &test_paths {
// if path.exists() {
// let volume = manager.volume_for_path(path).await;
// if let Some(vol) = volume {
// println!("Path {} is on volume: {}", path.display(), vol.name);
// // Verify the volume actually contains this path
// assert!(vol.contains_path(path));
// } else {
// println!("No volume found for path: {}", path.display());
// }
// }
// }
// }
// #[tokio::test]
// async fn test_volume_events() {
// let events = Arc::new(EventBus::default());
// let mut subscriber = events.subscribe();
// let config = VolumeDetectionConfig {
// include_system: true,
// include_virtual: false,
// run_speed_test: false,
// refresh_interval_secs: 0,
// };
// let manager = VolumeManager::new(config, events.clone());
// // Initialize and wait for events
// manager.initialize().await.unwrap();
// // Try to receive events with a timeout
// let event_result = timeout(Duration::from_millis(100), async {
// loop {
// match subscriber.recv().await {
// Ok(event) => {
// if event.is_volume_event() {
// return Some(event);
// }
// }
// Err(_) => return None,
// }
// }
// })
// .await;
// match event_result {
// Ok(Some(event)) => {
// println!("Received volume event: {:?}", event);
// }
// Ok(None) => {
// println!("No volume events received");
// }
// Err(_) => {
// println!("Timeout waiting for volume events");
// }
// }
// }
// #[tokio::test]
// async fn test_volume_statistics() {
// let events = Arc::new(EventBus::default());
// let config = VolumeDetectionConfig::default();
// let manager = VolumeManager::new(config, events);
// manager.initialize().await.unwrap();
// let stats = manager.get_statistics().await;
// println!("Volume Statistics:");
// println!(" Total volumes: {}", stats.total_volumes);
// println!(" Mounted volumes: {}", stats.mounted_volumes);
// println!(
// " Total capacity: {:.2} GB",
// stats.total_capacity as f64 / 1024.0 / 1024.0 / 1024.0
// );
// println!(
// " Total available: {:.2} GB",
// stats.total_available as f64 / 1024.0 / 1024.0 / 1024.0
// );
// println!(" By disk type:");
// for (disk_type, count) in &stats.by_type {
// println!(" {:?}: {}", disk_type, count);
// }
// println!(" By filesystem:");
// for (fs, count) in &stats.by_filesystem {
// println!(" {}: {}", fs, count);
// }
// assert!(stats.total_volumes > 0 || cfg!(target_os = "unknown"));
// }
// #[tokio::test]
// async fn test_same_volume_check() {
// let events = Arc::new(EventBus::default());
// let config = VolumeDetectionConfig::default();
// let manager = VolumeManager::new(config, events);
// manager.initialize().await.unwrap();
// let temp_dir = std::env::temp_dir();
// let current_dir = std::env::current_dir().unwrap_or_default();
// if temp_dir.exists() && current_dir.exists() {
// let same_volume = manager.same_volume(&temp_dir, &current_dir).await;
// println!("Temp dir and current dir on same volume: {}", same_volume);
// // Test with same path (should always be true if volume is found)
// let same_self = manager.same_volume(&temp_dir, &temp_dir).await;
// if manager.volume_for_path(&temp_dir).await.is_some() {
// assert!(same_self);
// }
// }
// }
// #[tokio::test]
// async fn test_volume_space_check() {
// let events = Arc::new(EventBus::default());
// let config = VolumeDetectionConfig::default();
// let manager = VolumeManager::new(config, events);
// manager.initialize().await.unwrap();
// let volumes = manager.get_all_volumes().await;
// for volume in &volumes {
// // Test VolumeExt trait methods
// let available = volume.is_available().await;
// let has_1gb = volume.has_space(1024 * 1024 * 1024); // 1GB
// let has_1tb = volume.has_space(1024u64.pow(4)); // 1TB
// println!(
// "Volume {}: available={}, has_1gb={}, has_1tb={}",
// volume.name, available, has_1gb, has_1tb
// );
// }
// // Test finding volumes with specific space requirements
// let volumes_with_1gb = manager.volumes_with_space(1024 * 1024 * 1024).await;
// println!(
// "Volumes with at least 1GB space: {}",
// volumes_with_1gb.len()
// );
// }
// #[tokio::test]
// async fn test_volume_capabilities() {
// let events = Arc::new(EventBus::default());
// let config = VolumeDetectionConfig::default();
// let manager = VolumeManager::new(config, events);
// manager.initialize().await.unwrap();
// let volumes = manager.get_all_volumes().await;
// for volume in &volumes {
// println!(
// "Volume {}: filesystem={}, supports_fast_copy={}, optimal_chunk_size={}KB",
// volume.name,
// volume.file_system,
// volume.supports_fast_copy(),
// volume.optimal_chunk_size() / 1024
// );
// // Test filesystem capabilities
// let supports_reflink = volume.file_system.supports_reflink();
// let supports_sendfile = volume.file_system.supports_sendfile();
// println!(
// " Reflink support: {}, Sendfile support: {}",
// supports_reflink, supports_sendfile
// );
// }
// }
// #[tokio::test]
// async fn test_volume_monitoring() {
// let events = Arc::new(EventBus::default());
// let config = VolumeDetectionConfig {
// include_system: true,
// include_virtual: false,
// run_speed_test: false,
// refresh_interval_secs: 1, // Very short interval for test
// };
// let manager = VolumeManager::new(config, events.clone());
// manager.initialize().await.unwrap();
// // Let monitoring run for a short time
// tokio::time::sleep(Duration::from_millis(1500)).await;
// // Stop monitoring
// manager.stop_monitoring().await;
// // Verify manager still works after stopping monitoring
// let volumes = manager.get_all_volumes().await;
// println!("After monitoring test: {} volumes", volumes.len());
// }
// #[cfg(not(target_os = "unknown"))]
// #[tokio::test]
// async fn test_volume_speed_test() {
// let events = Arc::new(EventBus::default());
// let config = VolumeDetectionConfig::default();
// let manager = VolumeManager::new(config, events);
// manager.initialize().await.unwrap();
// let volumes = manager.get_all_volumes().await;
// // Find a writable volume to test
// for volume in &volumes {
// if !volume.read_only && volume.is_mounted {
// println!("Running speed test on volume: {}", volume.name);
// let result = manager.run_speed_test(&volume.fingerprint).await;
// match result {
// Ok(()) => {
// // Get updated volume info
// if let Some(updated_volume) = manager.get_volume(&volume.fingerprint).await {
// if let (Some(read_speed), Some(write_speed)) = (
// updated_volume.read_speed_mbps,
// updated_volume.write_speed_mbps,
// ) {
// println!(
// "Speed test results: {}MB/s read, {}MB/s write",
// read_speed, write_speed
// );
// assert!(read_speed > 0);
// assert!(write_speed > 0);
// }
// }
// // Only test one volume to keep test time reasonable
// break;
// }
// Err(e) => {
// println!("Speed test failed for {}: {}", volume.name, e);
// // Continue to next volume
// }
// }
// }
// }
// }
// #[tokio::test]
// async fn test_volume_fingerprinting() {
// let events = Arc::new(EventBus::default());
// let config = VolumeDetectionConfig::default();
// let manager = VolumeManager::new(config, events);
// manager.initialize().await.unwrap();
// let volumes = manager.get_all_volumes().await;
// for volume in &volumes {
// // Verify fingerprint is not empty
// assert!(!volume.fingerprint.to_string().is_empty());
// // Verify fingerprint is consistent
// let fingerprint1 = volume.fingerprint.clone();
// let fingerprint2 = crate::volume::types::VolumeFingerprint::new(volume);
// assert_eq!(fingerprint1, fingerprint2);
// println!("Volume {} fingerprint: {}", volume.name, volume.fingerprint);
// }
// // Verify that different volumes have different fingerprints
// let mut fingerprints = std::collections::HashSet::new();
// for volume in &volumes {
// assert!(
// fingerprints.insert(volume.fingerprint.clone()),
// "Duplicate fingerprint found for volume: {}",
// volume.name
// );
// }
// }

View File

@@ -1,151 +0,0 @@
//! Simple integration test for volume tracking
use sd_core_new::{
infrastructure::database::entities,
library::LibraryConfig,
volume::{VolumeDetectionConfig, VolumeFingerprint, VolumeManager},
};
use std::sync::Arc;
use tempfile::TempDir;
use uuid::Uuid;
#[tokio::test]
async fn test_volume_tracking_basic() {
// Create temp directory for library
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let library_path = temp_dir.path().join("test.sdlibrary");
// Create library config
let config = LibraryConfig {
id: Uuid::new_v4(),
name: "Test Library".to_string(),
description: Some("Test library for volume tracking".to_string()),
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
settings: sd_core_new::library::LibrarySettings::default(),
statistics: sd_core_new::library::LibraryStatistics::default(),
};
// Save config
std::fs::create_dir_all(&library_path).expect("Failed to create library dir");
let config_path = library_path.join("library.json");
let config_json = serde_json::to_string_pretty(&config).expect("Failed to serialize config");
std::fs::write(config_path, config_json).expect("Failed to write config");
// Create events and volume manager
let events = Arc::new(sd_core_new::infrastructure::events::EventBus::default());
let volume_config = VolumeDetectionConfig::default();
let volume_manager = Arc::new(VolumeManager::new(volume_config, events.clone()));
// Initialize volume manager to detect volumes
volume_manager
.initialize()
.await
.expect("Failed to initialize volume manager");
// Stop monitoring to avoid background tasks
volume_manager.stop_monitoring().await;
// Get all volumes
let volumes = volume_manager.get_all_volumes().await;
println!("Detected {} volumes", volumes.len());
// Print volume information
for volume in &volumes {
println!(
"Volume: {} ({}), Mounted: {}, Capacity: {} GB",
volume.name,
volume.fingerprint,
volume.is_mounted,
volume.total_bytes_capacity / (1024 * 1024 * 1024)
);
}
// If we have at least one volume, test fingerprint parsing
if let Some(first_volume) = volumes.first() {
let fingerprint_str = first_volume.fingerprint.to_string();
let parsed = VolumeFingerprint::from_string(&fingerprint_str)
.expect("Failed to parse fingerprint");
assert_eq!(parsed.0, fingerprint_str);
}
}
#[tokio::test]
async fn test_volume_fingerprint_operations() {
// Test fingerprint creation from hex
let hex_string = "abcdef1234567890";
let fingerprint = VolumeFingerprint::from_hex(hex_string);
assert_eq!(fingerprint.0, hex_string);
assert_eq!(fingerprint.to_string(), hex_string);
// Test fingerprint equality
let fp1 = VolumeFingerprint::from_hex("test123");
let fp2 = VolumeFingerprint::from_hex("test123");
let fp3 = VolumeFingerprint::from_hex("test456");
assert_eq!(fp1, fp2);
assert_ne!(fp1, fp3);
}
#[tokio::test]
async fn test_volume_entity_conversion() {
use chrono::Utc;
// Create a volume entity model
let model = entities::volume::Model {
id: 1,
uuid: Uuid::new_v4(),
fingerprint: "test_fingerprint_123".to_string(),
display_name: Some("Test Volume".to_string()),
tracked_at: Utc::now(),
last_seen_at: Utc::now(),
is_online: true,
total_capacity: Some(1000000000),
available_capacity: Some(500000000),
read_speed_mbps: Some(100),
write_speed_mbps: Some(80),
last_speed_test_at: None,
file_system: Some("APFS".to_string()),
mount_point: Some("/Volumes/Test".to_string()),
is_removable: Some(false),
is_network_drive: Some(false),
device_model: Some("Samsung SSD".to_string()),
};
// Convert to tracked volume
let tracked = model.to_tracked_volume();
// Verify conversion
assert_eq!(tracked.id, model.id);
assert_eq!(tracked.uuid, model.uuid);
assert_eq!(tracked.fingerprint.0, model.fingerprint);
assert_eq!(tracked.display_name, model.display_name);
assert_eq!(tracked.is_online, model.is_online);
assert_eq!(tracked.total_capacity, Some(1000000000));
assert_eq!(tracked.available_capacity, Some(500000000));
assert_eq!(tracked.read_speed_mbps, Some(100));
assert_eq!(tracked.write_speed_mbps, Some(80));
assert_eq!(tracked.file_system, model.file_system);
assert_eq!(tracked.mount_point, model.mount_point);
assert_eq!(tracked.is_removable, model.is_removable);
assert_eq!(tracked.is_network_drive, model.is_network_drive);
assert_eq!(tracked.device_model, model.device_model);
}
#[test]
fn test_volume_error_types() {
use sd_core_new::volume::VolumeError;
// Test error creation and display
let db_error = VolumeError::Database("Connection failed".to_string());
assert_eq!(db_error.to_string(), "Database error: Connection failed");
let already_tracked = VolumeError::AlreadyTracked("vol123".to_string());
assert_eq!(already_tracked.to_string(), "Volume is already tracked: vol123");
let not_tracked = VolumeError::NotTracked("vol456".to_string());
assert_eq!(not_tracked.to_string(), "Volume is not tracked: vol456");
let not_found = VolumeError::NotFound("vol789".to_string());
assert_eq!(not_found.to_string(), "Volume not found: vol789");
}

View File

@@ -1,95 +1,119 @@
//! Integration tests for volume tracking functionality
use core_new::{
context::CoreContext,
use sd_core_new::{
Core,
infrastructure::{
actions::{Action, manager::ActionManager},
database::Database,
events::EventBus,
actions::{Action, output::ActionOutput},
},
library::{Library, LibraryConfig, LibraryManager},
operations::volumes::{
track::action::VolumeTrackAction,
untrack::action::VolumeUntrackAction,
},
volume::{VolumeDetectionConfig, VolumeFingerprint, VolumeManager},
};
use std::path::PathBuf;
use std::sync::Arc;
use tempfile::TempDir;
use uuid::Uuid;
/// Helper to create a test library
async fn create_test_library(
context: Arc<CoreContext>,
temp_dir: &TempDir,
) -> Arc<Library> {
let library_path = temp_dir.path().join("test_library.sdlibrary");
let config = LibraryConfig {
id: Uuid::new_v4(),
name: "Test Library".to_string(),
description: Some("Test library for volume tracking".to_string()),
..Default::default()
};
context
.library_manager
.create_library(library_path, config)
.await
.expect("Failed to create test library")
}
/// Helper to get first available volume for testing
async fn get_test_volume(volume_manager: &Arc<VolumeManager>) -> Option<(VolumeFingerprint, String)> {
let volumes = volume_manager.get_all_volumes().await;
volumes.first().map(|v| (v.fingerprint.clone(), v.name.clone()))
}
use tempfile::tempdir;
use tracing::info;
#[tokio::test]
async fn test_volume_tracking_lifecycle() {
// Initialize logging
let _ = tracing_subscriber::fmt::try_init();
// Setup test environment
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let data_dir = temp_dir.path().join("data");
std::fs::create_dir_all(&data_dir).expect("Failed to create data dir");
let data_dir = tempdir().unwrap();
let data_path = data_dir.path().to_path_buf();
// Initialize core context
let events = Arc::new(EventBus::default());
let volume_config = VolumeDetectionConfig::default();
let volume_manager = Arc::new(VolumeManager::new(volume_config, events.clone()));
// Initialize core - this handles all the setup automatically
let core = Arc::new(
Core::new_with_config(data_path.clone())
.await
.expect("Failed to create core"),
);
// Initialize volume manager
// Create a test library
let library = core
.libraries
.create_library(
"Test Library",
Some(data_path.join("libraries").join("test-library")),
core.context.clone(),
)
.await
.expect("Failed to create library");
let library_id = library.id();
info!("Created test library: {}", library_id);
// Get volume manager
let volume_manager = core.volumes.clone();
// Refresh volumes to ensure we have the latest
volume_manager
.initialize()
.refresh_volumes()
.await
.expect("Failed to initialize volume manager");
.expect("Failed to refresh volumes");
// Create test context with volume manager
let context = CoreContext::test_with_volume_manager(data_dir, volume_manager.clone())
// Get all volumes
let all_volumes = volume_manager.get_all_volumes().await;
info!("Detected {} volumes", all_volumes.len());
// Get first available volume for testing
let test_volume = all_volumes
.first()
.expect("No volumes available for testing")
.clone();
info!("Using volume '{}' for testing", test_volume.name);
let fingerprint = test_volume.fingerprint.clone();
// Get action manager from core context
let action_manager = core.context.get_action_manager().await
.expect("Action manager should be initialized");
// Test 1: Check if volume is already tracked (from auto-tracking)
info!("Checking initial tracking status...");
let initial_tracked = volume_manager
.is_volume_tracked(&library, &fingerprint)
.await
.expect("Failed to create test context");
let context = Arc::new(context);
.expect("Failed to check tracking status");
// Create test library
let library = create_test_library(context.clone(), &temp_dir).await;
// Get a test volume
let (fingerprint, volume_name) = get_test_volume(&volume_manager)
.await
.expect("No volumes available for testing");
// Test 1: Track volume
{
let track_action = VolumeTrackAction {
library_id: library.id(),
fingerprint: fingerprint.clone(),
name: Some("My Test Volume".to_string()),
if initial_tracked {
info!("Volume is already tracked (from auto-tracking), untracking first");
// Untrack it first so we can test tracking
let untrack_action = Action::VolumeUntrack {
action: VolumeUntrackAction {
fingerprint: fingerprint.clone(),
library_id,
},
};
let action = Action::VolumeTrack { action: track_action };
let result = context.action_manager.execute(action).await;
let result = action_manager.dispatch(untrack_action).await;
assert!(result.is_ok(), "Failed to untrack volume: {:?}", result);
}
// Test 1: Track volume
info!("Testing volume tracking...");
{
let track_action = Action::VolumeTrack {
action: VolumeTrackAction {
fingerprint: fingerprint.clone(),
library_id,
name: Some("My Test Volume".to_string()),
},
};
let result = action_manager.dispatch(track_action).await;
assert!(result.is_ok(), "Failed to track volume: {:?}", result);
if let Ok(ActionOutput::VolumeTracked { volume_name, .. }) = result {
info!("Volume tracked successfully as '{}'", volume_name);
}
// Verify volume is tracked
let is_tracked = volume_manager
.is_volume_tracked(&library, &fingerprint)
@@ -97,45 +121,58 @@ async fn test_volume_tracking_lifecycle() {
.expect("Failed to check tracking status");
assert!(is_tracked, "Volume should be tracked");
// Get tracked volumes
// Get tracked volumes
let tracked_volumes = volume_manager
.get_tracked_volumes(&library)
.await
.expect("Failed to get tracked volumes");
assert_eq!(tracked_volumes.len(), 1, "Should have one tracked volume");
assert_eq!(tracked_volumes[0].fingerprint, fingerprint);
// Find our specific volume (there might be others from auto-tracking)
let our_volume = tracked_volumes.iter()
.find(|v| v.fingerprint == fingerprint)
.expect("Our volume should be in tracked volumes");
assert_eq!(
tracked_volumes[0].display_name,
our_volume.display_name,
Some("My Test Volume".to_string())
);
}
// Test 2: Try to track same volume again (should fail)
info!("Testing duplicate tracking prevention...");
{
let track_action = VolumeTrackAction {
library_id: library.id(),
fingerprint: fingerprint.clone(),
name: Some("Another Name".to_string()),
let track_action = Action::VolumeTrack {
action: VolumeTrackAction {
fingerprint: fingerprint.clone(),
library_id,
name: Some("Another Name".to_string()),
},
};
let action = Action::VolumeTrack { action: track_action };
let result = context.action_manager.execute(action).await;
let result = action_manager.dispatch(track_action).await;
assert!(result.is_err(), "Should not be able to track volume twice");
info!("Duplicate tracking correctly prevented");
}
// Test 3: Untrack volume
info!("Testing volume untracking...");
{
let untrack_action = VolumeUntrackAction {
library_id: library.id(),
fingerprint: fingerprint.clone(),
let untrack_action = Action::VolumeUntrack {
action: VolumeUntrackAction {
fingerprint: fingerprint.clone(),
library_id,
},
};
let action = Action::VolumeUntrack { action: untrack_action };
let result = context.action_manager.execute(action).await;
let result = action_manager.dispatch(untrack_action).await;
assert!(result.is_ok(), "Failed to untrack volume: {:?}", result);
if let Ok(ActionOutput::VolumeUntracked { .. }) = result {
info!("Volume untracked successfully");
}
// Verify volume is no longer tracked
let is_tracked = volume_manager
.is_volume_tracked(&library, &fingerprint)
@@ -143,193 +180,209 @@ async fn test_volume_tracking_lifecycle() {
.expect("Failed to check tracking status");
assert!(!is_tracked, "Volume should not be tracked");
// Get tracked volumes (should be empty)
// Get tracked volumes and verify our volume is not there
let tracked_volumes = volume_manager
.get_tracked_volumes(&library)
.await
.expect("Failed to get tracked volumes");
assert_eq!(tracked_volumes.len(), 0, "Should have no tracked volumes");
let our_volume_still_tracked = tracked_volumes.iter()
.any(|v| v.fingerprint == fingerprint);
assert!(!our_volume_still_tracked, "Our volume should no longer be tracked");
}
// Test 4: Try to untrack non-tracked volume (should fail)
// Test 4: Try to untrack volume that's not tracked (should fail)
info!("Testing untrack of non-tracked volume...");
{
let untrack_action = VolumeUntrackAction {
library_id: library.id(),
fingerprint: fingerprint.clone(),
let untrack_action = Action::VolumeUntrack {
action: VolumeUntrackAction {
fingerprint: fingerprint.clone(),
library_id,
},
};
let action = Action::VolumeUntrack { action: untrack_action };
let result = context.action_manager.execute(action).await;
let result = action_manager.dispatch(untrack_action).await;
assert!(result.is_err(), "Should not be able to untrack non-tracked volume");
info!("Untrack of non-tracked volume correctly prevented");
}
info!("Volume tracking lifecycle test completed successfully");
}
#[tokio::test]
async fn test_volume_state_updates() {
// Setup test environment
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let data_dir = temp_dir.path().join("data");
std::fs::create_dir_all(&data_dir).expect("Failed to create data dir");
// Initialize core context
let events = Arc::new(EventBus::default());
let volume_config = VolumeDetectionConfig::default();
let volume_manager = Arc::new(VolumeManager::new(volume_config, events.clone()));
// Initialize volume manager
volume_manager
.initialize()
.await
.expect("Failed to initialize volume manager");
// Create test context
let context = CoreContext::test_with_volume_manager(data_dir, volume_manager.clone())
.await
.expect("Failed to create test context");
let context = Arc::new(context);
// Create test library
let library = create_test_library(context.clone(), &temp_dir).await;
// Get a test volume
let (fingerprint, _) = get_test_volume(&volume_manager)
.await
.expect("No volumes available for testing");
// Track volume
let track_action = VolumeTrackAction {
library_id: library.id(),
fingerprint: fingerprint.clone(),
name: None,
};
let action = Action::VolumeTrack { action: track_action };
context
.action_manager
.execute(action)
.await
.expect("Failed to track volume");
// Get initial state
let tracked_volumes = volume_manager
.get_tracked_volumes(&library)
.await
.expect("Failed to get tracked volumes");
let initial_state = tracked_volumes[0].clone();
// Update volume state (simulate volume change)
if let Some(current_volume) = volume_manager.get_volume(&fingerprint).await {
volume_manager
.update_tracked_volume_state(&library, &fingerprint, &current_volume)
.await
.expect("Failed to update volume state");
// Get updated state
let tracked_volumes = volume_manager
.get_tracked_volumes(&library)
.await
.expect("Failed to get tracked volumes");
let updated_state = tracked_volumes[0].clone();
// Verify state was updated
assert!(updated_state.last_seen_at > initial_state.last_seen_at);
}
}
async fn test_volume_tracking_multiple_libraries() {
// Initialize logging
let _ = tracing_subscriber::fmt::try_init();
#[tokio::test]
async fn test_multiple_libraries_tracking_same_volume() {
// Setup test environment
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let data_dir = temp_dir.path().join("data");
std::fs::create_dir_all(&data_dir).expect("Failed to create data dir");
let data_dir = tempdir().unwrap();
let data_path = data_dir.path().to_path_buf();
// Initialize core context
let events = Arc::new(EventBus::default());
let volume_config = VolumeDetectionConfig::default();
let volume_manager = Arc::new(VolumeManager::new(volume_config, events.clone()));
// Initialize volume manager
volume_manager
.initialize()
.await
.expect("Failed to initialize volume manager");
// Create test context
let context = CoreContext::test_with_volume_manager(data_dir, volume_manager.clone())
.await
.expect("Failed to create test context");
let context = Arc::new(context);
// Initialize core - this handles all the setup automatically
let core = Arc::new(
Core::new_with_config(data_path.clone())
.await
.expect("Failed to create core"),
);
// Create two test libraries
let library1 = create_test_library(context.clone(), &temp_dir).await;
let library2 = create_test_library(context.clone(), &temp_dir).await;
// Get a test volume
let (fingerprint, _) = get_test_volume(&volume_manager)
let library1 = core
.libraries
.create_library(
"Library 1",
Some(data_path.join("libraries").join("library1")),
core.context.clone(),
)
.await
.expect("No volumes available for testing");
.expect("Failed to create library 1");
let library2 = core
.libraries
.create_library(
"Library 2",
Some(data_path.join("libraries").join("library2")),
core.context.clone(),
)
.await
.expect("Failed to create library 2");
let library1_id = library1.id();
let library2_id = library2.id();
info!("Created libraries: {} and {}", library1_id, library2_id);
// Get volume manager and refresh
let volume_manager = core.volumes.clone();
volume_manager
.refresh_volumes()
.await
.expect("Failed to refresh volumes");
// Get first available volume
let test_volume = volume_manager
.get_all_volumes()
.await
.first()
.expect("No volumes available for testing")
.clone();
let fingerprint = test_volume.fingerprint.clone();
// Get action manager from core context
let action_manager = core.context.get_action_manager().await
.expect("Action manager should be initialized");
// Check if volume is already tracked in library 1 (from auto-tracking)
let is_tracked_lib1 = volume_manager
.is_volume_tracked(&library1, &fingerprint)
.await
.expect("Failed to check tracking status");
if is_tracked_lib1 {
info!("Volume already tracked in library 1, untracking first");
let untrack_action = Action::VolumeUntrack {
action: VolumeUntrackAction {
fingerprint: fingerprint.clone(),
library_id: library1_id,
},
};
action_manager.dispatch(untrack_action).await
.expect("Failed to untrack from library 1");
}
// Track volume in library 1
let track_action1 = VolumeTrackAction {
library_id: library1.id(),
fingerprint: fingerprint.clone(),
name: Some("Library 1 Volume".to_string()),
};
info!("Tracking volume in library 1...");
{
let track_action = Action::VolumeTrack {
action: VolumeTrackAction {
fingerprint: fingerprint.clone(),
library_id: library1_id,
name: Some("Library 1 Volume".to_string()),
},
};
let result = action_manager.dispatch(track_action).await;
assert!(result.is_ok(), "Failed to track volume in library 1");
}
let action1 = Action::VolumeTrack { action: track_action1 };
context
.action_manager
.execute(action1)
// Check if volume is already tracked in library 2 (from auto-tracking)
let is_tracked_lib2 = volume_manager
.is_volume_tracked(&library2, &fingerprint)
.await
.expect("Failed to track volume in library 1");
.expect("Failed to check tracking status");
if is_tracked_lib2 {
info!("Volume already tracked in library 2, untracking first");
let untrack_action = Action::VolumeUntrack {
action: VolumeUntrackAction {
fingerprint: fingerprint.clone(),
library_id: library2_id,
},
};
action_manager.dispatch(untrack_action).await
.expect("Failed to untrack from library 2");
}
// Track same volume in library 2 (should work)
let track_action2 = VolumeTrackAction {
library_id: library2.id(),
fingerprint: fingerprint.clone(),
name: Some("Library 2 Volume".to_string()),
};
let action2 = Action::VolumeTrack { action: track_action2 };
context
.action_manager
.execute(action2)
.await
.expect("Failed to track volume in library 2");
// Track same volume in library 2 (should succeed)
info!("Tracking same volume in library 2...");
{
let track_action = Action::VolumeTrack {
action: VolumeTrackAction {
fingerprint: fingerprint.clone(),
library_id: library2_id,
name: Some("Library 2 Volume".to_string()),
},
};
let result = action_manager.dispatch(track_action).await;
assert!(result.is_ok(), "Should be able to track volume in different library");
}
// Verify both libraries have the volume tracked
let lib1_volumes = volume_manager
.get_tracked_volumes(&library1)
.await
.expect("Failed to get library 1 volumes");
assert_eq!(lib1_volumes.len(), 1);
assert_eq!(lib1_volumes[0].display_name, Some("Library 1 Volume".to_string()));
let lib1_our_volume = lib1_volumes.iter()
.find(|v| v.fingerprint == fingerprint)
.expect("Our volume should be in library 1");
assert_eq!(lib1_our_volume.display_name, Some("Library 1 Volume".to_string()));
let lib2_volumes = volume_manager
.get_tracked_volumes(&library2)
.await
.expect("Failed to get library 2 volumes");
assert_eq!(lib2_volumes.len(), 1);
assert_eq!(lib2_volumes[0].display_name, Some("Library 2 Volume".to_string()));
let lib2_our_volume = lib2_volumes.iter()
.find(|v| v.fingerprint == fingerprint)
.expect("Our volume should be in library 2");
assert_eq!(lib2_our_volume.display_name, Some("Library 2 Volume".to_string()));
// Untrack from library 1
let untrack_action = VolumeUntrackAction {
library_id: library1.id(),
fingerprint: fingerprint.clone(),
};
let action = Action::VolumeUntrack { action: untrack_action };
context
.action_manager
.execute(action)
.await
.expect("Failed to untrack from library 1");
info!("Untracking volume from library 1...");
{
let untrack_action = Action::VolumeUntrack {
action: VolumeUntrackAction {
fingerprint: fingerprint.clone(),
library_id: library1_id,
},
};
let result = action_manager.dispatch(untrack_action).await;
assert!(result.is_ok(), "Failed to untrack from library 1");
}
// Verify library 2 still has it tracked
let lib2_volumes = volume_manager
.get_tracked_volumes(&library2)
.await
.expect("Failed to get library 2 volumes");
assert_eq!(lib2_volumes.len(), 1);
let lib2_still_has_volume = lib2_volumes.iter()
.any(|v| v.fingerprint == fingerprint);
assert!(lib2_still_has_volume, "Library 2 should still have volume tracked");
info!("Multiple library volume tracking test completed successfully");
}