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