pulseengine_mcp_auth/
storage.rs

1//! Storage backend for authentication data
2
3use crate::{
4    config::StorageConfig,
5    models::{ApiKey, SecureApiKey},
6};
7use async_trait::async_trait;
8use std::collections::HashMap;
9use std::path::PathBuf;
10use std::sync::Arc;
11use thiserror::Error;
12use tokio::fs;
13use tracing::{debug, error, info, warn};
14
15#[derive(Debug, Error)]
16pub enum StorageError {
17    #[error("Storage error: {0}")]
18    General(String),
19
20    #[error("File I/O error: {0}")]
21    Io(#[from] std::io::Error),
22
23    #[error("Serialization error: {0}")]
24    Serialization(#[from] serde_json::Error),
25
26    #[error("Permission error: {0}")]
27    Permission(String),
28
29    #[error("Encryption error: {0}")]
30    Encryption(#[from] crate::crypto::encryption::EncryptionError),
31}
32
33/// Storage backend trait
34#[async_trait]
35pub trait StorageBackend: Send + Sync {
36    async fn load_keys(&self) -> Result<HashMap<String, ApiKey>, StorageError>;
37    async fn save_key(&self, key: &ApiKey) -> Result<(), StorageError>;
38    async fn delete_key(&self, key_id: &str) -> Result<(), StorageError>;
39    async fn save_all_keys(&self, keys: &HashMap<String, ApiKey>) -> Result<(), StorageError>;
40}
41
42/// Create a storage backend from configuration
43pub async fn create_storage_backend(
44    config: &StorageConfig,
45) -> Result<Arc<dyn StorageBackend>, StorageError> {
46    match config {
47        StorageConfig::File {
48            path,
49            file_permissions,
50            dir_permissions,
51            require_secure_filesystem,
52            enable_filesystem_monitoring,
53        } => {
54            let storage = FileStorage::new(
55                path.clone(),
56                *file_permissions,
57                *dir_permissions,
58                *require_secure_filesystem,
59                *enable_filesystem_monitoring,
60            )
61            .await?;
62            Ok(Arc::new(storage))
63        }
64        StorageConfig::Environment { prefix } => {
65            let storage = EnvironmentStorage::new(prefix.clone());
66            Ok(Arc::new(storage))
67        }
68        StorageConfig::Memory => {
69            let storage = MemoryStorage::new();
70            Ok(Arc::new(storage))
71        }
72    }
73}
74
75/// File-based storage backend with atomic operations and encryption
76pub struct FileStorage {
77    path: PathBuf,
78    encryption_key: [u8; 32],
79    file_permissions: u32,
80    #[allow(dead_code)]
81    dir_permissions: u32,
82    #[allow(dead_code)]
83    require_secure_filesystem: bool,
84    enable_filesystem_monitoring: bool,
85}
86
87impl FileStorage {
88    pub async fn new(
89        path: PathBuf,
90        file_permissions: u32,
91        dir_permissions: u32,
92        require_secure_filesystem: bool,
93        enable_filesystem_monitoring: bool,
94    ) -> Result<Self, StorageError> {
95        use crate::crypto::encryption::derive_encryption_key;
96        use crate::crypto::keys::generate_master_key;
97
98        // Validate filesystem security if required
99        if require_secure_filesystem {
100            Self::validate_filesystem_security(&path).await?;
101        }
102
103        // Ensure parent directory exists with secure permissions
104        if let Some(parent) = path.parent() {
105            fs::create_dir_all(parent).await?;
106
107            // Set secure permissions on Unix
108            #[cfg(unix)]
109            {
110                use std::os::unix::fs::PermissionsExt;
111                let mut perms = fs::metadata(parent).await?.permissions();
112                perms.set_mode(dir_permissions); // Use configured directory permissions
113                fs::set_permissions(parent, perms).await?;
114
115                // Verify no other users have access
116                Self::verify_directory_security(parent, dir_permissions).await?;
117            }
118        }
119
120        // Generate or load master key, then derive storage key
121        let master_key = generate_master_key().map_err(|e| StorageError::General(e.to_string()))?;
122        let encryption_key = derive_encryption_key(&master_key, "api-key-storage");
123
124        let storage = Self {
125            path,
126            encryption_key,
127            file_permissions,
128            dir_permissions,
129            require_secure_filesystem,
130            enable_filesystem_monitoring,
131        };
132
133        // Initialize empty file if it doesn't exist
134        if !storage.path.exists() {
135            storage.save_all_keys(&HashMap::new()).await?;
136        } else {
137            // Verify existing file security
138            storage.ensure_secure_permissions().await?;
139        }
140
141        Ok(storage)
142    }
143
144    async fn ensure_secure_permissions(&self) -> Result<(), StorageError> {
145        #[cfg(unix)]
146        {
147            use std::os::unix::fs::PermissionsExt;
148
149            if self.path.exists() {
150                let metadata = fs::metadata(&self.path).await?;
151                let mode = metadata.permissions().mode() & 0o777;
152
153                // Check if permissions are more permissive than configured
154                if mode != self.file_permissions {
155                    warn!(
156                        "Incorrect permissions on key file: {:o}, fixing to {:o}",
157                        mode, self.file_permissions
158                    );
159                    let mut perms = metadata.permissions();
160                    perms.set_mode(self.file_permissions);
161                    fs::set_permissions(&self.path, perms).await?;
162                }
163
164                // Verify file ownership (only owner should have access)
165                Self::verify_file_ownership(&self.path).await?;
166            }
167        }
168        Ok(())
169    }
170
171    /// Validate that the filesystem is secure (not network/shared)
172    async fn validate_filesystem_security(path: &PathBuf) -> Result<(), StorageError> {
173        #[cfg(unix)]
174        {
175            use std::os::unix::fs::MetadataExt;
176
177            if let Some(parent) = path.parent() {
178                if parent.exists() {
179                    let metadata = fs::metadata(parent).await?;
180
181                    // Check if this is a network filesystem (basic check)
182                    let _dev = metadata.dev();
183
184                    // On many Unix systems, network filesystems have device IDs that indicate remote storage
185                    // This is a basic check - in production you might want more sophisticated detection
186                    if let Ok(mount_info) = fs::read_to_string("/proc/mounts").await {
187                        let path_str = parent.to_string_lossy();
188                        for line in mount_info.lines() {
189                            let parts: Vec<&str> = line.split_whitespace().collect();
190                            if parts.len() >= 3 {
191                                let mount_point = parts[1];
192                                let fs_type = parts[2];
193
194                                if path_str.starts_with(mount_point) {
195                                    // Check for network filesystem types
196                                    match fs_type {
197                                        "nfs" | "nfs4" | "cifs" | "smb" | "smbfs"
198                                        | "fuse.sshfs" => {
199                                            return Err(StorageError::Permission(format!(
200                                                "Storage path {} is on insecure network filesystem: {}", 
201                                                path_str, fs_type
202                                            )));
203                                        }
204                                        _ => {}
205                                    }
206                                }
207                            }
208                        }
209                    }
210                }
211            }
212        }
213
214        Ok(())
215    }
216
217    /// Verify directory security and ownership
218    async fn verify_directory_security(
219        dir: &std::path::Path,
220        expected_perms: u32,
221    ) -> Result<(), StorageError> {
222        #[cfg(unix)]
223        {
224            use std::os::unix::fs::{MetadataExt, PermissionsExt};
225
226            let metadata = fs::metadata(dir).await?;
227            let mode = metadata.permissions().mode() & 0o777;
228
229            // Verify permissions are not more permissive than expected
230            if (mode & !expected_perms) != 0 {
231                return Err(StorageError::Permission(format!(
232                    "Directory {} has insecure permissions: {:o} (expected: {:o})",
233                    dir.display(),
234                    mode,
235                    expected_perms
236                )));
237            }
238
239            // Verify ownership (should be current user)
240            let current_uid = unsafe { libc::getuid() };
241            if metadata.uid() != current_uid {
242                return Err(StorageError::Permission(format!(
243                    "Directory {} is not owned by current user (uid: {} vs {})",
244                    dir.display(),
245                    metadata.uid(),
246                    current_uid
247                )));
248            }
249        }
250
251        Ok(())
252    }
253
254    /// Verify file ownership
255    async fn verify_file_ownership(file: &std::path::Path) -> Result<(), StorageError> {
256        #[cfg(unix)]
257        {
258            use std::os::unix::fs::MetadataExt;
259
260            let metadata = fs::metadata(file).await?;
261            let current_uid = unsafe { libc::getuid() };
262
263            if metadata.uid() != current_uid {
264                return Err(StorageError::Permission(format!(
265                    "File {} is not owned by current user (uid: {} vs {})",
266                    file.display(),
267                    metadata.uid(),
268                    current_uid
269                )));
270            }
271        }
272
273        Ok(())
274    }
275
276    /// Save secure keys with encryption
277    async fn save_secure_keys(
278        &self,
279        keys: &HashMap<String, SecureApiKey>,
280    ) -> Result<(), StorageError> {
281        use crate::crypto::encryption::encrypt_data;
282
283        let content = serde_json::to_string_pretty(keys)?;
284        let encrypted_data = encrypt_data(content.as_bytes(), &self.encryption_key)?;
285        let encrypted_content = serde_json::to_string_pretty(&encrypted_data)?;
286
287        // Atomic write using temp file
288        let temp_path = self.path.with_extension("tmp");
289        fs::write(&temp_path, encrypted_content).await?;
290
291        // Set secure permissions before moving
292        #[cfg(unix)]
293        {
294            use std::os::unix::fs::PermissionsExt;
295            let mut perms = fs::metadata(&temp_path).await?.permissions();
296            perms.set_mode(self.file_permissions); // Use configured file permissions
297            fs::set_permissions(&temp_path, perms).await?;
298        }
299
300        // Atomic move
301        fs::rename(&temp_path, &self.path).await?;
302
303        debug!("Saved {} keys to encrypted file storage", keys.len());
304        Ok(())
305    }
306
307    /// Create a secure backup of the storage file
308    pub async fn create_backup(&self) -> Result<PathBuf, StorageError> {
309        if !self.path.exists() {
310            return Err(StorageError::General(
311                "Storage file does not exist".to_string(),
312            ));
313        }
314
315        let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S_%3f");
316        let backup_path = self
317            .path
318            .with_extension(format!("backup_{}.enc", timestamp));
319
320        // Copy with secure permissions
321        fs::copy(&self.path, &backup_path).await?;
322
323        #[cfg(unix)]
324        {
325            use std::os::unix::fs::PermissionsExt;
326            let mut perms = fs::metadata(&backup_path).await?.permissions();
327            perms.set_mode(self.file_permissions);
328            fs::set_permissions(&backup_path, perms).await?;
329        }
330
331        debug!("Created secure backup: {}", backup_path.display());
332        Ok(backup_path)
333    }
334
335    /// Restore from a backup file
336    pub async fn restore_from_backup(&self, backup_path: &PathBuf) -> Result<(), StorageError> {
337        if !backup_path.exists() {
338            return Err(StorageError::General(
339                "Backup file does not exist".to_string(),
340            ));
341        }
342
343        // Verify backup file security
344        Self::verify_file_ownership(backup_path).await?;
345
346        // Create temp file for atomic restore
347        let temp_path = self.path.with_extension("restore_tmp");
348        fs::copy(backup_path, &temp_path).await?;
349
350        #[cfg(unix)]
351        {
352            use std::os::unix::fs::PermissionsExt;
353            let mut perms = fs::metadata(&temp_path).await?.permissions();
354            perms.set_mode(self.file_permissions);
355            fs::set_permissions(&temp_path, perms).await?;
356        }
357
358        // Atomic move
359        fs::rename(&temp_path, &self.path).await?;
360
361        info!("Restored from backup: {}", backup_path.display());
362        Ok(())
363    }
364
365    /// Clean up old backup files (keep only last N backups)
366    pub async fn cleanup_backups(&self, keep_count: usize) -> Result<(), StorageError> {
367        if let Some(parent) = self.path.parent() {
368            let filename_stem = self
369                .path
370                .file_stem()
371                .and_then(|s| s.to_str())
372                .unwrap_or("keys");
373
374            let mut backups = Vec::new();
375            let mut entries = fs::read_dir(parent).await?;
376
377            while let Some(entry) = entries.next_entry().await? {
378                let path = entry.path();
379                if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
380                    if filename.starts_with(&format!("{}.backup_", filename_stem)) {
381                        if let Ok(metadata) = entry.metadata().await {
382                            backups.push((
383                                path,
384                                metadata
385                                    .modified()
386                                    .unwrap_or(std::time::SystemTime::UNIX_EPOCH),
387                            ));
388                        }
389                    }
390                }
391            }
392
393            // Sort by modification time (newest first)
394            backups.sort_by(|a, b| b.1.cmp(&a.1));
395
396            // Remove old backups
397            for (backup_path, _) in backups.iter().skip(keep_count) {
398                if let Err(e) = fs::remove_file(backup_path).await {
399                    warn!(
400                        "Failed to remove old backup {}: {}",
401                        backup_path.display(),
402                        e
403                    );
404                } else {
405                    debug!("Removed old backup: {}", backup_path.display());
406                }
407            }
408        }
409
410        Ok(())
411    }
412
413    /// Start filesystem monitoring for unauthorized changes (Linux only)
414    #[cfg(target_os = "linux")]
415    pub async fn start_filesystem_monitoring(&self) -> Result<(), StorageError> {
416        if !self.enable_filesystem_monitoring {
417            return Ok(());
418        }
419
420        use inotify::{Inotify, WatchMask};
421
422        let mut inotify = Inotify::init()
423            .map_err(|e| StorageError::General(format!("Failed to initialize inotify: {}", e)))?;
424
425        // Watch the directory for changes
426        if let Some(parent) = self.path.parent() {
427            inotify
428                .watches()
429                .add(
430                    parent,
431                    WatchMask::MODIFY | WatchMask::ATTRIB | WatchMask::MOVED_TO | WatchMask::DELETE,
432                )
433                .map_err(|e| {
434                    StorageError::General(format!("Failed to add inotify watch: {}", e))
435                })?;
436
437            info!("Started filesystem monitoring for: {}", parent.display());
438
439            // Spawn background task to monitor changes
440            let path = self.path.clone();
441            let file_permissions = self.file_permissions;
442
443            tokio::spawn(async move {
444                use tracing::{error, warn};
445                let mut buffer = [0; 1024];
446                loop {
447                    match inotify.read_events_blocking(&mut buffer) {
448                        Ok(events) => {
449                            for event in events {
450                                if let Some(name) = event.name {
451                                    if name.to_string_lossy().contains("keys") {
452                                        warn!(
453                                            "Detected unauthorized change to auth storage: {:?} (mask: {:?})",
454                                            name, event.mask
455                                        );
456
457                                        // Verify file permissions haven't been changed
458                                        if path.exists() {
459                                            #[cfg(unix)]
460                                            {
461                                                use std::os::unix::fs::PermissionsExt;
462                                                if let Ok(metadata) = std::fs::metadata(&path) {
463                                                    let mode =
464                                                        metadata.permissions().mode() & 0o777;
465                                                    if mode != file_permissions {
466                                                        error!(
467                                                            "Security violation: File permissions changed from {:o} to {:o}",
468                                                            file_permissions, mode
469                                                        );
470                                                    }
471                                                }
472                                            }
473                                        }
474                                    }
475                                }
476                            }
477                        }
478                        Err(e) => {
479                            error!("Error reading inotify events: {}", e);
480                            break;
481                        }
482                    }
483                }
484            });
485        }
486
487        Ok(())
488    }
489
490    /// Start filesystem monitoring (no-op on non-Linux systems)
491    #[cfg(not(target_os = "linux"))]
492    pub async fn start_filesystem_monitoring(&self) -> Result<(), StorageError> {
493        if self.enable_filesystem_monitoring {
494            warn!("Filesystem monitoring is only supported on Linux systems");
495        }
496        Ok(())
497    }
498}
499
500#[async_trait]
501impl StorageBackend for FileStorage {
502    async fn load_keys(&self) -> Result<HashMap<String, ApiKey>, StorageError> {
503        use crate::crypto::encryption::decrypt_data;
504
505        self.ensure_secure_permissions().await?;
506
507        if !self.path.exists() {
508            return Ok(HashMap::new());
509        }
510
511        let content = fs::read(&self.path).await?;
512        if content.is_empty() {
513            return Ok(HashMap::new());
514        }
515
516        // Try to decrypt the content (new format)
517        let decrypted_content = if let Ok(encrypted_data) = serde_json::from_slice(&content) {
518            // Encrypted format
519            let decrypted_bytes = decrypt_data(&encrypted_data, &self.encryption_key)?;
520            String::from_utf8(decrypted_bytes)
521                .map_err(|e| StorageError::General(format!("Invalid UTF-8: {}", e)))?
522        } else {
523            // Legacy plain text format - convert to secure format
524            let plain_text = String::from_utf8(content)
525                .map_err(|e| StorageError::General(format!("Invalid UTF-8: {}", e)))?;
526            warn!("Found legacy plain text keys, converting to secure format");
527
528            // Load legacy keys and convert them
529            let legacy_keys: HashMap<String, ApiKey> = serde_json::from_str(&plain_text)?;
530            let secure_keys: HashMap<String, SecureApiKey> = legacy_keys
531                .into_iter()
532                .map(|(id, key)| (id, key.to_secure_storage()))
533                .collect();
534
535            // Save in secure format
536            self.save_secure_keys(&secure_keys).await?;
537
538            // Return the decrypted content for this load
539            plain_text
540        };
541
542        // Parse secure keys from decrypted content
543        let secure_keys: HashMap<String, SecureApiKey> = serde_json::from_str(&decrypted_content)?;
544
545        // Convert secure keys back to API keys (without plain text)
546        let keys: HashMap<String, ApiKey> = secure_keys
547            .into_iter()
548            .map(|(id, secure_key)| (id, secure_key.to_api_key()))
549            .collect();
550
551        debug!("Loaded {} keys from encrypted file storage", keys.len());
552        Ok(keys)
553    }
554
555    async fn save_key(&self, key: &ApiKey) -> Result<(), StorageError> {
556        let mut keys = self.load_keys().await?;
557        keys.insert(key.id.clone(), key.clone());
558        self.save_all_keys(&keys).await
559    }
560
561    async fn delete_key(&self, key_id: &str) -> Result<(), StorageError> {
562        let mut keys = self.load_keys().await?;
563        keys.remove(key_id);
564        self.save_all_keys(&keys).await
565    }
566
567    async fn save_all_keys(&self, keys: &HashMap<String, ApiKey>) -> Result<(), StorageError> {
568        // Convert to secure keys for storage
569        let secure_keys: HashMap<String, SecureApiKey> = keys
570            .iter()
571            .map(|(id, key)| (id.clone(), key.to_secure_storage()))
572            .collect();
573
574        self.save_secure_keys(&secure_keys).await
575    }
576}
577
578/// Environment variable storage backend
579pub struct EnvironmentStorage {
580    var_name: String,
581}
582
583impl EnvironmentStorage {
584    pub fn new(var_name: String) -> Self {
585        Self { var_name }
586    }
587}
588
589#[async_trait]
590impl StorageBackend for EnvironmentStorage {
591    async fn load_keys(&self) -> Result<HashMap<String, ApiKey>, StorageError> {
592        match std::env::var(&self.var_name) {
593            Ok(content) => {
594                if content.trim().is_empty() {
595                    return Ok(HashMap::new());
596                }
597                let keys: HashMap<String, ApiKey> = serde_json::from_str(&content)?;
598                debug!("Loaded {} keys from environment storage", keys.len());
599                Ok(keys)
600            }
601            Err(_) => {
602                debug!(
603                    "Environment variable {} not found, returning empty keys",
604                    self.var_name
605                );
606                Ok(HashMap::new())
607            }
608        }
609    }
610
611    async fn save_key(&self, key: &ApiKey) -> Result<(), StorageError> {
612        let mut keys = self.load_keys().await?;
613        keys.insert(key.id.clone(), key.clone());
614        self.save_all_keys(&keys).await
615    }
616
617    async fn delete_key(&self, key_id: &str) -> Result<(), StorageError> {
618        let mut keys = self.load_keys().await?;
619        keys.remove(key_id);
620        self.save_all_keys(&keys).await
621    }
622
623    async fn save_all_keys(&self, keys: &HashMap<String, ApiKey>) -> Result<(), StorageError> {
624        let content = serde_json::to_string(keys)?;
625        std::env::set_var(&self.var_name, content);
626
627        debug!("Saved {} keys to environment storage", keys.len());
628        Ok(())
629    }
630}
631
632/// In-memory storage backend (for testing)
633pub struct MemoryStorage {
634    keys: tokio::sync::RwLock<HashMap<String, ApiKey>>,
635}
636
637impl MemoryStorage {
638    pub fn new() -> Self {
639        Self {
640            keys: tokio::sync::RwLock::new(HashMap::new()),
641        }
642    }
643}
644
645#[async_trait]
646impl StorageBackend for MemoryStorage {
647    async fn load_keys(&self) -> Result<HashMap<String, ApiKey>, StorageError> {
648        let keys = self.keys.read().await;
649        debug!("Loaded {} keys from memory storage", keys.len());
650        Ok(keys.clone())
651    }
652
653    async fn save_key(&self, key: &ApiKey) -> Result<(), StorageError> {
654        let mut keys = self.keys.write().await;
655        keys.insert(key.id.clone(), key.clone());
656        debug!("Saved key {} to memory storage", key.id);
657        Ok(())
658    }
659
660    async fn delete_key(&self, key_id: &str) -> Result<(), StorageError> {
661        let mut keys = self.keys.write().await;
662        keys.remove(key_id);
663        debug!("Deleted key {} from memory storage", key_id);
664        Ok(())
665    }
666
667    async fn save_all_keys(&self, new_keys: &HashMap<String, ApiKey>) -> Result<(), StorageError> {
668        let mut keys = self.keys.write().await;
669        *keys = new_keys.clone();
670        debug!(
671            "Replaced all keys in memory storage with {} keys",
672            new_keys.len()
673        );
674        Ok(())
675    }
676}
677
678#[cfg(test)]
679mod tests {
680    use super::*;
681    use crate::models::{ApiKey, Role};
682    use chrono::{Duration, Utc};
683    use std::collections::HashMap;
684    use tempfile::TempDir;
685    use tokio::fs;
686
687    // Helper function to create test API key
688    fn create_test_key(name: &str, role: Role) -> ApiKey {
689        ApiKey::new(
690            name.to_string(),
691            role,
692            Some(Utc::now() + Duration::days(30)),
693            vec!["127.0.0.1".to_string()],
694        )
695    }
696
697    // Helper function to create multiple test keys
698    fn create_test_keys() -> HashMap<String, ApiKey> {
699        let mut keys = HashMap::new();
700
701        let admin_key = create_test_key("admin-key", Role::Admin);
702        let operator_key = create_test_key("operator-key", Role::Operator);
703        let monitor_key = create_test_key("monitor-key", Role::Monitor);
704
705        keys.insert(admin_key.id.clone(), admin_key);
706        keys.insert(operator_key.id.clone(), operator_key);
707        keys.insert(monitor_key.id.clone(), monitor_key);
708
709        keys
710    }
711
712    #[test]
713    fn test_storage_error_display() {
714        let error = StorageError::General("test error".to_string());
715        assert_eq!(error.to_string(), "Storage error: test error");
716
717        let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
718        let storage_error = StorageError::Io(io_error);
719        assert!(storage_error.to_string().contains("File I/O error"));
720
721        let perm_error = StorageError::Permission("access denied".to_string());
722        assert_eq!(perm_error.to_string(), "Permission error: access denied");
723    }
724
725    #[test]
726    fn test_storage_error_from_io_error() {
727        let io_error =
728            std::io::Error::new(std::io::ErrorKind::PermissionDenied, "permission denied");
729        let storage_error: StorageError = io_error.into();
730
731        match storage_error {
732            StorageError::Io(_) => (),
733            _ => panic!("Expected Io variant"),
734        }
735    }
736
737    #[test]
738    fn test_storage_error_from_serde_error() {
739        let serde_error = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
740        let storage_error: StorageError = serde_error.into();
741
742        match storage_error {
743            StorageError::Serialization(_) => (),
744            _ => panic!("Expected Serialization variant"),
745        }
746    }
747
748    mod memory_storage_tests {
749        use super::*;
750
751        #[tokio::test]
752        async fn test_memory_storage_new() {
753            let storage = MemoryStorage::new();
754            let keys = storage.load_keys().await.unwrap();
755            assert!(keys.is_empty());
756        }
757
758        #[tokio::test]
759        async fn test_memory_storage_save_and_load_key() {
760            let storage = MemoryStorage::new();
761            let test_key = create_test_key("test-key", Role::Operator);
762
763            storage.save_key(&test_key).await.unwrap();
764
765            let keys = storage.load_keys().await.unwrap();
766            assert_eq!(keys.len(), 1);
767            assert!(keys.contains_key(&test_key.id));
768
769            let loaded_key = &keys[&test_key.id];
770            assert_eq!(loaded_key.name, test_key.name);
771            assert_eq!(loaded_key.role, test_key.role);
772        }
773
774        #[tokio::test]
775        async fn test_memory_storage_save_multiple_keys() {
776            let storage = MemoryStorage::new();
777            let test_keys = create_test_keys();
778
779            for key in test_keys.values() {
780                storage.save_key(key).await.unwrap();
781            }
782
783            let loaded_keys = storage.load_keys().await.unwrap();
784            assert_eq!(loaded_keys.len(), test_keys.len());
785
786            for (id, key) in test_keys.iter() {
787                assert!(loaded_keys.contains_key(id));
788                assert_eq!(loaded_keys[id].name, key.name);
789            }
790        }
791
792        #[tokio::test]
793        async fn test_memory_storage_delete_key() {
794            let storage = MemoryStorage::new();
795            let test_key = create_test_key("test-key", Role::Monitor);
796
797            storage.save_key(&test_key).await.unwrap();
798            assert_eq!(storage.load_keys().await.unwrap().len(), 1);
799
800            storage.delete_key(&test_key.id).await.unwrap();
801            let keys = storage.load_keys().await.unwrap();
802            assert!(keys.is_empty());
803        }
804
805        #[tokio::test]
806        async fn test_memory_storage_delete_nonexistent_key() {
807            let storage = MemoryStorage::new();
808
809            // Should not error when deleting non-existent key
810            storage.delete_key("nonexistent").await.unwrap();
811            assert!(storage.load_keys().await.unwrap().is_empty());
812        }
813
814        #[tokio::test]
815        async fn test_memory_storage_save_all_keys() {
816            let storage = MemoryStorage::new();
817            let test_keys = create_test_keys();
818
819            storage.save_all_keys(&test_keys).await.unwrap();
820
821            let loaded_keys = storage.load_keys().await.unwrap();
822            assert_eq!(loaded_keys.len(), test_keys.len());
823
824            for (id, key) in test_keys.iter() {
825                assert!(loaded_keys.contains_key(id));
826                assert_eq!(loaded_keys[id].name, key.name);
827            }
828        }
829
830        #[tokio::test]
831        async fn test_memory_storage_save_all_keys_replaces_existing() {
832            let storage = MemoryStorage::new();
833
834            // Save initial keys
835            let initial_keys = create_test_keys();
836            storage.save_all_keys(&initial_keys).await.unwrap();
837            assert_eq!(storage.load_keys().await.unwrap().len(), initial_keys.len());
838
839            // Replace with new set
840            let mut new_keys = HashMap::new();
841            let new_key = create_test_key("new-key", Role::Admin);
842            new_keys.insert(new_key.id.clone(), new_key);
843
844            storage.save_all_keys(&new_keys).await.unwrap();
845
846            let loaded_keys = storage.load_keys().await.unwrap();
847            assert_eq!(loaded_keys.len(), 1);
848            assert!(loaded_keys.contains_key(new_keys.keys().next().unwrap()));
849        }
850
851        #[tokio::test]
852        async fn test_memory_storage_concurrent_access() {
853            let storage = std::sync::Arc::new(MemoryStorage::new());
854            let mut handles = vec![];
855
856            // Spawn multiple tasks that save keys concurrently
857            for i in 0..10 {
858                let storage_clone = storage.clone();
859                let handle = tokio::spawn(async move {
860                    let key = create_test_key(&format!("key-{}", i), Role::Operator);
861                    storage_clone.save_key(&key).await.unwrap();
862                    key.id
863                });
864                handles.push(handle);
865            }
866
867            let mut saved_ids = vec![];
868            for handle in handles {
869                saved_ids.push(handle.await.unwrap());
870            }
871
872            let keys = storage.load_keys().await.unwrap();
873            assert_eq!(keys.len(), 10);
874
875            for id in saved_ids {
876                assert!(keys.contains_key(&id));
877            }
878        }
879    }
880
881    mod environment_storage_tests {
882        use super::*;
883
884        #[tokio::test]
885        async fn test_environment_storage_new() {
886            let storage = EnvironmentStorage::new("TEST_MCP_KEYS".to_string());
887
888            // Clear any existing value
889            std::env::remove_var("TEST_MCP_KEYS");
890
891            let keys = storage.load_keys().await.unwrap();
892            assert!(keys.is_empty());
893        }
894
895        #[tokio::test]
896        async fn test_environment_storage_save_and_load_key() {
897            let var_name = "TEST_MCP_KEYS_SAVE_LOAD";
898            std::env::remove_var(var_name);
899
900            let storage = EnvironmentStorage::new(var_name.to_string());
901            let test_key = create_test_key("env-test-key", Role::Monitor);
902
903            storage.save_key(&test_key).await.unwrap();
904
905            let keys = storage.load_keys().await.unwrap();
906            assert_eq!(keys.len(), 1);
907            assert!(keys.contains_key(&test_key.id));
908
909            // Verify environment variable was set
910            assert!(std::env::var(var_name).is_ok());
911
912            // Cleanup
913            std::env::remove_var(var_name);
914        }
915
916        #[tokio::test]
917        async fn test_environment_storage_multiple_keys() {
918            let var_name = "TEST_MCP_KEYS_MULTIPLE";
919            std::env::remove_var(var_name);
920
921            let storage = EnvironmentStorage::new(var_name.to_string());
922            let test_keys = create_test_keys();
923
924            storage.save_all_keys(&test_keys).await.unwrap();
925
926            let loaded_keys = storage.load_keys().await.unwrap();
927            assert_eq!(loaded_keys.len(), test_keys.len());
928
929            for (id, key) in test_keys.iter() {
930                assert!(loaded_keys.contains_key(id));
931                assert_eq!(loaded_keys[id].name, key.name);
932            }
933
934            // Cleanup
935            std::env::remove_var(var_name);
936        }
937
938        #[tokio::test]
939        async fn test_environment_storage_delete_key() {
940            let var_name = "TEST_MCP_KEYS_DELETE";
941            std::env::remove_var(var_name);
942
943            let storage = EnvironmentStorage::new(var_name.to_string());
944            let test_keys = create_test_keys();
945            let key_to_delete = test_keys.values().next().unwrap().id.clone();
946
947            storage.save_all_keys(&test_keys).await.unwrap();
948            assert_eq!(storage.load_keys().await.unwrap().len(), test_keys.len());
949
950            storage.delete_key(&key_to_delete).await.unwrap();
951
952            let remaining_keys = storage.load_keys().await.unwrap();
953            assert_eq!(remaining_keys.len(), test_keys.len() - 1);
954            assert!(!remaining_keys.contains_key(&key_to_delete));
955
956            // Cleanup
957            std::env::remove_var(var_name);
958        }
959
960        #[tokio::test]
961        async fn test_environment_storage_empty_content() {
962            let var_name = "TEST_MCP_KEYS_EMPTY";
963            std::env::set_var(var_name, "");
964
965            let storage = EnvironmentStorage::new(var_name.to_string());
966            let keys = storage.load_keys().await.unwrap();
967            assert!(keys.is_empty());
968
969            // Cleanup
970            std::env::remove_var(var_name);
971        }
972
973        #[tokio::test]
974        async fn test_environment_storage_invalid_json() {
975            let var_name = "TEST_MCP_KEYS_INVALID";
976            std::env::set_var(var_name, "invalid json content");
977
978            let storage = EnvironmentStorage::new(var_name.to_string());
979            let result = storage.load_keys().await;
980
981            assert!(result.is_err());
982            match result.unwrap_err() {
983                StorageError::Serialization(_) => (),
984                _ => panic!("Expected serialization error"),
985            }
986
987            // Cleanup
988            std::env::remove_var(var_name);
989        }
990
991        #[tokio::test]
992        async fn test_environment_storage_overwrite_existing() {
993            let var_name = "TEST_MCP_KEYS_OVERWRITE";
994            std::env::remove_var(var_name);
995
996            let storage = EnvironmentStorage::new(var_name.to_string());
997
998            // Save initial keys
999            let initial_keys = create_test_keys();
1000            storage.save_all_keys(&initial_keys).await.unwrap();
1001
1002            // Save new keys (should overwrite)
1003            let mut new_keys = HashMap::new();
1004            let new_key = create_test_key("overwrite-key", Role::Admin);
1005            new_keys.insert(new_key.id.clone(), new_key);
1006
1007            storage.save_all_keys(&new_keys).await.unwrap();
1008
1009            let loaded_keys = storage.load_keys().await.unwrap();
1010            assert_eq!(loaded_keys.len(), 1);
1011            assert!(loaded_keys.contains_key(new_keys.keys().next().unwrap()));
1012
1013            // Cleanup
1014            std::env::remove_var(var_name);
1015        }
1016    }
1017
1018    mod file_storage_tests {
1019        use super::*;
1020
1021        async fn create_test_file_storage() -> (FileStorage, TempDir) {
1022            // Set a consistent master key for all file storage tests
1023            std::env::set_var(
1024                "PULSEENGINE_MCP_MASTER_KEY",
1025                "l9EYbalIRp2CF35M4mKcWDqRvx3TFc7U4nX5zvQF56Q",
1026            );
1027            let temp_dir = TempDir::new().unwrap();
1028            let storage_path = temp_dir.path().join("test_keys.enc");
1029
1030            let storage = FileStorage::new(
1031                storage_path,
1032                0o600,
1033                0o700,
1034                false, // Don't require secure filesystem for tests
1035                false, // Don't enable filesystem monitoring for tests
1036            )
1037            .await
1038            .unwrap();
1039
1040            (storage, temp_dir)
1041        }
1042
1043        #[tokio::test]
1044        async fn test_file_storage_new() {
1045            let (storage, _temp_dir) = create_test_file_storage().await;
1046
1047            // Should create empty storage initially
1048            let keys = storage.load_keys().await.unwrap();
1049            assert!(keys.is_empty());
1050
1051            // Storage file should exist after creation
1052            assert!(storage.path.exists());
1053        }
1054
1055        #[tokio::test]
1056        async fn test_file_storage_save_and_load_key() {
1057            let (storage, _temp_dir) = create_test_file_storage().await;
1058            let test_key = create_test_key("file-test-key", Role::Operator);
1059
1060            storage.save_key(&test_key).await.unwrap();
1061
1062            let keys = storage.load_keys().await.unwrap();
1063            assert_eq!(keys.len(), 1);
1064            assert!(keys.contains_key(&test_key.id));
1065
1066            let loaded_key = &keys[&test_key.id];
1067            assert_eq!(loaded_key.name, test_key.name);
1068            assert_eq!(loaded_key.role, test_key.role);
1069            // Note: Plain text key should be redacted in loaded key
1070            assert_eq!(loaded_key.key, "***redacted***");
1071        }
1072
1073        #[tokio::test]
1074        async fn test_file_storage_multiple_keys() {
1075            let (storage, _temp_dir) = create_test_file_storage().await;
1076            let test_keys = create_test_keys();
1077
1078            storage.save_all_keys(&test_keys).await.unwrap();
1079
1080            let loaded_keys = storage.load_keys().await.unwrap();
1081            assert_eq!(loaded_keys.len(), test_keys.len());
1082
1083            for (id, key) in test_keys.iter() {
1084                assert!(loaded_keys.contains_key(id));
1085                assert_eq!(loaded_keys[id].name, key.name);
1086                assert_eq!(loaded_keys[id].role, key.role);
1087            }
1088        }
1089
1090        #[tokio::test]
1091        async fn test_file_storage_delete_key() {
1092            let (storage, _temp_dir) = create_test_file_storage().await;
1093            let test_keys = create_test_keys();
1094            let key_to_delete = test_keys.values().next().unwrap().id.clone();
1095
1096            storage.save_all_keys(&test_keys).await.unwrap();
1097            assert_eq!(storage.load_keys().await.unwrap().len(), test_keys.len());
1098
1099            storage.delete_key(&key_to_delete).await.unwrap();
1100
1101            let remaining_keys = storage.load_keys().await.unwrap();
1102            assert_eq!(remaining_keys.len(), test_keys.len() - 1);
1103            assert!(!remaining_keys.contains_key(&key_to_delete));
1104        }
1105
1106        #[tokio::test]
1107        async fn test_file_storage_encryption() {
1108            let (storage, _temp_dir) = create_test_file_storage().await;
1109            let test_key = create_test_key("encryption-test", Role::Admin);
1110
1111            storage.save_key(&test_key).await.unwrap();
1112
1113            // Read raw file content - should be encrypted
1114            let raw_content = fs::read(&storage.path).await.unwrap();
1115            let raw_text = String::from_utf8_lossy(&raw_content);
1116
1117            // Should not contain plain text key information
1118            assert!(!raw_text.contains(&test_key.name));
1119            assert!(!raw_text.contains(&test_key.key));
1120
1121            // But should be loadable through storage interface
1122            let loaded_keys = storage.load_keys().await.unwrap();
1123            assert_eq!(loaded_keys.len(), 1);
1124            assert!(loaded_keys.contains_key(&test_key.id));
1125        }
1126
1127        #[tokio::test]
1128        async fn test_file_storage_empty_file() {
1129            let temp_dir = TempDir::new().unwrap();
1130            let storage_path = temp_dir.path().join("empty_keys.enc");
1131
1132            // Create empty file
1133            fs::write(&storage_path, "").await.unwrap();
1134
1135            let storage = FileStorage::new(storage_path, 0o600, 0o700, false, false)
1136                .await
1137                .unwrap();
1138
1139            let keys = storage.load_keys().await.unwrap();
1140            assert!(keys.is_empty());
1141        }
1142
1143        #[tokio::test]
1144        async fn test_file_storage_nonexistent_file() {
1145            let temp_dir = TempDir::new().unwrap();
1146            let storage_path = temp_dir.path().join("nonexistent").join("keys.enc");
1147
1148            // Parent directory doesn't exist - should be created
1149            let storage = FileStorage::new(storage_path.clone(), 0o600, 0o700, false, false)
1150                .await
1151                .unwrap();
1152
1153            // Should create empty storage
1154            let keys = storage.load_keys().await.unwrap();
1155            assert!(keys.is_empty());
1156            assert!(storage_path.exists());
1157        }
1158
1159        #[tokio::test]
1160        async fn test_file_storage_persistence() {
1161            // Set a consistent master key for persistence testing
1162            // Use a lock to ensure this test doesn't interfere with others
1163            static TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
1164            let _lock = TEST_LOCK.lock().unwrap();
1165            
1166            // First, ensure no master key env var exists to avoid interference
1167            let original_master_key = std::env::var("PULSEENGINE_MCP_MASTER_KEY").ok();
1168            
1169            // Set our test master key
1170            std::env::set_var(
1171                "PULSEENGINE_MCP_MASTER_KEY",
1172                "l9EYbalIRp2CF35M4mKcWDqRvx3TFc7U4nX5zvQF56Q",
1173            );
1174            
1175            // Small delay to ensure environment variable is set across threads
1176            tokio::time::sleep(std::time::Duration::from_millis(10)).await;
1177
1178            let temp_dir = TempDir::new().unwrap();
1179            let storage_path = temp_dir.path().join("persistent_keys.enc");
1180            let test_keys = create_test_keys();
1181
1182            // Create storage and save keys
1183            {
1184                let storage = FileStorage::new(storage_path.clone(), 0o600, 0o700, false, false)
1185                    .await
1186                    .unwrap();
1187
1188                storage.save_all_keys(&test_keys).await.unwrap();
1189            }
1190            
1191            // Ensure the file was created and has content
1192            assert!(storage_path.exists());
1193            let file_metadata = std::fs::metadata(&storage_path).unwrap();
1194            assert!(file_metadata.len() > 0);
1195
1196            // Create new storage instance and verify keys persist
1197            {
1198                let storage = FileStorage::new(storage_path, 0o600, 0o700, false, false)
1199                    .await
1200                    .unwrap();
1201
1202                let loaded_keys = storage.load_keys().await.unwrap();
1203                assert_eq!(loaded_keys.len(), test_keys.len());
1204
1205                for (id, key) in test_keys.iter() {
1206                    assert!(loaded_keys.contains_key(id));
1207                    assert_eq!(loaded_keys[id].name, key.name);
1208                }
1209            }
1210
1211            // Restore original environment variable or remove if it didn't exist
1212            match original_master_key {
1213                Some(key) => std::env::set_var("PULSEENGINE_MCP_MASTER_KEY", key),
1214                None => std::env::remove_var("PULSEENGINE_MCP_MASTER_KEY"),
1215            }
1216        }
1217
1218        #[tokio::test]
1219        async fn test_file_storage_backup_and_restore() {
1220            let (storage, _temp_dir) = create_test_file_storage().await;
1221            let test_keys = create_test_keys();
1222
1223            // Save initial keys
1224            storage.save_all_keys(&test_keys).await.unwrap();
1225
1226            // Create backup
1227            let backup_path = storage.create_backup().await.unwrap();
1228            assert!(backup_path.exists());
1229            assert!(backup_path.to_string_lossy().contains("backup_"));
1230
1231            // Modify storage
1232            let mut modified_keys = HashMap::new();
1233            let new_key = create_test_key("backup-test", Role::Monitor);
1234            modified_keys.insert(new_key.id.clone(), new_key);
1235            storage.save_all_keys(&modified_keys).await.unwrap();
1236
1237            // Verify modification
1238            assert_eq!(storage.load_keys().await.unwrap().len(), 1);
1239
1240            // Restore from backup
1241            storage.restore_from_backup(&backup_path).await.unwrap();
1242
1243            // Verify restoration
1244            let restored_keys = storage.load_keys().await.unwrap();
1245            assert_eq!(restored_keys.len(), test_keys.len());
1246
1247            for id in test_keys.keys() {
1248                assert!(restored_keys.contains_key(id));
1249            }
1250        }
1251
1252        #[tokio::test]
1253        async fn test_file_storage_backup_nonexistent_storage() {
1254            let temp_dir = TempDir::new().unwrap();
1255            let storage_path = temp_dir.path().join("missing_keys.enc");
1256
1257            let storage = FileStorage::new(storage_path, 0o600, 0o700, false, false)
1258                .await
1259                .unwrap();
1260
1261            // Delete the storage file to simulate missing file
1262            fs::remove_file(&storage.path).await.unwrap();
1263
1264            let result = storage.create_backup().await;
1265            assert!(result.is_err());
1266            match result.unwrap_err() {
1267                StorageError::General(msg) => assert!(msg.contains("does not exist")),
1268                _ => panic!("Expected general error"),
1269            }
1270        }
1271
1272        #[tokio::test]
1273        async fn test_file_storage_restore_nonexistent_backup() {
1274            let (storage, temp_dir) = create_test_file_storage().await;
1275            let nonexistent_backup = temp_dir.path().join("nonexistent_backup.enc");
1276
1277            let result = storage.restore_from_backup(&nonexistent_backup).await;
1278            assert!(result.is_err());
1279            match result.unwrap_err() {
1280                StorageError::General(msg) => assert!(msg.contains("does not exist")),
1281                _ => panic!("Expected general error"),
1282            }
1283        }
1284
1285        #[tokio::test]
1286        async fn test_file_storage_cleanup_backups() {
1287            // Use a lock to ensure this test doesn't interfere with others
1288            static TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
1289            let _lock = TEST_LOCK.lock().unwrap();
1290            
1291            // Store original master key to restore later
1292            let original_master_key = std::env::var("PULSEENGINE_MCP_MASTER_KEY").ok();
1293            
1294            // Set a consistent master key for cleanup testing
1295            std::env::set_var(
1296                "PULSEENGINE_MCP_MASTER_KEY",
1297                "l9EYbalIRp2CF35M4mKcWDqRvx3TFc7U4nX5zvQF56Q",
1298            );
1299            
1300            // Small delay to ensure environment variable is set across threads
1301            tokio::time::sleep(std::time::Duration::from_millis(10)).await;
1302
1303            let (storage, _temp_dir) = create_test_file_storage().await;
1304            let test_key = create_test_key("cleanup-test", Role::Admin);
1305
1306            storage.save_key(&test_key).await.unwrap();
1307
1308            // Create multiple backups
1309            let mut backup_paths = vec![];
1310            for _i in 0..5 {
1311                let backup_path = storage.create_backup().await.unwrap();
1312                backup_paths.push(backup_path);
1313                // Small delay to ensure different timestamps
1314                tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
1315            }
1316
1317            // Verify all backups exist
1318            for (i, path) in backup_paths.iter().enumerate() {
1319                assert!(path.exists(), "Backup {} does not exist: {:?}", i, path);
1320            }
1321
1322            // Cleanup keeping only 2 backups
1323            storage.cleanup_backups(2).await.unwrap();
1324
1325            // Count remaining backup files
1326            let parent = storage.path.parent().unwrap();
1327            let mut remaining_backups = 0;
1328            let mut entries = fs::read_dir(parent).await.unwrap();
1329
1330            while let Some(entry) = entries.next_entry().await.unwrap() {
1331                if entry.file_name().to_string_lossy().contains("backup_") {
1332                    remaining_backups += 1;
1333                }
1334            }
1335
1336            assert_eq!(remaining_backups, 2);
1337
1338            // Restore original environment variable or remove if it didn't exist
1339            match original_master_key {
1340                Some(key) => std::env::set_var("PULSEENGINE_MCP_MASTER_KEY", key),
1341                None => std::env::remove_var("PULSEENGINE_MCP_MASTER_KEY"),
1342            }
1343        }
1344
1345        #[cfg(unix)]
1346        #[tokio::test]
1347        async fn test_file_storage_permissions() {
1348            use std::os::unix::fs::PermissionsExt;
1349
1350            let (storage, _temp_dir) = create_test_file_storage().await;
1351            let test_key = create_test_key("perm-test", Role::Operator);
1352
1353            storage.save_key(&test_key).await.unwrap();
1354
1355            // Check file permissions
1356            let metadata = fs::metadata(&storage.path).await.unwrap();
1357            let mode = metadata.permissions().mode() & 0o777;
1358            assert_eq!(mode, 0o600);
1359
1360            // Check parent directory permissions
1361            if let Some(parent) = storage.path.parent() {
1362                let parent_metadata = fs::metadata(parent).await.unwrap();
1363                let parent_mode = parent_metadata.permissions().mode() & 0o777;
1364                assert_eq!(parent_mode, 0o700);
1365            }
1366        }
1367
1368        #[tokio::test]
1369        async fn test_file_storage_atomic_operations() {
1370            // Use a lock to ensure this test doesn't interfere with others
1371            static TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
1372            let _lock = TEST_LOCK.lock().unwrap();
1373            
1374            // Store original master key to restore later
1375            let original_master_key = std::env::var("PULSEENGINE_MCP_MASTER_KEY").ok();
1376            
1377            // Set a consistent master key for atomic operations testing
1378            std::env::set_var(
1379                "PULSEENGINE_MCP_MASTER_KEY",
1380                "l9EYbalIRp2CF35M4mKcWDqRvx3TFc7U4nX5zvQF56Q",
1381            );
1382            
1383            // Small delay to ensure environment variable is set across threads
1384            tokio::time::sleep(std::time::Duration::from_millis(10)).await;
1385
1386            let (storage, _temp_dir) = create_test_file_storage().await;
1387            let initial_keys = create_test_keys();
1388
1389            storage.save_all_keys(&initial_keys).await.unwrap();
1390
1391            // Simulate concurrent operations
1392            let storage_clone = std::sync::Arc::new(storage);
1393            let mut handles = vec![];
1394
1395            for i in 0..10 {
1396                let storage_ref = storage_clone.clone();
1397                let handle = tokio::spawn(async move {
1398                    let key = create_test_key(&format!("concurrent-{}", i), Role::Monitor);
1399                    storage_ref.save_key(&key).await
1400                });
1401                handles.push(handle);
1402            }
1403
1404            // Wait for all operations to complete
1405            for handle in handles {
1406                // Some concurrent operations may fail due to race conditions, which is expected
1407                let _ = handle.await;
1408            }
1409
1410            // Verify final state is consistent
1411            let final_keys = storage_clone.load_keys().await.unwrap();
1412            assert!(final_keys.len() >= initial_keys.len());
1413
1414            // Verify all initial keys are still present
1415            for id in initial_keys.keys() {
1416                assert!(final_keys.contains_key(id));
1417            }
1418
1419            // Restore original environment variable or remove if it didn't exist
1420            match original_master_key {
1421                Some(key) => std::env::set_var("PULSEENGINE_MCP_MASTER_KEY", key),
1422                None => std::env::remove_var("PULSEENGINE_MCP_MASTER_KEY"),
1423            }
1424        }
1425    }
1426
1427    mod storage_factory_tests {
1428        use super::*;
1429        use crate::config::StorageConfig;
1430
1431        #[tokio::test]
1432        async fn test_create_memory_storage_backend() {
1433            let config = StorageConfig::Memory;
1434            let backend = create_storage_backend(&config).await.unwrap();
1435
1436            // Test basic operations
1437            let test_key = create_test_key("memory-factory-test", Role::Admin);
1438            backend.save_key(&test_key).await.unwrap();
1439
1440            let keys = backend.load_keys().await.unwrap();
1441            assert_eq!(keys.len(), 1);
1442            assert!(keys.contains_key(&test_key.id));
1443        }
1444
1445        #[tokio::test]
1446        async fn test_create_environment_storage_backend() {
1447            let var_name = "TEST_FACTORY_ENV_STORAGE";
1448            std::env::remove_var(var_name);
1449
1450            let config = StorageConfig::Environment {
1451                prefix: var_name.to_string(),
1452            };
1453            let backend = create_storage_backend(&config).await.unwrap();
1454
1455            // Test basic operations
1456            let test_key = create_test_key("env-factory-test", Role::Operator);
1457            backend.save_key(&test_key).await.unwrap();
1458
1459            let keys = backend.load_keys().await.unwrap();
1460            assert_eq!(keys.len(), 1);
1461            assert!(keys.contains_key(&test_key.id));
1462
1463            // Cleanup
1464            std::env::remove_var(var_name);
1465        }
1466
1467        #[tokio::test]
1468        async fn test_create_file_storage_backend() {
1469            let temp_dir = TempDir::new().unwrap();
1470            let storage_path = temp_dir.path().join("factory_test_keys.enc");
1471
1472            let config = StorageConfig::File {
1473                path: storage_path.clone(),
1474                file_permissions: 0o600,
1475                dir_permissions: 0o700,
1476                require_secure_filesystem: false,
1477                enable_filesystem_monitoring: false,
1478            };
1479            let backend = create_storage_backend(&config).await.unwrap();
1480
1481            // Test basic operations
1482            let test_key = create_test_key("file-factory-test", Role::Monitor);
1483            backend.save_key(&test_key).await.unwrap();
1484
1485            let keys = backend.load_keys().await.unwrap();
1486            assert_eq!(keys.len(), 1);
1487            assert!(keys.contains_key(&test_key.id));
1488
1489            // Verify file was created
1490            assert!(storage_path.exists());
1491        }
1492
1493        #[tokio::test]
1494        async fn test_create_file_storage_backend_with_nested_path() {
1495            let temp_dir = TempDir::new().unwrap();
1496            let storage_path = temp_dir.path().join("nested").join("dirs").join("keys.enc");
1497
1498            let config = StorageConfig::File {
1499                path: storage_path.clone(),
1500                file_permissions: 0o600,
1501                dir_permissions: 0o700,
1502                require_secure_filesystem: false,
1503                enable_filesystem_monitoring: false,
1504            };
1505            let backend = create_storage_backend(&config).await.unwrap();
1506
1507            // Test that nested directories were created
1508            assert!(storage_path.parent().unwrap().exists());
1509
1510            // Test basic operations
1511            let test_key = create_test_key(
1512                "nested-factory-test",
1513                Role::Device {
1514                    allowed_devices: vec!["device1".to_string()],
1515                },
1516            );
1517            backend.save_key(&test_key).await.unwrap();
1518
1519            let keys = backend.load_keys().await.unwrap();
1520            assert_eq!(keys.len(), 1);
1521            assert!(keys.contains_key(&test_key.id));
1522        }
1523    }
1524
1525    #[tokio::test]
1526    async fn test_storage_backend_trait_object() {
1527        // Test that we can use storage backends through trait objects
1528        let memory_storage: Box<dyn StorageBackend> = Box::new(MemoryStorage::new());
1529        let env_storage: Box<dyn StorageBackend> =
1530            Box::new(EnvironmentStorage::new("TEST_TRAIT_OBJECT".to_string()));
1531
1532        let storages: Vec<Box<dyn StorageBackend>> = vec![memory_storage, env_storage];
1533
1534        for (i, storage) in storages.into_iter().enumerate() {
1535            let test_key = create_test_key(
1536                &format!("trait-test-{}", i),
1537                Role::Custom {
1538                    permissions: vec!["test:read".to_string()],
1539                },
1540            );
1541
1542            storage.save_key(&test_key).await.unwrap();
1543            let keys = storage.load_keys().await.unwrap();
1544            assert_eq!(keys.len(), 1);
1545            assert!(keys.contains_key(&test_key.id));
1546        }
1547
1548        // Cleanup
1549        std::env::remove_var("TEST_TRAIT_OBJECT");
1550    }
1551
1552    #[tokio::test]
1553    async fn test_secure_api_key_conversion() {
1554        let original_key = create_test_key("conversion-test", Role::Admin);
1555        let secure_key = original_key.to_secure_storage();
1556        let restored_key = secure_key.to_api_key();
1557
1558        // Verify secure conversion
1559        assert_eq!(restored_key.id, original_key.id);
1560        assert_eq!(restored_key.name, original_key.name);
1561        assert_eq!(restored_key.role, original_key.role);
1562        assert_eq!(restored_key.created_at, original_key.created_at);
1563        assert_eq!(restored_key.expires_at, original_key.expires_at);
1564        assert_eq!(restored_key.ip_whitelist, original_key.ip_whitelist);
1565        assert_eq!(restored_key.active, original_key.active);
1566        assert_eq!(restored_key.usage_count, original_key.usage_count);
1567
1568        // Key should be redacted in restored version
1569        assert_eq!(restored_key.key, "***redacted***");
1570        assert_ne!(restored_key.key, original_key.key);
1571
1572        // Hash and salt should be preserved
1573        assert_eq!(restored_key.secret_hash, original_key.secret_hash);
1574        assert_eq!(restored_key.salt, original_key.salt);
1575    }
1576}