1use crate::EnvxError;
2use chrono::{DateTime, Utc};
3use color_eyre::Result;
4use indexmap::IndexMap;
5use regex::Regex;
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9pub enum EnvVarSource {
10 System,
11 User,
12 Process,
13 Shell,
14 Application(String),
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct EnvVar {
19 pub name: String,
20 pub value: String,
21 pub source: EnvVarSource,
22 pub modified: DateTime<Utc>,
23 pub original_value: Option<String>,
24}
25
26pub struct EnvVarManager {
27 pub vars: IndexMap<String, EnvVar>,
28 pub history: Vec<crate::history::HistoryEntry>,
29}
30
31impl Default for EnvVarManager {
32 fn default() -> Self {
33 Self {
34 vars: IndexMap::new(),
35 history: Vec::new(),
36 }
37 }
38}
39
40impl EnvVarManager {
41 #[must_use]
42 pub fn new() -> Self {
43 Self::default()
44 }
45
46 pub fn load_all(&mut self) -> Result<()> {
58 for (key, value) in std::env::vars() {
60 self.vars.insert(
61 key.clone(),
62 EnvVar {
63 name: key,
64 value,
65 source: EnvVarSource::Process,
66 modified: Utc::now(),
67 original_value: None,
68 },
69 );
70 }
71
72 #[cfg(windows)]
73 self.load_windows_vars();
74
75 #[cfg(unix)]
76 self.load_unix_vars();
77
78 Ok(())
79 }
80
81 #[cfg(windows)]
82 fn load_windows_vars(&mut self) {
83 use winreg::RegKey;
84 use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
85
86 let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
88 if let Ok(env_key) = hklm.open_subkey("System\\CurrentControlSet\\Control\\Session Manager\\Environment") {
89 for (name, value) in env_key.enum_values().filter_map(std::result::Result::ok) {
90 let val_str = value.to_string();
91 self.vars.insert(
92 name.clone(),
93 EnvVar {
94 name,
95 value: val_str,
96 source: EnvVarSource::System,
97 modified: Utc::now(),
98 original_value: None,
99 },
100 );
101 }
102 }
103
104 let hkcu = RegKey::predef(HKEY_CURRENT_USER);
105 if let Ok(env_key) = hkcu.open_subkey("Environment") {
106 for (name, value) in env_key.enum_values().filter_map(std::result::Result::ok) {
107 let val_str = value.to_string();
108 self.vars.insert(
109 name.clone(),
110 EnvVar {
111 name,
112 value: val_str,
113 source: EnvVarSource::User,
114 modified: Utc::now(),
115 original_value: None,
116 },
117 );
118 }
119 }
120 }
121
122 #[cfg(unix)]
123 fn load_unix_vars(&mut self) {
124 for (key, value) in std::env::vars() {
127 let source = if key.starts_with("BASH_") || key.starts_with("ZSH_") {
128 EnvVarSource::Shell
129 } else {
130 EnvVarSource::Process
131 };
132
133 self.vars.insert(
134 key.clone(),
135 EnvVar {
136 name: key,
137 value,
138 source,
139 modified: Utc::now(),
140 original_value: None,
141 },
142 );
143 }
144 }
145
146 #[must_use]
147 pub fn get(&self, name: &str) -> Option<&EnvVar> {
148 self.vars.get(name)
149 }
150
151 #[must_use]
153 pub fn get_pattern(&self, pattern: &str) -> Vec<&EnvVar> {
154 if pattern.starts_with('/') && pattern.ends_with('/') && pattern.len() > 2 {
156 self.get_regex(&pattern[1..pattern.len() - 1])
158 } else if pattern.contains('*') || pattern.contains('?') {
159 self.get_wildcard(pattern)
161 } else {
162 self.get(pattern).into_iter().collect()
164 }
165 }
166
167 #[must_use]
169 pub fn get_wildcard(&self, pattern: &str) -> Vec<&EnvVar> {
170 let regex_pattern = wildcard_to_regex(pattern);
171 self.get_regex(®ex_pattern)
172 }
173
174 #[must_use]
176 pub fn get_regex(&self, pattern: &str) -> Vec<&EnvVar> {
177 match Regex::new(pattern) {
178 Ok(re) => self.vars.values().filter(|v| re.is_match(&v.name)).collect(),
179 Err(_) => vec![],
180 }
181 }
182
183 #[must_use]
185 pub fn get_prefix(&self, prefix: &str) -> Vec<&EnvVar> {
186 self.vars.values().filter(|v| v.name.starts_with(prefix)).collect()
187 }
188
189 #[must_use]
191 pub fn get_suffix(&self, suffix: &str) -> Vec<&EnvVar> {
192 self.vars.values().filter(|v| v.name.ends_with(suffix)).collect()
193 }
194
195 #[must_use]
197 pub fn get_containing(&self, substring: &str) -> Vec<&EnvVar> {
198 let lower = substring.to_lowercase();
199 self.vars
200 .values()
201 .filter(|v| v.name.to_lowercase().contains(&lower))
202 .collect()
203 }
204
205 pub fn set(&mut self, name: &str, value: &str, permanent: bool) -> Result<()> {
218 if name.is_empty() {
219 return Err(EnvxError::InvalidName("Variable name cannot be empty".to_string()).into());
220 }
221
222 if name.contains('=') {
223 return Err(EnvxError::InvalidName(format!("Variable name '{name}' cannot contain '='")).into());
224 }
225
226 #[cfg(windows)]
227 {
228 if name.contains('\0') {
230 return Err(EnvxError::InvalidName("Variable name cannot contain null character".to_string()).into());
231 }
232 }
233
234 let old_var = self.vars.get(name).cloned();
235
236 self.history
238 .push(crate::history::HistoryEntry::new(crate::history::HistoryAction::Set {
239 name: name.to_string(),
240 old_value: old_var.as_ref().map(|v| v.value.clone()),
241 new_value: value.to_string(),
242 }));
243
244 let var = EnvVar {
246 name: name.to_string(),
247 value: value.to_string(),
248 source: if permanent {
249 EnvVarSource::User
250 } else {
251 EnvVarSource::Process
252 },
253 modified: Utc::now(),
254 original_value: old_var.map(|v| v.value),
255 };
256 self.vars.insert(name.to_string(), var);
257
258 unsafe { std::env::set_var(name, value) };
260
261 if permanent {
262 #[cfg(windows)]
263 Self::set_windows_var(name, value, false)?;
264
265 #[cfg(unix)]
266 Self::set_unix_var(name, value);
267 }
268
269 Ok(())
270 }
271
272 #[cfg(windows)]
273 fn set_windows_var(name: &str, value: &str, system: bool) -> Result<()> {
274 use winreg::RegKey;
275 use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_SET_VALUE};
276
277 let (key, subkey) = if system {
278 (
279 HKEY_LOCAL_MACHINE,
280 "System\\CurrentControlSet\\Control\\Session Manager\\Environment",
281 )
282 } else {
283 (HKEY_CURRENT_USER, "Environment")
284 };
285
286 let hkey = RegKey::predef(key);
287 let env_key = hkey.open_subkey_with_flags(subkey, KEY_SET_VALUE)?;
288 env_key.set_value(name, &value)?;
289
290 Ok(())
307 }
308
309 #[cfg(unix)]
310 fn set_unix_var(name: &str, value: &str) {
311 println!("Note: To make this permanent on Unix, add to your shell config:");
315 println!("export {name}=\"{value}\"");
316 }
317
318 pub fn delete(&mut self, name: &str) -> Result<()> {
328 let old_var = self
329 .vars
330 .swap_remove(name)
331 .ok_or_else(|| EnvxError::VarNotFound(name.to_string()))?;
332
333 self.history.push(crate::history::HistoryEntry::new(
334 crate::history::HistoryAction::Delete {
335 name: name.to_string(),
336 old_value: old_var.value,
337 },
338 ));
339
340 unsafe { std::env::remove_var(name) };
341 Ok(())
342 }
343
344 #[must_use]
345 pub fn list(&self) -> Vec<&EnvVar> {
346 self.vars.values().collect()
347 }
348
349 #[must_use]
350 pub fn filter_by_source(&self, source: &EnvVarSource) -> Vec<&EnvVar> {
351 self.vars.values().filter(|v| v.source == *source).collect()
352 }
353
354 #[must_use]
355 pub fn search(&self, query: &str) -> Vec<&EnvVar> {
356 let query_lower = query.to_lowercase();
357 self.vars
358 .values()
359 .filter(|v| v.name.to_lowercase().contains(&query_lower) || v.value.to_lowercase().contains(&query_lower))
360 .collect()
361 }
362
363 pub fn undo(&mut self) -> Result<()> {
376 if let Some(entry) = self.history.pop() {
377 match entry.action {
379 crate::history::HistoryAction::Set { name, old_value, .. } => {
380 if let Some(old) = old_value {
381 let var = EnvVar {
383 name: name.clone(),
384 value: old.clone(),
385 source: EnvVarSource::Process,
386 modified: Utc::now(),
387 original_value: self.vars.get(&name).map(|v| v.value.clone()),
388 };
389 self.vars.insert(name.clone(), var);
390 unsafe { std::env::set_var(&name, &old) };
391 } else {
392 self.vars.swap_remove(&name);
394 unsafe { std::env::remove_var(&name) };
395 }
396 }
397 crate::history::HistoryAction::Delete { name, old_value } => {
398 let var = EnvVar {
400 name: name.clone(),
401 value: old_value.clone(),
402 source: EnvVarSource::Process,
403 modified: Utc::now(),
404 original_value: None,
405 };
406 self.vars.insert(name.clone(), var);
407 unsafe { std::env::set_var(&name, &old_value) };
408 }
409 crate::history::HistoryAction::BatchUpdate { .. } => {}
410 }
411 }
412 Ok(())
413 }
414
415 pub fn clear(&mut self) {
416 self.vars.clear();
417 }
418}
419fn wildcard_to_regex(pattern: &str) -> String {
420 let mut regex = String::new();
421 regex.push('^');
422
423 for ch in pattern.chars() {
424 match ch {
425 '*' => regex.push_str(".*"),
426 '?' => regex.push('.'),
427 '.' | '+' | '^' | '$' | '(' | ')' | '[' | ']' | '{' | '}' | '|' | '\\' => {
428 regex.push('\\');
429 regex.push(ch);
430 }
431 _ => regex.push(ch),
432 }
433 }
434
435 regex.push('$');
436 regex
437}
438
439#[cfg(test)]
440mod tests {
441 #![allow(clippy::unwrap_used)]
442 #![allow(clippy::expect_used)]
443 #![allow(clippy::panic)]
444 use chrono::Utc;
445
446 use crate::{EnvVarManager, EnvVarSource};
447
448 use super::*;
449
450 fn create_test_var(name: &str, value: &str, source: EnvVarSource) -> EnvVar {
452 EnvVar {
453 name: name.to_string(),
454 value: value.to_string(),
455 source,
456 modified: Utc::now(),
457 original_value: None,
458 }
459 }
460
461 fn create_test_manager() -> EnvVarManager {
463 let mut manager = EnvVarManager::new();
464
465 manager.vars.insert(
467 "PATH".to_string(),
468 create_test_var("PATH", "/usr/bin:/usr/local/bin", EnvVarSource::System),
469 );
470 manager.vars.insert(
471 "HOME".to_string(),
472 create_test_var("HOME", "/home/user", EnvVarSource::User),
473 );
474 manager.vars.insert(
475 "RUST_LOG".to_string(),
476 create_test_var("RUST_LOG", "debug", EnvVarSource::Process),
477 );
478 manager.vars.insert(
479 "API_KEY".to_string(),
480 create_test_var("API_KEY", "secret123", EnvVarSource::User),
481 );
482 manager.vars.insert(
483 "API_SECRET".to_string(),
484 create_test_var("API_SECRET", "supersecret", EnvVarSource::User),
485 );
486 manager.vars.insert(
487 "DATABASE_URL".to_string(),
488 create_test_var("DATABASE_URL", "postgres://localhost", EnvVarSource::Process),
489 );
490 manager.vars.insert(
491 "APP_VERSION".to_string(),
492 create_test_var("APP_VERSION", "1.0.0", EnvVarSource::Application("myapp".to_string())),
493 );
494
495 manager
496 }
497
498 #[test]
499 fn test_new() {
500 let manager = EnvVarManager::new();
501 assert!(manager.vars.is_empty());
502 assert!(manager.history.is_empty());
503 }
504
505 #[test]
506 fn test_get() {
507 let manager = create_test_manager();
508
509 let var = manager.get("PATH").unwrap();
511 assert_eq!(var.name, "PATH");
512 assert_eq!(var.value, "/usr/bin:/usr/local/bin");
513 assert_eq!(var.source, EnvVarSource::System);
514
515 assert!(manager.get("NON_EXISTENT").is_none());
517 }
518
519 #[test]
520 fn test_get_pattern_exact_match() {
521 let manager = create_test_manager();
522
523 let vars = manager.get_pattern("PATH");
524 assert_eq!(vars.len(), 1);
525 assert_eq!(vars[0].name, "PATH");
526 }
527
528 #[test]
529 fn test_get_pattern_wildcard() {
530 let manager = create_test_manager();
531
532 let vars = manager.get_pattern("API_*");
534 assert_eq!(vars.len(), 2);
535 let names: Vec<&str> = vars.iter().map(|v| v.name.as_str()).collect();
536 assert!(names.contains(&"API_KEY"));
537 assert!(names.contains(&"API_SECRET"));
538
539 let vars = manager.get_pattern("HOM?");
541 assert_eq!(vars.len(), 1);
542 assert_eq!(vars[0].name, "HOME");
543
544 let vars = manager.get_pattern("*_*");
546 assert!(vars.len() >= 4); }
548
549 #[test]
550 fn test_get_pattern_regex() {
551 let manager = create_test_manager();
552
553 let vars = manager.get_pattern("/^API.*/");
555 assert_eq!(vars.len(), 2);
556
557 let vars = manager.get_pattern("/(?i)^api.*/");
559 assert_eq!(vars.len(), 2);
560 }
561
562 #[test]
563 fn test_get_wildcard() {
564 let manager = create_test_manager();
565
566 assert_eq!(manager.get_wildcard("*").len(), 7); assert_eq!(manager.get_wildcard("A*").len(), 3); assert_eq!(manager.get_wildcard("*URL").len(), 1); assert_eq!(manager.get_wildcard("????").len(), 2); }
572
573 #[test]
574 fn test_get_regex() {
575 let manager = create_test_manager();
576
577 assert_eq!(manager.get_regex("^API.*").len(), 2);
579 assert_eq!(manager.get_regex(".*URL$").len(), 1);
580 assert_eq!(manager.get_regex("^[A-Z]+$").len(), 2); assert_eq!(manager.get_regex("[").len(), 0);
584 }
585
586 #[test]
587 fn test_get_prefix() {
588 let manager = create_test_manager();
589
590 assert_eq!(manager.get_prefix("API_").len(), 2);
591 assert_eq!(manager.get_prefix("DATA").len(), 1);
592 assert_eq!(manager.get_prefix("NON").len(), 0);
593 }
594
595 #[test]
596 fn test_get_suffix() {
597 let manager = create_test_manager();
598
599 assert_eq!(manager.get_suffix("_URL").len(), 1);
600 assert_eq!(manager.get_suffix("KEY").len(), 1);
601 assert_eq!(manager.get_suffix("SECRET").len(), 1);
602 assert_eq!(manager.get_suffix("XYZ").len(), 0);
603 }
604
605 #[test]
606 fn test_get_containing() {
607 let manager = create_test_manager();
608
609 assert_eq!(manager.get_containing("api").len(), 2);
611 assert_eq!(manager.get_containing("API").len(), 2);
612 assert_eq!(manager.get_containing("_").len(), 5);
613 assert_eq!(manager.get_containing("URL").len(), 1);
614 }
615
616 #[test]
617 fn test_set_temporary() {
618 let mut manager = EnvVarManager::new();
619
620 manager.set("TEST_VAR", "test_value", false).unwrap();
622
623 let var = manager.get("TEST_VAR").unwrap();
624 assert_eq!(var.value, "test_value");
625 assert_eq!(var.source, EnvVarSource::Process);
626 assert!(var.original_value.is_none());
627
628 assert_eq!(std::env::var("TEST_VAR").unwrap(), "test_value");
630
631 unsafe { std::env::remove_var("TEST_VAR") };
633 }
634
635 #[test]
636 fn test_set_overwrite_existing() {
637 let mut manager = create_test_manager();
638
639 manager.set("RUST_LOG", "info", false).unwrap();
641
642 let var = manager.get("RUST_LOG").unwrap();
643 assert_eq!(var.value, "info");
644 assert_eq!(var.original_value, Some("debug".to_string()));
645 }
646
647 #[test]
648 fn test_delete() {
649 let mut manager = create_test_manager();
650
651 assert!(manager.delete("RUST_LOG").is_ok());
653 assert!(manager.get("RUST_LOG").is_none());
654
655 assert!(manager.delete("NON_EXISTENT").is_err());
657 }
658
659 #[test]
660 fn test_list() {
661 let manager = create_test_manager();
662 let vars = manager.list();
663 assert_eq!(vars.len(), 7);
664 }
665
666 #[test]
667 fn test_filter_by_source() {
668 let manager = create_test_manager();
669
670 assert_eq!(manager.filter_by_source(&EnvVarSource::System).len(), 1);
671 assert_eq!(manager.filter_by_source(&EnvVarSource::User).len(), 3);
672 assert_eq!(manager.filter_by_source(&EnvVarSource::Process).len(), 2);
673 assert_eq!(manager.filter_by_source(&EnvVarSource::Shell).len(), 0);
674 assert_eq!(
675 manager
676 .filter_by_source(&EnvVarSource::Application("myapp".to_string()))
677 .len(),
678 1
679 );
680 }
681
682 #[test]
683 fn test_search() {
684 let manager = create_test_manager();
685
686 assert_eq!(manager.search("api").len(), 2);
688 assert_eq!(manager.search("PATH").len(), 1);
689
690 assert_eq!(manager.search("secret").len(), 2);
692 assert_eq!(manager.search("localhost").len(), 1);
693
694 assert_eq!(manager.search("API").len(), 2);
696 assert_eq!(manager.search("SECRET").len(), 2);
697 }
698
699 #[test]
700 fn test_history_tracking() {
701 let mut manager = EnvVarManager::new();
702
703 manager.set("VAR1", "value1", false).unwrap();
705 assert_eq!(manager.history.len(), 1);
706
707 manager.set("VAR1", "value2", false).unwrap();
709 assert_eq!(manager.history.len(), 2);
710
711 manager.delete("VAR1").unwrap();
713 assert_eq!(manager.history.len(), 3);
714
715 if let crate::history::HistoryAction::Delete { name, old_value } = &manager.history[2].action {
717 assert_eq!(name, "VAR1");
718 assert_eq!(old_value, "value2");
719 } else {
720 panic!("Expected Delete action");
721 }
722 }
723
724 #[test]
725 fn test_undo_set() {
726 let mut manager = EnvVarManager::new();
727
728 manager.set("UNDO_TEST", "value1", false).unwrap();
730
731 manager.set("UNDO_TEST", "value2", false).unwrap();
733
734 manager.undo().unwrap();
736 assert_eq!(manager.get("UNDO_TEST").unwrap().value, "value1");
737
738 manager.undo().unwrap();
740 assert!(manager.get("UNDO_TEST").is_none());
741 }
742
743 #[test]
744 fn test_undo_delete() {
745 let mut manager = EnvVarManager::new();
746
747 manager.set("DELETE_TEST", "value", false).unwrap();
749 manager.delete("DELETE_TEST").unwrap();
750 assert!(manager.get("DELETE_TEST").is_none());
751
752 manager.undo().unwrap();
754 assert_eq!(manager.get("DELETE_TEST").unwrap().value, "value");
755 }
756
757 #[test]
758 fn test_wildcard_to_regex() {
759 assert_eq!(wildcard_to_regex("API_*"), "^API_.*$");
761 assert_eq!(wildcard_to_regex("*_KEY"), "^.*_KEY$");
762 assert_eq!(wildcard_to_regex("*TEST*"), "^.*TEST.*$");
763
764 assert_eq!(wildcard_to_regex("HOM?"), "^HOM.$");
766 assert_eq!(wildcard_to_regex("??ST"), "^..ST$");
767
768 assert_eq!(wildcard_to_regex("TEST.VAR"), "^TEST\\.VAR$");
770 assert_eq!(wildcard_to_regex("VAR[1]"), "^VAR\\[1\\]$");
771 assert_eq!(wildcard_to_regex("A+B"), "^A\\+B$");
772 assert_eq!(wildcard_to_regex("^START"), "^\\^START$");
773 assert_eq!(wildcard_to_regex("END$"), "^END\\$$");
774 assert_eq!(wildcard_to_regex("(GROUP)"), "^\\(GROUP\\)$");
775 assert_eq!(wildcard_to_regex("{BRACE}"), "^\\{BRACE\\}$");
776 assert_eq!(wildcard_to_regex("A|B"), "^A\\|B$");
777 assert_eq!(wildcard_to_regex("C\\D"), "^C\\\\D$");
778
779 assert_eq!(wildcard_to_regex("*.txt"), "^.*\\.txt$");
781 assert_eq!(wildcard_to_regex("file?.log"), "^file.\\.log$");
782 }
783
784 #[test]
785 fn test_env_var_source_equality() {
786 assert_eq!(EnvVarSource::System, EnvVarSource::System);
787 assert_eq!(EnvVarSource::User, EnvVarSource::User);
788 assert_eq!(EnvVarSource::Process, EnvVarSource::Process);
789 assert_eq!(EnvVarSource::Shell, EnvVarSource::Shell);
790 assert_eq!(
791 EnvVarSource::Application("app1".to_string()),
792 EnvVarSource::Application("app1".to_string())
793 );
794 assert_ne!(
795 EnvVarSource::Application("app1".to_string()),
796 EnvVarSource::Application("app2".to_string())
797 );
798 assert_ne!(EnvVarSource::System, EnvVarSource::User);
799 }
800
801 #[test]
802 fn test_load_all() {
803 let mut manager = EnvVarManager::new();
804
805 unsafe { std::env::set_var("TEST_LOAD_VAR1", "value1") };
807 unsafe { std::env::set_var("TEST_LOAD_VAR2", "value2") };
808
809 manager.load_all().unwrap();
811
812 assert!(manager.get("TEST_LOAD_VAR1").is_some());
814 assert!(manager.get("TEST_LOAD_VAR2").is_some());
815
816 assert_eq!(manager.get("TEST_LOAD_VAR1").unwrap().source, EnvVarSource::Process);
818
819 unsafe { std::env::remove_var("TEST_LOAD_VAR1") };
821 unsafe { std::env::remove_var("TEST_LOAD_VAR2") };
822 }
823
824 #[test]
825 #[cfg(unix)]
826 fn test_unix_shell_detection() {
827 let mut manager = EnvVarManager::new();
828
829 unsafe { std::env::set_var("BASH_VERSION", "5.0.0") };
831
832 manager.load_unix_vars();
833
834 if let Some(var) = manager.get("BASH_VERSION") {
835 assert_eq!(var.source, EnvVarSource::Shell);
836 }
837
838 unsafe { std::env::remove_var("BASH_VERSION") };
840 }
841
842 #[test]
843 fn test_special_characters_in_values() {
844 let mut manager = EnvVarManager::new();
845
846 let special_values = vec![
848 ("NEWLINE_VAR", "line1\nline2"),
849 ("TAB_VAR", "col1\tcol2"),
850 ("QUOTE_VAR", "value with \"quotes\""),
851 ("BACKSLASH_VAR", "C:\\path\\to\\file"),
852 ("UNICODE_VAR", "Hello δΈη π"),
853 ("EMPTY_VAR", ""),
854 ("SPACE_VAR", " spaces around "),
855 ];
856
857 for (name, value) in special_values {
858 manager.set(name, value, false).unwrap();
859 assert_eq!(manager.get(name).unwrap().value, value);
860 assert_eq!(std::env::var(name).unwrap(), value);
861 unsafe { std::env::remove_var(name) };
862 }
863 }
864
865 #[test]
866 fn test_variable_ordering() {
867 let mut manager = EnvVarManager::new();
868
869 let vars = vec!["ZETA", "ALPHA", "GAMMA", "BETA"];
871 for var in &vars {
872 manager.set(var, "value", false).unwrap();
873 }
874
875 let list: Vec<&str> = manager.list().iter().map(|v| v.name.as_str()).collect();
877 assert_eq!(list, vars);
878 }
879
880 #[test]
881 fn test_concurrent_modification_safety() {
882 let mut manager = create_test_manager();
883
884 let initial_count = manager.list().len();
886
887 let vars_to_modify: Vec<String> = manager.get_prefix("API_").iter().map(|v| v.name.clone()).collect();
889
890 for name in vars_to_modify {
891 manager.set(&name, "modified", false).unwrap();
892 }
893
894 assert_eq!(manager.list().len(), initial_count);
896 }
897
898 #[test]
899 fn test_empty_operations() {
900 let manager = EnvVarManager::new();
901
902 assert_eq!(manager.list().len(), 0);
904 assert_eq!(manager.get_pattern("*").len(), 0);
905 assert_eq!(manager.get_prefix("").len(), 0);
906 assert_eq!(manager.get_suffix("").len(), 0);
907 assert_eq!(manager.get_containing("").len(), 0);
908 assert_eq!(manager.search("anything").len(), 0);
909 }
910
911 #[test]
912 fn test_case_sensitivity() {
913 let mut manager = EnvVarManager::new();
914
915 manager.set("test_var", "lower", false).unwrap();
917 manager.set("TEST_VAR", "upper", false).unwrap();
918
919 assert_eq!(manager.get("test_var").unwrap().value, "lower");
920 assert_eq!(manager.get("TEST_VAR").unwrap().value, "upper");
921 assert!(manager.get("Test_Var").is_none());
922
923 assert_eq!(manager.search("test_var").len(), 2);
925 assert_eq!(manager.get_containing("test_var").len(), 2);
926 }
927
928 #[test]
929 fn test_original_value_tracking() {
930 let mut manager = EnvVarManager::new();
931
932 manager.set("TRACK_VAR", "v1", false).unwrap();
934 assert!(manager.get("TRACK_VAR").unwrap().original_value.is_none());
935
936 manager.set("TRACK_VAR", "v2", false).unwrap();
938 assert_eq!(manager.get("TRACK_VAR").unwrap().original_value, Some("v1".to_string()));
939
940 manager.set("TRACK_VAR", "v3", false).unwrap();
942 assert_eq!(manager.get("TRACK_VAR").unwrap().original_value, Some("v2".to_string()));
943 }
944}