Skip to main content

chamber_vault/
manager.rs

1use crate::registry::VaultInfo;
2use crate::{Vault, VaultCategory, VaultRegistry};
3use color_eyre::Result;
4use color_eyre::eyre::eyre;
5use std::collections::HashMap;
6use std::path::PathBuf;
7
8#[derive(Debug)]
9pub struct VaultManager {
10    pub registry: VaultRegistry,
11    pub open_vaults: HashMap<String, Vault>,
12}
13
14impl VaultManager {
15    /// Creates a new instance of the struct.
16    ///
17    /// # Returns
18    ///
19    /// Returns a `Result` containing a newly initialized instance of `Self`
20    /// if the operation is successful. Otherwise, it returns an error.
21    ///
22    /// # Procedure
23    ///
24    /// - Loads the vault registry using the `VaultRegistry::load()` method.
25    /// - Initializes the `open_vaults` field as an empty `HashMap`.
26    ///
27    /// # Errors
28    ///
29    /// This function will return an error if `VaultRegistry::load()` fails.
30    pub fn new() -> Result<Self> {
31        let registry = VaultRegistry::load()?;
32        Ok(Self {
33            registry,
34            open_vaults: HashMap::new(),
35        })
36    }
37
38    /// List all available vaults
39    #[must_use]
40    pub fn list_vaults(&self) -> Vec<&VaultInfo> {
41        self.registry.list_vaults()
42    }
43
44    /// Creates a new vault with the specified parameters and initializes it with the given master password.
45    ///
46    /// # Arguments
47    ///
48    /// * `name` - A `String` representing the name of the vault. This will serve as the identifying label for the vault.
49    /// * `path` - An optional `PathBuf` specifying the file system location where the vault will be stored.
50    ///   If `None` is provided, a default location will be determined by the system.
51    /// * `category` - A `VaultCategory` enum indicating the category or use case associated with this vault (e.g., personal, business).
52    /// * `description` - An optional `String` for providing additional details or metadata about the vault.
53    /// * `master_password` - A reference to a `str` that will be used to secure the freshly created vault. This master password is required for future vault access.
54    ///
55    /// # Returns
56    ///
57    /// Returns a `Result<String>`:
58    /// * `Ok(String)` - The unique identifier (vault ID) of the newly created vault on successful creation and initialization.
59    /// * `Err` - An error is returned if vault creation, retrieval, or initialization fails.
60    ///
61    /// # Errors
62    ///
63    /// This function may return an error in the following scenarios:
64    /// * If the vault could not be successfully created in the registry.
65    /// * If the vault information retrieval process encounters an issue.
66    /// * If the vault initialization process fails (e.g., due to encryption setup or invalid master password).
67    ///
68    /// # Panics
69    ///
70    /// # Notes
71    ///
72    /// * Ensure that `master_password` is secure and not easily guessable, as it safeguards the integrity of the vault's contents.
73    /// * It is the caller's responsibility to manage and securely store the returned vault ID for future reference.
74    ///
75    /// # See Also
76    ///
77    /// * `self.registry.create_vault` - Handles the creation of the vault in the internal registry.
78    /// * `Vault::open_or_create` - Opens an existing vault or creates a new one if it does not exist.
79    /// * `Vault::initialize` - Prepares the vault for use by setting up encryption and other necessary configurations.
80    #[allow(clippy::panic)]
81    pub fn create_vault(
82        &mut self,
83        name: String,
84        path: Option<PathBuf>,
85        category: VaultCategory,
86        description: Option<String>,
87        master_password: &str,
88    ) -> Result<String> {
89        let vault_id = self.registry.create_vault(name, path, category, description)?;
90
91        // Initialize the new vault
92        let vault_info = self
93            .registry
94            .get_vault(&vault_id)
95            .ok_or_else(|| eyre!("Vault with id {} not found", vault_id))?;
96        let mut vault = Vault::open_or_create(Some(&vault_info.path))?;
97        vault.initialize(master_password)?;
98
99        Ok(vault_id)
100    }
101
102    /// Opens a vault by its identifier and unlocks it using the provided master password.
103    ///
104    /// # Parameters
105    /// - `vault_id`: A string slice representing the unique identifier of the vault to be opened.
106    /// - `master_password`: A string slice representing the master password used to unlock the vault.
107    ///
108    /// # Returns
109    /// - `Ok(())`: If the vault is successfully opened and unlocked.
110    /// - `Err`: If the vault does not exist or an error occurs during any operation (e.g., unlocking or reading the vault).
111    ///
112    /// # Errors
113    /// - Returns an error if the vault with the given `vault_id` is not found in the registry.
114    /// - Returns an error if unlocking the vault with the `master_password` fails.
115    /// - Returns an error if there is an issue opening or creating the vault.
116    ///
117    /// # Side Effects
118    /// - Adds the opened vault to the `open_vaults` collection, which keeps track of currently open vaults.
119    pub fn open_vault(&mut self, vault_id: &str, master_password: &str) -> Result<()> {
120        let vault_info = self
121            .registry
122            .get_vault(vault_id)
123            .ok_or_else(|| eyre!("Vault '{}' not found", vault_id))?;
124
125        let mut vault = Vault::open_or_create(Some(&vault_info.path))?;
126        vault.unlock(master_password)?;
127
128        self.open_vaults.insert(vault_id.to_string(), vault);
129        Ok(())
130    }
131
132    /// Switches the active vault to the specified vault ID.
133    ///
134    /// This function checks if the given `vault_id` exists in the registry's list of vaults.
135    /// If the vault exists, it sets the specified vault as the active vault. If the vault
136    /// does not exist, an error is returned.
137    ///
138    /// # Arguments
139    ///
140    /// * `vault_id` - A string slice that holds the ID of the vault to be set as active.
141    ///
142    /// # Returns
143    ///
144    /// * `Ok(())` if the active vault is successfully switched.
145    /// * `Err` if the specified `vault_id` is not found in the registry's list of vaults or
146    ///   if there is an error setting the active vault.
147    ///
148    /// # Errors
149    ///
150    /// This function returns an error under the following conditions:
151    /// - The specified `vault_id` is not found in the registry.
152    /// - There is an error while calling `set_active_vault`.
153    pub fn switch_active_vault(&mut self, vault_id: &str) -> Result<()> {
154        if !self.registry.vaults.contains_key(vault_id) {
155            return Err(eyre!("Vault '{}' not found", vault_id));
156        }
157
158        self.registry.set_active_vault(vault_id)?;
159        Ok(())
160    }
161
162    /// Retrieves a mutable reference to the currently active `Vault`.
163    ///
164    /// This function checks if there is an active vault ID registered in `self.registry`.
165    /// If no active vault ID is set, it returns an error indicating that there is no active vault.
166    /// If an active vault ID is set but the corresponding vault is not unlocked (i.e., not present
167    /// in `self.open_vaults`), it returns an error indicating that the vault is not unlocked.
168    /// On success, it returns a mutable reference to the active `Vault`.
169    ///
170    /// # Returns
171    ///
172    /// * `Ok(&mut Vault)` - A mutable reference to the active `Vault` if one exists and is unlocked.
173    /// * `Err(anyhow::Error)` - An error if there is no active vault ID set or the vault is not unlocked.
174    ///
175    /// # Errors
176    ///
177    /// * Returns an error with the message `"No active vault"` if `self.registry.active_vault_id` is `None`.
178    /// * Returns an error with the message `"Active vault '<ID>' is not unlocked"` if the active vault is not
179    ///   found in `self.open_vaults`.
180    ///
181    /// # Panics
182    ///
183    /// * This function will panic if `unwrap()` is called and the vault corresponding
184    ///   to the active ID is unexpectedly missing from `self.open_vaults`. However, this condition
185    ///   should not occur due to the prior `contains_key` check.
186    pub fn get_active_vault(&mut self) -> Result<&mut Vault> {
187        let active_id = self
188            .registry
189            .active_vault_id
190            .as_ref()
191            .ok_or_else(|| eyre!("No active vault"))?;
192
193        if !self.open_vaults.contains_key(active_id) {
194            return Err(eyre!("Active vault '{}' is not unlocked", active_id));
195        }
196        let message = format!("Active vault '{active_id}' not found in open vaults.");
197        Ok(self.open_vaults.get_mut(active_id).expect(&message))
198    }
199
200    /// Closes the vault associated with the given vault ID.
201    ///
202    /// # Parameters
203    /// - `vault_id`: A string slice that represents the unique identifier of the vault to be closed.
204    ///
205    /// # Returns
206    /// - `Ok(())` if the operation was successful.
207    /// - `Err(e)` if an error occurs (specific error type depends on the implementation of `Result`).
208    ///
209    /// # Errors
210    ///
211    /// # Behavior
212    /// - This function removes the specified `vault_id` from the `open_vaults` collection.
213    /// - After the operation, the vault will no longer be considered open.
214    pub fn close_vault(&mut self, vault_id: &str) -> Result<()> {
215        self.open_vaults.remove(vault_id);
216        Ok(())
217    }
218
219    /// Close all vaults
220    pub fn close_all_vaults(&mut self) {
221        self.open_vaults.clear();
222    }
223
224    /// Deletes a specified vault from the system.
225    ///
226    /// This method performs the following operations:
227    /// 1. Closes the vault if it is currently open.
228    /// 2. Removes the vault from the registry.
229    /// 3. Optionally deletes the vault's associated files from the storage if `delete_file` is set to `true`.
230    ///
231    /// # Arguments
232    ///
233    /// * `vault_id` - A string slice that represents the unique identifier of the vault to be deleted.
234    /// * `delete_file` - A boolean that, when `true`, ensures the associated vault files are also deleted from the system.
235    ///
236    /// # Returns
237    ///
238    /// * `Ok(())` on successful deletion of the vault.
239    /// * `Err` if there is an issue with deleting the vault from the registry or associated files.
240    ///
241    /// # Errors
242    ///
243    /// Returns an error in the following cases:
244    /// * If there is a failure while removing the vault from the registry.
245    /// * If deleting the associated files fails when `delete_file` is `true`.
246    pub fn delete_vault(&mut self, vault_id: &str, delete_file: bool) -> Result<()> {
247        // Close the vault if it's open
248        self.open_vaults.remove(vault_id);
249
250        // Remove from the registry
251        self.registry.delete_vault(vault_id, delete_file)?;
252        Ok(())
253    }
254
255    /// Imports a vault into the registry.
256    ///
257    /// This function allows you to import a vault by specifying the path to the vault file,
258    /// a name for the vault, a category, and whether you want to copy the file during the import process.
259    ///
260    /// # Arguments
261    ///
262    /// * `vault_file` - A reference to the file path of the vault to be imported.
263    /// * `name` - A `String` representing the name to be assigned to the imported vault.
264    /// * `category` - A `VaultCategory` specifying the category of the vault.
265    /// * `copy_file` - A `bool` indicating whether the vault file should be copied during the import.
266    ///   - `true`: The file will be copied to the registry.
267    ///   - `false`: The original file path will be used without copying.
268    ///
269    /// # Returns
270    ///
271    /// Returns a `Result` containing:
272    /// * `Ok(String)`: A success message or identifier for the imported vault.
273    /// * `Err(_)`: An error if the import fails.
274    ///
275    /// # Errors
276    ///
277    /// This function will return an error if:
278    /// * The file at the given path cannot be accessed.
279    /// * The file cannot be imported due to issues such as invalid format or permissions.
280    /// * The registry encounters an internal error during the import process.
281    pub fn import_vault(
282        &mut self,
283        vault_file: &std::path::Path,
284        name: String,
285        category: VaultCategory,
286        copy_file: bool,
287    ) -> Result<String> {
288        self.registry.import_vault(vault_file, name, category, copy_file)
289    }
290
291    ///
292    /// Updates the information of a vault identified by its `vault_id`.
293    ///
294    /// This method allows updating various properties of a vault stored in the registry,
295    /// such as its name, description, category, and favorite status. Each property can
296    /// be updated optionally by passing a `Some` value, or left unchanged by passing `None`.
297    ///
298    /// # Parameters
299    /// - `vault_id`: A string slice that uniquely identifies the vault to be updated.
300    /// - `name`: An `Option<String>` representing the new name of the vault. Pass `Some(new_name)` to update
301    ///   the name or `None` to leave it unchanged.
302    /// - `description`: An `Option<String>` representing the new description for the vault. Pass `Some(new_description)`
303    ///   to update the description or `None` to leave it unchanged.
304    /// - `category`: An `Option<VaultCategory>` specifying a new category for the vault. Pass `Some(new_category)`
305    ///   to update the category or `None` to leave it unchanged.
306    /// - `is_favorite`: An `Option<bool>` indicating whether the vault is a favorite. Pass `Some(true)` to mark it
307    ///   as a favorite, `Some(false)` to remove it as a favorite, or `None` to leave this setting unchanged.
308    ///
309    /// # Returns
310    /// - `Result<()>`: Returns an `Ok(())` on success, indicating that the vault's information was successfully updated.
311    ///   Returns an error if `vault_id` does not exist or if there is an issue with updating the registry.
312    ///
313    /// # Errors
314    /// This method will return an error in the following cases:
315    /// - The registry fails to find the vault with the given `vault_id`.
316    /// - An unexpected error occurs while trying to update the vault's information.
317    pub fn update_vault_info(
318        &mut self,
319        vault_id: &str,
320        name: Option<String>,
321        description: Option<String>,
322        category: Option<VaultCategory>,
323        is_favorite: Option<bool>,
324    ) -> Result<()> {
325        self.registry
326            .update_vault(vault_id, name, description, category, is_favorite)
327    }
328
329    /// Check if a vault is currently open/unlocked
330    #[must_use]
331    pub fn is_vault_open(&self, vault_id: &str) -> bool {
332        self.open_vaults.contains_key(vault_id)
333    }
334
335    /// Get vault by ID (must be open)
336    pub fn get_vault(&mut self, vault_id: &str) -> Option<&mut Vault> {
337        self.open_vaults.get_mut(vault_id)
338    }
339}
340
341impl Default for VaultManager {
342    fn default() -> Self {
343        Self::new().expect("Failed to initialize VaultManager")
344    }
345}
346
347#[cfg(test)]
348mod tests {
349    #![allow(clippy::unwrap_used)]
350    #![allow(clippy::panic)]
351    #![allow(clippy::absurd_extreme_comparisons)]
352    #![allow(unused_comparisons)]
353
354    use super::*;
355    use crate::registry::{VaultCategory, VaultInfo, VaultRegistry};
356    use crate::{BackupConfig, Item, ItemKind};
357    use color_eyre::Result;
358    use std::collections::HashMap;
359    use std::fs;
360    use tempfile::TempDir;
361    use time::OffsetDateTime;
362
363    // Test helper functions using Solution 1: Isolated environment
364    fn create_isolated_vault_manager() -> (VaultManager, TempDir) {
365        let temp_dir = TempDir::new().expect("Failed to create temp directory");
366        let registry_path = temp_dir.path().join("isolated_registry.json");
367        let vaults_dir = temp_dir.path().join("vaults");
368        fs::create_dir_all(&vaults_dir).expect("Failed to create vaults directory");
369
370        // Create an isolated registry
371        let registry = VaultRegistry {
372            vaults: HashMap::new(),
373            active_vault_id: None,
374            registry_path,
375        };
376
377        let manager = VaultManager {
378            registry,
379            open_vaults: HashMap::new(),
380        };
381
382        (manager, temp_dir)
383    }
384
385    fn create_isolated_vault() -> (Vault, TempDir) {
386        let temp_dir = TempDir::new().expect("Failed to create temp directory");
387        let vault_path = temp_dir.path().join("test_vault.db");
388
389        let vault = Vault::open_or_create(Some(&vault_path)).expect("Failed to create isolated vault");
390
391        (vault, temp_dir)
392    }
393
394    fn create_test_vault_info(id: &str, name: &str, category: VaultCategory, temp_dir: &TempDir) -> VaultInfo {
395        let vault_path = temp_dir.path().join(format!("{name}.db"));
396
397        VaultInfo {
398            id: id.to_string(),
399            name: name.to_string(),
400            path: vault_path,
401            created_at: OffsetDateTime::now_utc(),
402            last_accessed: OffsetDateTime::now_utc(),
403            description: Some(format!("Test vault: {name}")),
404            category,
405            is_active: false,
406            is_favorite: false,
407        }
408    }
409
410    fn create_test_vault_file(path: &std::path::Path) -> Result<()> {
411        // Create a minimal vault file for testing
412        let mut vault = Vault::open_or_create(Some(path))?;
413        vault.initialize("test_master_password")?;
414        Ok(())
415    }
416
417    fn create_test_item(id: u64, name: &str) -> Item {
418        Item {
419            id,
420            name: name.to_string(),
421            kind: ItemKind::Password,
422            value: "test_value".to_string(),
423            created_at: OffsetDateTime::now_utc(),
424            updated_at: OffsetDateTime::now_utc(),
425        }
426    }
427
428    // VaultManager tests using isolated environment
429    #[test]
430    fn test_isolated_vault_manager_new_creates_empty_open_vaults() {
431        let (manager, _temp_dir) = create_isolated_vault_manager();
432        assert!(manager.open_vaults.is_empty());
433        assert!(manager.registry.vaults.is_empty());
434        assert!(manager.registry.active_vault_id.is_none());
435    }
436
437    #[test]
438    fn test_isolated_list_vaults_delegates_to_registry() {
439        let (mut manager, temp_dir) = create_isolated_vault_manager();
440
441        // Add test vaults to the isolated registry
442        let vault1 = create_test_vault_info("vault1", "Personal Vault", VaultCategory::Personal, &temp_dir);
443        let vault2 = create_test_vault_info("vault2", "Work Vault", VaultCategory::Work, &temp_dir);
444
445        manager.registry.vaults.insert("vault1".to_string(), vault1);
446        manager.registry.vaults.insert("vault2".to_string(), vault2);
447
448        let vaults = manager.list_vaults();
449        assert_eq!(vaults.len(), 2);
450
451        let vault_names: Vec<&str> = vaults.iter().map(|v| v.name.as_str()).collect();
452        assert!(vault_names.contains(&"Personal Vault"));
453        assert!(vault_names.contains(&"Work Vault"));
454    }
455
456    #[test]
457    fn test_isolated_is_vault_open_returns_false_for_nonexistent_vault() {
458        let (manager, _temp_dir) = create_isolated_vault_manager();
459        assert!(!manager.is_vault_open("nonexistent_vault"));
460    }
461
462    #[test]
463    fn test_isolated_close_vault_removes_from_open_vaults() {
464        let (mut manager, _temp_dir) = create_isolated_vault_manager();
465        let (vault, _vault_temp_dir) = create_isolated_vault();
466
467        // Add vault to open_vaults
468        manager.open_vaults.insert("test_vault".to_string(), vault);
469        assert!(manager.is_vault_open("test_vault"));
470
471        // Close the vault
472        let result = manager.close_vault("test_vault");
473        assert!(result.is_ok());
474        assert!(!manager.is_vault_open("test_vault"));
475    }
476
477    #[test]
478    fn test_isolated_close_vault_succeeds_even_for_nonexistent_vault() {
479        let (mut manager, _temp_dir) = create_isolated_vault_manager();
480
481        // Should succeed even if vault doesn't exist
482        let result = manager.close_vault("nonexistent_vault");
483        assert!(result.is_ok());
484    }
485
486    #[test]
487    fn test_isolated_close_all_vaults_clears_open_vaults() {
488        let (mut manager, _temp_dir) = create_isolated_vault_manager();
489        let (vault1, _temp1) = create_isolated_vault();
490        let (vault2, _temp2) = create_isolated_vault();
491
492        // Add vaults to open_vaults
493        manager.open_vaults.insert("vault1".to_string(), vault1);
494        manager.open_vaults.insert("vault2".to_string(), vault2);
495
496        assert_eq!(manager.open_vaults.len(), 2);
497
498        manager.close_all_vaults();
499        assert!(manager.open_vaults.is_empty());
500    }
501
502    #[test]
503    fn test_isolated_get_vault_returns_none_for_closed_vault() {
504        let (mut manager, _temp_dir) = create_isolated_vault_manager();
505
506        let result = manager.get_vault("nonexistent_vault");
507        assert!(result.is_none());
508    }
509
510    #[test]
511    fn test_isolated_get_vault_returns_some_for_open_vault() {
512        let (mut manager, _temp_dir) = create_isolated_vault_manager();
513        let (vault, _vault_temp_dir) = create_isolated_vault();
514
515        // Add vault to open_vaults
516        manager.open_vaults.insert("test_vault".to_string(), vault);
517
518        let result = manager.get_vault("test_vault");
519        assert!(result.is_some());
520    }
521
522    #[test]
523    fn test_isolated_get_active_vault_fails_when_no_active_vault() {
524        let (mut manager, _temp_dir) = create_isolated_vault_manager();
525
526        let result = manager.get_active_vault();
527        assert!(result.is_err());
528        assert!(result.unwrap_err().to_string().contains("No active vault"));
529    }
530
531    #[test]
532    fn test_isolated_get_active_vault_fails_when_active_vault_not_open() {
533        let (mut manager, temp_dir) = create_isolated_vault_manager();
534        let vault_info = create_test_vault_info("test_vault", "Test Vault", VaultCategory::Personal, &temp_dir);
535
536        // Add vault to registry and set as active, but don't open it
537        manager.registry.vaults.insert("test_vault".to_string(), vault_info);
538        manager.registry.active_vault_id = Some("test_vault".to_string());
539
540        let result = manager.get_active_vault();
541        assert!(result.is_err());
542    }
543
544    #[test]
545    fn test_isolated_switch_active_vault_fails_for_nonexistent_vault() {
546        let (mut manager, _temp_dir) = create_isolated_vault_manager();
547
548        let result = manager.switch_active_vault("nonexistent_vault");
549        assert!(result.is_err());
550    }
551
552    #[test]
553    fn test_isolated_vault_lifecycle_create_open_close() {
554        let (mut manager, temp_dir) = create_isolated_vault_manager();
555        let master_password = "test_master_password";
556
557        // Create vault
558        let vault_id = manager
559            .create_vault(
560                "Test Vault".to_string(),
561                Some(temp_dir.path().join("test_vault.db")),
562                VaultCategory::Personal,
563                Some("Test description".to_string()),
564                master_password,
565            )
566            .expect("Failed to create vault");
567
568        // Verify vault was created
569        assert!(manager.registry.vaults.contains_key(&vault_id));
570
571        // Open vault
572        let result = manager.open_vault(&vault_id, master_password);
573        assert!(result.is_ok());
574        assert!(manager.is_vault_open(&vault_id));
575
576        // Close vault
577        let result = manager.close_vault(&vault_id);
578        assert!(result.is_ok());
579        assert!(!manager.is_vault_open(&vault_id));
580    }
581
582    #[test]
583    fn test_isolated_vault_creation_with_custom_path() {
584        let (mut manager, temp_dir) = create_isolated_vault_manager();
585        let custom_path = temp_dir.path().join("custom").join("path").join("vault.db");
586        let master_password = "test_password";
587
588        let vault_id = manager
589            .create_vault(
590                "Custom Path Vault".to_string(),
591                Some(custom_path.clone()),
592                VaultCategory::Work,
593                None,
594                master_password,
595            )
596            .expect("Failed to create vault with custom path");
597
598        let vault_info = manager.registry.vaults.get(&vault_id).unwrap();
599        assert_eq!(vault_info.path, custom_path);
600        assert_eq!(vault_info.category, VaultCategory::Work);
601    }
602
603    #[test]
604    fn test_isolated_open_vault_with_wrong_password() {
605        let (mut manager, temp_dir) = create_isolated_vault_manager();
606        let correct_password = "correct_password";
607        let wrong_password = "wrong_password";
608
609        // Create vault with correct password
610        let vault_id = manager
611            .create_vault(
612                "Test Vault".to_string(),
613                Some(temp_dir.path().join("test_vault.db")),
614                VaultCategory::Personal,
615                None,
616                correct_password,
617            )
618            .expect("Failed to create vault");
619
620        // Try to open with wrong password
621        let result = manager.open_vault(&vault_id, wrong_password);
622        assert!(result.is_err());
623        assert!(!manager.is_vault_open(&vault_id));
624    }
625
626    #[test]
627    fn test_isolated_open_nonexistent_vault() {
628        let (mut manager, _temp_dir) = create_isolated_vault_manager();
629
630        let result = manager.open_vault("nonexistent_vault", "password");
631        assert!(result.is_err());
632    }
633
634    #[test]
635    fn test_isolated_update_vault_info() {
636        let (mut manager, temp_dir) = create_isolated_vault_manager();
637        let vault_info = create_test_vault_info("test_vault", "Original Name", VaultCategory::Personal, &temp_dir);
638        manager.registry.vaults.insert("test_vault".to_string(), vault_info);
639
640        let result = manager.update_vault_info(
641            "test_vault",
642            Some("Updated Name".to_string()),
643            Some("Updated description".to_string()),
644            Some(VaultCategory::Work),
645            Some(true),
646        );
647
648        assert!(result.is_ok());
649        let updated_vault = manager.registry.vaults.get("test_vault").unwrap();
650        assert_eq!(updated_vault.name, "Updated Name");
651        assert_eq!(updated_vault.description, Some("Updated description".to_string()));
652        assert_eq!(updated_vault.category, VaultCategory::Work);
653        assert!(updated_vault.is_favorite);
654    }
655
656    #[test]
657    fn test_isolated_update_nonexistent_vault() {
658        let (mut manager, _temp_dir) = create_isolated_vault_manager();
659
660        let result = manager.update_vault_info("nonexistent_vault", Some("New Name".to_string()), None, None, None);
661
662        assert!(result.is_err());
663    }
664
665    #[test]
666    fn test_isolated_delete_vault_without_file() {
667        let (mut manager, temp_dir) = create_isolated_vault_manager();
668        let vault_info = create_test_vault_info("test_vault", "Test Vault", VaultCategory::Personal, &temp_dir);
669        manager.registry.vaults.insert("test_vault".to_string(), vault_info);
670
671        // Delete vault but keep file
672        let result = manager.delete_vault("test_vault", false);
673        assert!(result.is_ok());
674        assert!(!manager.registry.vaults.contains_key("test_vault"));
675    }
676
677    #[test]
678    fn test_isolated_delete_vault_with_file() {
679        let (mut manager, temp_dir) = create_isolated_vault_manager();
680        let vault_path = temp_dir.path().join("test_vault.db");
681
682        // Create an actual vault file
683        create_test_vault_file(&vault_path).expect("Failed to create test vault file");
684
685        // Create vault info with the SAME path as the file we created
686        let mut vault_info = create_test_vault_info("test_vault", "Test Vault", VaultCategory::Personal, &temp_dir);
687        vault_info.path = vault_path.clone(); // Override the path to match our test file
688
689        manager.registry.vaults.insert("test_vault".to_string(), vault_info);
690
691        assert!(vault_path.exists());
692
693        // Delete vault and file
694        let result = manager.delete_vault("test_vault", true);
695        assert!(result.is_ok());
696        assert!(!manager.registry.vaults.contains_key("test_vault"));
697        assert!(!vault_path.exists());
698    }
699
700    #[test]
701    fn test_isolated_delete_nonexistent_vault() {
702        let (mut manager, _temp_dir) = create_isolated_vault_manager();
703
704        let result = manager.delete_vault("nonexistent_vault", false);
705        assert!(result.is_err());
706    }
707
708    #[test]
709    fn test_isolated_import_vault() {
710        let (mut manager, temp_dir) = create_isolated_vault_manager();
711        let source_vault_path = temp_dir.path().join("source_vault.db");
712
713        // Create a source vault file
714        create_test_vault_file(&source_vault_path).expect("Failed to create source vault file");
715
716        // Store the initial vault count to detect new entries
717        let initial_vault_count = manager.registry.vaults.len();
718
719        let vault_id = manager
720            .import_vault(
721                &source_vault_path,
722                "Imported Vault".to_string(),
723                VaultCategory::Archive,
724                true, // copy file
725            )
726            .expect("Failed to import vault");
727
728        // Verify vault was imported
729        assert!(manager.registry.vaults.contains_key(&vault_id));
730        assert_eq!(manager.registry.vaults.len(), initial_vault_count + 1);
731
732        let vault_info = manager.registry.vaults.get(&vault_id).unwrap();
733        assert_eq!(vault_info.name, "Imported Vault");
734        assert_eq!(vault_info.category, VaultCategory::Archive);
735
736        // Store the imported vault file path for cleanup
737        let imported_vault_path = vault_info.path.clone();
738
739        // CLEANUP: Delete the vault entry and any files that may have been created outside temp_dir
740        let cleanup_result = manager.delete_vault(&vault_id, true); // delete_file = true
741
742        // Verify cleanup was successful
743        if cleanup_result.is_ok() {
744            assert!(!manager.registry.vaults.contains_key(&vault_id));
745            // If the file was created outside temp_dir, it should now be deleted
746            if !imported_vault_path.starts_with(temp_dir.path()) {
747                assert!(
748                    !imported_vault_path.exists(),
749                    "Production file should be cleaned up: {imported_vault_path:?}"
750                );
751            }
752        } else {
753            // If delete_vault failed, try manual cleanup
754            eprintln!("Warning: delete_vault failed, attempting manual cleanup: {cleanup_result:?}");
755
756            // Remove from the registry manually
757            manager.registry.vaults.remove(&vault_id);
758
759            // Try to delete the file manually if it's outside our temp directory
760            if !imported_vault_path.starts_with(temp_dir.path()) && imported_vault_path.exists() {
761                if let Err(e) = std::fs::remove_file(&imported_vault_path) {
762                    eprintln!("Warning: Failed to clean up test file {imported_vault_path:?}: {e}");
763                }
764            }
765        }
766
767        // Final verification that we cleaned up properly
768        assert!(
769            !manager.registry.vaults.contains_key(&vault_id),
770            "Vault should be cleaned up from registry"
771        );
772    }
773
774    #[test]
775    fn test_isolated_import_vault_without_copy() {
776        let (mut manager, temp_dir) = create_isolated_vault_manager();
777        let source_vault_path = temp_dir.path().join("source_vault.db");
778
779        // Create source vault file
780        create_test_vault_file(&source_vault_path).expect("Failed to create source vault file");
781
782        let vault_id = manager
783            .import_vault(
784                &source_vault_path,
785                "Linked Vault".to_string(),
786                VaultCategory::Project,
787                false, // don't copy file, just reference
788            )
789            .expect("Failed to import vault");
790
791        // Verify vault was imported
792        assert!(manager.registry.vaults.contains_key(&vault_id));
793        let vault_info = manager.registry.vaults.get(&vault_id).unwrap();
794        assert_eq!(vault_info.path, source_vault_path);
795    }
796
797    #[test]
798    fn test_isolated_import_nonexistent_vault() {
799        let (mut manager, temp_dir) = create_isolated_vault_manager();
800        let nonexistent_path = temp_dir.path().join("nonexistent.db");
801
802        let result = manager.import_vault(
803            &nonexistent_path,
804            "Nonexistent Vault".to_string(),
805            VaultCategory::Personal,
806            true,
807        );
808
809        assert!(result.is_err());
810    }
811
812    #[test]
813    fn test_isolated_multiple_vaults_management() {
814        let (mut manager, temp_dir) = create_isolated_vault_manager();
815        let master_password = "test_password";
816
817        // Create multiple vaults
818        let vault1_id = manager
819            .create_vault(
820                "Personal Vault".to_string(),
821                Some(temp_dir.path().join("personal.db")),
822                VaultCategory::Personal,
823                None,
824                master_password,
825            )
826            .expect("Failed to create personal vault");
827
828        let vault2_id = manager
829            .create_vault(
830                "Work Vault".to_string(),
831                Some(temp_dir.path().join("work.db")),
832                VaultCategory::Work,
833                None,
834                master_password,
835            )
836            .expect("Failed to create work vault");
837
838        // Verify both vaults exist
839        assert_eq!(manager.list_vaults().len(), 2);
840
841        // Open both vaults
842        assert!(manager.open_vault(&vault1_id, master_password).is_ok());
843        assert!(manager.open_vault(&vault2_id, master_password).is_ok());
844
845        // Verify both are open
846        assert!(manager.is_vault_open(&vault1_id));
847        assert!(manager.is_vault_open(&vault2_id));
848
849        // Switch active vault
850        assert!(manager.switch_active_vault(&vault1_id).is_ok());
851        assert_eq!(manager.registry.active_vault_id, Some(vault1_id.clone()));
852
853        // Close all vaults
854        manager.close_all_vaults();
855        assert!(!manager.is_vault_open(&vault1_id));
856        assert!(!manager.is_vault_open(&vault2_id));
857    }
858
859    #[test]
860    fn test_isolated_active_vault_workflow() {
861        let (mut manager, temp_dir) = create_isolated_vault_manager();
862        let master_password = "test_password";
863
864        // Create and open vault
865        let vault_id = manager
866            .create_vault(
867                "Active Vault".to_string(),
868                Some(temp_dir.path().join("active.db")),
869                VaultCategory::Personal,
870                None,
871                master_password,
872            )
873            .expect("Failed to create vault");
874
875        assert!(manager.open_vault(&vault_id, master_password).is_ok());
876        assert!(manager.switch_active_vault(&vault_id).is_ok());
877
878        // Should be able to get active vault
879        let active_vault_result = manager.get_active_vault();
880        assert!(active_vault_result.is_ok());
881    }
882
883    #[test]
884    fn test_isolated_vault_categories() {
885        let (mut manager, temp_dir) = create_isolated_vault_manager();
886        let master_password = "test_password";
887
888        // Test different categories
889        let categories = [
890            VaultCategory::Personal,
891            VaultCategory::Work,
892            VaultCategory::Team,
893            VaultCategory::Project,
894            VaultCategory::Testing,
895            VaultCategory::Archive,
896            VaultCategory::Custom("Custom Category".to_string()),
897        ];
898
899        for (i, category) in categories.iter().enumerate() {
900            let vault_id = manager
901                .create_vault(
902                    format!("Vault {i}"),
903                    Some(temp_dir.path().join(format!("vault_{i}.db"))),
904                    category.clone(),
905                    None,
906                    master_password,
907                )
908                .expect("Failed to create vault");
909
910            let vault_info = manager.registry.vaults.get(&vault_id).unwrap();
911            assert_eq!(vault_info.category, *category);
912        }
913
914        assert_eq!(manager.list_vaults().len(), categories.len());
915    }
916
917    #[test]
918    fn test_isolated_error_handling_consistency() {
919        let (mut manager, _temp_dir) = create_isolated_vault_manager();
920
921        // All operations on nonexistent vaults should return errors
922        assert!(manager.open_vault("nonexistent", "password").is_err());
923        assert!(manager.switch_active_vault("nonexistent").is_err());
924        assert!(
925            manager
926                .update_vault_info("nonexistent", None, None, None, None)
927                .is_err()
928        );
929        assert!(manager.delete_vault("nonexistent", false).is_err());
930
931        // Operations that should succeed even with nonexistent vaults
932        assert!(manager.close_vault("nonexistent").is_ok());
933        assert!(!manager.is_vault_open("nonexistent"));
934        assert!(manager.get_vault("nonexistent").is_none());
935    }
936
937    // BackupManager tests can also be isolated
938    fn create_test_config(temp_dir: &TempDir) -> BackupConfig {
939        BackupConfig {
940            enabled: true,
941            backup_dir: temp_dir.path().join("backups"),
942            interval_hours: 24,
943            max_backups: 5,
944            format: String::from("Json"),
945            compress: false,
946            verify_after_backup: false,
947        }
948    }
949
950    #[test]
951    fn test_isolated_generic_backup_manager_creation() {
952        let temp_dir = TempDir::new().unwrap();
953        let config = create_test_config(&temp_dir);
954        let _items = [create_test_item(1, "test_item")];
955
956        // Create a mock vault for testing (you'd need to implement this)
957        // let vault = MockVault::new(items);
958        // let manager = BackupManager::new(vault, config.clone());
959        //
960        // assert_eq!(manager.config.enabled, config.enabled);
961        // assert_eq!(manager.config.format, config.format);
962        // assert_eq!(manager.config.max_backups, config.max_backups);
963
964        // For now, just test that config creation works
965        assert!(config.enabled);
966        assert_eq!(config.max_backups, 5);
967        assert!(temp_dir.path().join("backups") == config.backup_dir);
968    }
969}