//! Comprehensive tests for StorageFactory and multi-tier storage //! //! Tests factory pattern for creating storage instances and multi-tier //! storage functionality (local primary + S3 secondary). use std::sync::Arc; use storage::local::{LocalStorage, LocalStorageConfig}; use storage::{Storage, StorageFactory, StorageProvider}; use tempfile::TempDir; #[tokio::test] async fn test_factory_create_local_storage() { let temp_dir = TempDir::new().expect("Failed to create temp dir"); let config = LocalStorageConfig { base_path: temp_dir.path().to_path_buf(), ..Default::default() }; let provider = StorageProvider::Local(config); let storage = StorageFactory::create(provider, None) .await .expect("Failed to create local storage"); // Test basic operations storage.store("test.txt", b"data").await.unwrap(); let data = storage.retrieve("test.txt").await.unwrap(); assert_eq!(data, b"data"); } #[tokio::test] async fn test_factory_create_multi_tier_storage() { let temp_dir1 = TempDir::new().expect("Failed to create temp dir 1"); let temp_dir2 = TempDir::new().expect("Failed to create temp dir 2"); let primary_config = LocalStorageConfig { base_path: temp_dir1.path().to_path_buf(), ..Default::default() }; let secondary_config = LocalStorageConfig { base_path: temp_dir2.path().to_path_buf(), ..Default::default() }; let provider = StorageProvider::MultiTier { primary: Box::new(StorageProvider::Local(primary_config)), secondary: Box::new(StorageProvider::Local(secondary_config)), }; let storage = StorageFactory::create(provider, None) .await .expect("Failed to create multi-tier storage"); // Store should go to both storage.store("multi.txt", b"multi_data").await.unwrap(); // Should retrieve from primary let data = storage.retrieve("multi.txt").await.unwrap(); assert_eq!(data, b"multi_data"); } #[tokio::test] async fn test_multi_tier_fallback_to_secondary() { let temp_dir1 = TempDir::new().unwrap(); let temp_dir2 = TempDir::new().unwrap(); let config1 = LocalStorageConfig { base_path: temp_dir1.path().to_path_buf(), ..Default::default() }; let config2 = LocalStorageConfig { base_path: temp_dir2.path().to_path_buf(), ..Default::default() }; let primary = LocalStorage::new(config1).await.unwrap(); let secondary = LocalStorage::new(config2).await.unwrap(); // Store only in secondary secondary .store("secondary_only.txt", b"secondary_data") .await .unwrap(); let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary)); // Should fallback to secondary let data = multi_tier .retrieve("secondary_only.txt") .await .expect("Failed to retrieve from secondary"); assert_eq!(data, b"secondary_data"); } #[tokio::test] async fn test_multi_tier_exists_primary() { let temp_dir1 = TempDir::new().unwrap(); let temp_dir2 = TempDir::new().unwrap(); let config1 = LocalStorageConfig { base_path: temp_dir1.path().to_path_buf(), ..Default::default() }; let config2 = LocalStorageConfig { base_path: temp_dir2.path().to_path_buf(), ..Default::default() }; let primary = LocalStorage::new(config1).await.unwrap(); let secondary = LocalStorage::new(config2).await.unwrap(); // Store in primary primary.store("primary.txt", b"data").await.unwrap(); let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary)); // Should find in primary let exists = multi_tier.exists("primary.txt").await.unwrap(); assert!(exists); } #[tokio::test] async fn test_multi_tier_exists_secondary() { let temp_dir1 = TempDir::new().unwrap(); let temp_dir2 = TempDir::new().unwrap(); let config1 = LocalStorageConfig { base_path: temp_dir1.path().to_path_buf(), ..Default::default() }; let config2 = LocalStorageConfig { base_path: temp_dir2.path().to_path_buf(), ..Default::default() }; let primary = LocalStorage::new(config1).await.unwrap(); let secondary = LocalStorage::new(config2).await.unwrap(); // Store only in secondary secondary.store("secondary.txt", b"data").await.unwrap(); let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary)); // Should find in secondary let exists = multi_tier.exists("secondary.txt").await.unwrap(); assert!(exists); } #[tokio::test] async fn test_multi_tier_exists_neither() { let temp_dir1 = TempDir::new().unwrap(); let temp_dir2 = TempDir::new().unwrap(); let config1 = LocalStorageConfig { base_path: temp_dir1.path().to_path_buf(), ..Default::default() }; let config2 = LocalStorageConfig { base_path: temp_dir2.path().to_path_buf(), ..Default::default() }; let primary = LocalStorage::new(config1).await.unwrap(); let secondary = LocalStorage::new(config2).await.unwrap(); let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary)); // Should not find nonexistent file let exists = multi_tier.exists("nonexistent.txt").await.unwrap(); assert!(!exists); } #[tokio::test] async fn test_multi_tier_delete_from_both() { let temp_dir1 = TempDir::new().unwrap(); let temp_dir2 = TempDir::new().unwrap(); let config1 = LocalStorageConfig { base_path: temp_dir1.path().to_path_buf(), ..Default::default() }; let config2 = LocalStorageConfig { base_path: temp_dir2.path().to_path_buf(), ..Default::default() }; let primary = LocalStorage::new(config1).await.unwrap(); let secondary = LocalStorage::new(config2).await.unwrap(); // Store in both primary.store("delete_me.txt", b"data").await.unwrap(); secondary.store("delete_me.txt", b"data").await.unwrap(); let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary)); // Delete should remove from both let deleted = multi_tier.delete("delete_me.txt").await.unwrap(); assert!(deleted); // Should not exist anymore let exists = multi_tier.exists("delete_me.txt").await.unwrap(); assert!(!exists); } #[tokio::test] async fn test_multi_tier_delete_only_primary() { let temp_dir1 = TempDir::new().unwrap(); let temp_dir2 = TempDir::new().unwrap(); let config1 = LocalStorageConfig { base_path: temp_dir1.path().to_path_buf(), ..Default::default() }; let config2 = LocalStorageConfig { base_path: temp_dir2.path().to_path_buf(), ..Default::default() }; let primary = LocalStorage::new(config1).await.unwrap(); let secondary = LocalStorage::new(config2).await.unwrap(); // Store only in primary primary.store("primary_only.txt", b"data").await.unwrap(); let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary)); let deleted = multi_tier.delete("primary_only.txt").await.unwrap(); assert!(deleted); } #[tokio::test] async fn test_multi_tier_list_combines_both() { let temp_dir1 = TempDir::new().unwrap(); let temp_dir2 = TempDir::new().unwrap(); let config1 = LocalStorageConfig { base_path: temp_dir1.path().to_path_buf(), ..Default::default() }; let config2 = LocalStorageConfig { base_path: temp_dir2.path().to_path_buf(), ..Default::default() }; let primary = LocalStorage::new(config1).await.unwrap(); let secondary = LocalStorage::new(config2).await.unwrap(); // Store different files in each primary.store("file1.txt", b"data1").await.unwrap(); primary.store("file2.txt", b"data2").await.unwrap(); secondary.store("file3.txt", b"data3").await.unwrap(); let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary)); // List should combine both let files = multi_tier.list("").await.unwrap(); assert_eq!(files.len(), 3); } #[tokio::test] async fn test_multi_tier_list_with_prefix() { let temp_dir1 = TempDir::new().unwrap(); let temp_dir2 = TempDir::new().unwrap(); let config1 = LocalStorageConfig { base_path: temp_dir1.path().to_path_buf(), ..Default::default() }; let config2 = LocalStorageConfig { base_path: temp_dir2.path().to_path_buf(), ..Default::default() }; let primary = LocalStorage::new(config1).await.unwrap(); let secondary = LocalStorage::new(config2).await.unwrap(); primary.store("models/v1.txt", b"data1").await.unwrap(); secondary.store("models/v2.txt", b"data2").await.unwrap(); secondary.store("data/test.txt", b"data3").await.unwrap(); let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary)); let files = multi_tier.list("models/").await.unwrap(); assert_eq!(files.len(), 2); } #[tokio::test] async fn test_multi_tier_metadata_from_primary() { let temp_dir1 = TempDir::new().unwrap(); let temp_dir2 = TempDir::new().unwrap(); let config1 = LocalStorageConfig { base_path: temp_dir1.path().to_path_buf(), ..Default::default() }; let config2 = LocalStorageConfig { base_path: temp_dir2.path().to_path_buf(), ..Default::default() }; let primary = LocalStorage::new(config1).await.unwrap(); let secondary = LocalStorage::new(config2).await.unwrap(); primary.store("primary.txt", b"primary_data").await.unwrap(); let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary)); let metadata = multi_tier.metadata("primary.txt").await.unwrap(); assert_eq!(metadata.size, 12); } #[tokio::test] async fn test_multi_tier_metadata_from_secondary() { let temp_dir1 = TempDir::new().unwrap(); let temp_dir2 = TempDir::new().unwrap(); let config1 = LocalStorageConfig { base_path: temp_dir1.path().to_path_buf(), ..Default::default() }; let config2 = LocalStorageConfig { base_path: temp_dir2.path().to_path_buf(), ..Default::default() }; let primary = LocalStorage::new(config1).await.unwrap(); let secondary = LocalStorage::new(config2).await.unwrap(); secondary .store("secondary.txt", b"secondary_data") .await .unwrap(); let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary)); let metadata = multi_tier.metadata("secondary.txt").await.unwrap(); assert_eq!(metadata.size, 14); } #[tokio::test] async fn test_multi_tier_store_writes_to_both() { let temp_dir1 = TempDir::new().unwrap(); let temp_dir2 = TempDir::new().unwrap(); let config1 = LocalStorageConfig { base_path: temp_dir1.path().to_path_buf(), ..Default::default() }; let config2 = LocalStorageConfig { base_path: temp_dir2.path().to_path_buf(), ..Default::default() }; let primary = LocalStorage::new(config1).await.unwrap(); let secondary = LocalStorage::new(config2).await.unwrap(); let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary)); // Store via multi-tier multi_tier.store("both.txt", b"data").await.unwrap(); // Should exist in both assert!(multi_tier.exists("both.txt").await.unwrap()); } #[tokio::test] async fn test_multi_tier_large_file() { let temp_dir1 = TempDir::new().unwrap(); let temp_dir2 = TempDir::new().unwrap(); let config1 = LocalStorageConfig { base_path: temp_dir1.path().to_path_buf(), ..Default::default() }; let config2 = LocalStorageConfig { base_path: temp_dir2.path().to_path_buf(), ..Default::default() }; let primary = LocalStorage::new(config1).await.unwrap(); let secondary = LocalStorage::new(config2).await.unwrap(); let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary)); // Store 1MB file let large_data = vec![0xAAu8; 1_048_576]; multi_tier .store("large.bin", &large_data) .await .expect("Failed to store large file"); // Retrieve and verify let retrieved = multi_tier.retrieve("large.bin").await.unwrap(); assert_eq!(retrieved.len(), large_data.len()); } #[tokio::test] async fn test_multi_tier_concurrent_operations() { let temp_dir1 = TempDir::new().unwrap(); let temp_dir2 = TempDir::new().unwrap(); let config1 = LocalStorageConfig { base_path: temp_dir1.path().to_path_buf(), ..Default::default() }; let config2 = LocalStorageConfig { base_path: temp_dir2.path().to_path_buf(), ..Default::default() }; let primary = LocalStorage::new(config1).await.unwrap(); let secondary = LocalStorage::new(config2).await.unwrap(); let multi_tier = Arc::new(storage::MultiTierStorage::new( Box::new(primary), Box::new(secondary), )); let mut handles = vec![]; for i in 0..10 { let multi_tier = multi_tier.clone(); let handle = tokio::spawn(async move { let path = format!("concurrent_{}.txt", i); let data = format!("data_{}", i); multi_tier.store(&path, data.as_bytes()).await.unwrap(); let retrieved = multi_tier.retrieve(&path).await.unwrap(); assert_eq!(retrieved, data.as_bytes()); }); handles.push(handle); } for handle in handles { handle.await.unwrap(); } } #[tokio::test] async fn test_storage_provider_local_variant() { let temp_dir = TempDir::new().unwrap(); let config = LocalStorageConfig { base_path: temp_dir.path().to_path_buf(), ..Default::default() }; let _provider = StorageProvider::Local(config); // Successfully created local provider variant } #[tokio::test] async fn test_storage_provider_multi_tier_variant() { let temp_dir1 = TempDir::new().unwrap(); let temp_dir2 = TempDir::new().unwrap(); let config1 = LocalStorageConfig { base_path: temp_dir1.path().to_path_buf(), ..Default::default() }; let config2 = LocalStorageConfig { base_path: temp_dir2.path().to_path_buf(), ..Default::default() }; let _provider = StorageProvider::MultiTier { primary: Box::new(StorageProvider::Local(config1)), secondary: Box::new(StorageProvider::Local(config2)), }; // Successfully created multi-tier provider variant } #[tokio::test] async fn test_multi_tier_overwrite_in_both() { let temp_dir1 = TempDir::new().unwrap(); let temp_dir2 = TempDir::new().unwrap(); let config1 = LocalStorageConfig { base_path: temp_dir1.path().to_path_buf(), ..Default::default() }; let config2 = LocalStorageConfig { base_path: temp_dir2.path().to_path_buf(), ..Default::default() }; let primary = LocalStorage::new(config1).await.unwrap(); let secondary = LocalStorage::new(config2).await.unwrap(); let multi_tier = storage::MultiTierStorage::new(Box::new(primary), Box::new(secondary)); // Store initial data multi_tier .store("overwrite.txt", b"initial") .await .unwrap(); // Overwrite multi_tier .store("overwrite.txt", b"updated") .await .unwrap(); // Retrieve should get updated data let data = multi_tier.retrieve("overwrite.txt").await.unwrap(); assert_eq!(data, b"updated"); }