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 pub fn new() -> Result<Self> {
31 let registry = VaultRegistry::load()?;
32 Ok(Self {
33 registry,
34 open_vaults: HashMap::new(),
35 })
36 }
37
38 #[must_use]
40 pub fn list_vaults(&self) -> Vec<&VaultInfo> {
41 self.registry.list_vaults()
42 }
43
44 #[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 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 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 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 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 pub fn close_vault(&mut self, vault_id: &str) -> Result<()> {
215 self.open_vaults.remove(vault_id);
216 Ok(())
217 }
218
219 pub fn close_all_vaults(&mut self) {
221 self.open_vaults.clear();
222 }
223
224 pub fn delete_vault(&mut self, vault_id: &str, delete_file: bool) -> Result<()> {
247 self.open_vaults.remove(vault_id);
249
250 self.registry.delete_vault(vault_id, delete_file)?;
252 Ok(())
253 }
254
255 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 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 #[must_use]
331 pub fn is_vault_open(&self, vault_id: &str) -> bool {
332 self.open_vaults.contains_key(vault_id)
333 }
334
335 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 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 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 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 #[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 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 manager.open_vaults.insert("test_vault".to_string(), vault);
469 assert!(manager.is_vault_open("test_vault"));
470
471 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 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 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 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 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 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 assert!(manager.registry.vaults.contains_key(&vault_id));
570
571 let result = manager.open_vault(&vault_id, master_password);
573 assert!(result.is_ok());
574 assert!(manager.is_vault_open(&vault_id));
575
576 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 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 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 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_test_vault_file(&vault_path).expect("Failed to create test vault file");
684
685 let mut vault_info = create_test_vault_info("test_vault", "Test Vault", VaultCategory::Personal, &temp_dir);
687 vault_info.path = vault_path.clone(); manager.registry.vaults.insert("test_vault".to_string(), vault_info);
690
691 assert!(vault_path.exists());
692
693 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_test_vault_file(&source_vault_path).expect("Failed to create source vault file");
715
716 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, )
726 .expect("Failed to import vault");
727
728 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 let imported_vault_path = vault_info.path.clone();
738
739 let cleanup_result = manager.delete_vault(&vault_id, true); if cleanup_result.is_ok() {
744 assert!(!manager.registry.vaults.contains_key(&vault_id));
745 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 eprintln!("Warning: delete_vault failed, attempting manual cleanup: {cleanup_result:?}");
755
756 manager.registry.vaults.remove(&vault_id);
758
759 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 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_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, )
789 .expect("Failed to import vault");
790
791 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 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 assert_eq!(manager.list_vaults().len(), 2);
840
841 assert!(manager.open_vault(&vault1_id, master_password).is_ok());
843 assert!(manager.open_vault(&vault2_id, master_password).is_ok());
844
845 assert!(manager.is_vault_open(&vault1_id));
847 assert!(manager.is_vault_open(&vault2_id));
848
849 assert!(manager.switch_active_vault(&vault1_id).is_ok());
851 assert_eq!(manager.registry.active_vault_id, Some(vault1_id.clone()));
852
853 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 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 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 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 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 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 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 assert!(config.enabled);
966 assert_eq!(config.max_backups, 5);
967 assert!(temp_dir.path().join("backups") == config.backup_dir);
968 }
969}