envx_core/
env.rs

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    /// Loads environment variables from all available sources (process, system, and user).
47    ///
48    /// This method loads environment variables from the current process environment
49    /// and platform-specific sources like the Windows registry or Unix shell configurations.
50    ///
51    /// # Errors
52    ///
53    /// Returns an error if:
54    /// - Registry access fails on Windows platforms
55    /// - File system operations fail when reading Unix shell configurations
56    /// - Other platform-specific environment variable access fails
57    pub fn load_all(&mut self) -> Result<()> {
58        // Load process environment variables
59        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        // Load system variables
87        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        // On Unix, we primarily work with process environment
125        // Shell-specific vars can be detected by checking common patterns
126        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    /// Get variables matching a pattern (supports wildcards and regex)
152    #[must_use]
153    pub fn get_pattern(&self, pattern: &str) -> Vec<&EnvVar> {
154        // Check if it's a regex pattern first (enclosed in slashes)
155        if pattern.starts_with('/') && pattern.ends_with('/') && pattern.len() > 2 {
156            // Regex pattern like /^PATH.*/
157            self.get_regex(&pattern[1..pattern.len() - 1])
158        } else if pattern.contains('*') || pattern.contains('?') {
159            // Simple wildcard pattern
160            self.get_wildcard(pattern)
161        } else {
162            // Exact match
163            self.get(pattern).into_iter().collect()
164        }
165    }
166
167    /// Get variables matching a wildcard pattern (* and ?)
168    #[must_use]
169    pub fn get_wildcard(&self, pattern: &str) -> Vec<&EnvVar> {
170        let regex_pattern = wildcard_to_regex(pattern);
171        self.get_regex(&regex_pattern)
172    }
173
174    /// Get variables matching a regex pattern
175    #[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    /// Get variables with names starting with a prefix
184    #[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    /// Get variables with names ending with a suffix
190    #[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    /// Get variables containing a substring (case-insensitive)
196    #[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    /// Sets an environment variable with the given name and value.
206    ///
207    /// This method updates the variable both in the manager's internal state
208    /// and in the current process environment. If `permanent` is true, it will
209    /// also attempt to persist the variable to the system or user environment.
210    ///
211    /// # Errors
212    ///
213    /// Returns an error if:
214    /// - Registry operations fail on Windows platforms when setting permanent variables
215    /// - File system operations fail when modifying shell configuration files on Unix
216    /// - Other platform-specific environment variable persistence operations fail
217    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            // Windows-specific validation
229            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        // Record history
237        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        // Update in-memory
245        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        // Apply to process
259        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        // Broadcast WM_SETTINGCHANGE to notify other processes
291        /* unsafe {
292            use windows::Win32::Foundation::*;
293            use windows::Win32::UI::WindowsAndMessaging::*;
294
295            let _ = SendMessageTimeoutW(
296                HWND_BROADCAST,
297                WM_SETTINGCHANGE,
298                WPARAM(0),
299                LPARAM(s!("Environment").as_ptr() as isize),
300                SMTO_ABORTIFHUNG,
301                5000,
302                None,
303            );
304        } */
305
306        Ok(())
307    }
308
309    #[cfg(unix)]
310    fn set_unix_var(name: &str, value: &str) {
311        // On Unix, we'd typically need to modify shell config files
312        // This is a simplified version - real implementation would handle
313        // .bashrc, .zshrc, etc.
314        println!("Note: To make this permanent on Unix, add to your shell config:");
315        println!("export {name}=\"{value}\"");
316    }
317
318    /// Deletes an environment variable by name.
319    ///
320    /// This method removes the variable from both the manager's internal state
321    /// and the current process environment. The operation is recorded in the
322    /// history for potential undo operations.
323    ///
324    /// # Errors
325    ///
326    /// Returns an error if the variable with the given name does not exist.
327    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    /// Undoes the last environment variable operation.
364    ///
365    /// This method reverses the most recent operation (set or delete) by restoring
366    /// the previous state from the history. For set operations, it either restores
367    /// the previous value or removes the variable if it didn't exist before. For
368    /// delete operations, it restores the deleted variable with its previous value.
369    ///
370    /// # Errors
371    ///
372    /// Currently, this method always returns `Ok(())` and does not produce errors,
373    /// but it returns a `Result` for future extensibility and consistency with
374    /// other methods in the API.
375    pub fn undo(&mut self) -> Result<()> {
376        if let Some(entry) = self.history.pop() {
377            // Implement undo logic based on history entry
378            match entry.action {
379                crate::history::HistoryAction::Set { name, old_value, .. } => {
380                    if let Some(old) = old_value {
381                        // Variable existed before - restore old value without adding to history
382                        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                        // Variable didn't exist before - remove it without adding to history
393                        self.vars.swap_remove(&name);
394                        unsafe { std::env::remove_var(&name) };
395                    }
396                }
397                crate::history::HistoryAction::Delete { name, old_value } => {
398                    // Restore deleted variable without adding to history
399                    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    // Helper function to create a test EnvVar
451    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    // Helper to create a manager with test data
462    fn create_test_manager() -> EnvVarManager {
463        let mut manager = EnvVarManager::new();
464
465        // Add some test variables
466        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        // Test existing variable
510        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        // Test non-existing variable
516        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        // Test asterisk wildcard
533        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        // Test question mark wildcard
540        let vars = manager.get_pattern("HOM?");
541        assert_eq!(vars.len(), 1);
542        assert_eq!(vars[0].name, "HOME");
543
544        // Test combination
545        let vars = manager.get_pattern("*_*");
546        assert!(vars.len() >= 4); // API_KEY, API_SECRET, DATABASE_URL, APP_VERSION
547    }
548
549    #[test]
550    fn test_get_pattern_regex() {
551        let manager = create_test_manager();
552
553        // Test regex pattern
554        let vars = manager.get_pattern("/^API.*/");
555        assert_eq!(vars.len(), 2);
556
557        // Test case-insensitive regex
558        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        // Test various wildcard patterns
567        assert_eq!(manager.get_wildcard("*").len(), 7); // All variables
568        assert_eq!(manager.get_wildcard("A*").len(), 3); // API_KEY, API_SECRET, APP_VERSION
569        assert_eq!(manager.get_wildcard("*URL").len(), 1); // DATABASE_URL
570        assert_eq!(manager.get_wildcard("????").len(), 2); // PATH, HOME
571    }
572
573    #[test]
574    fn test_get_regex() {
575        let manager = create_test_manager();
576
577        // Test valid regex
578        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); // PATH, HOME
581
582        // Test invalid regex - should return empty
583        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        // Case-insensitive search
610        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        // Set a new variable temporarily
621        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        // Verify it was set in the process environment
629        assert_eq!(std::env::var("TEST_VAR").unwrap(), "test_value");
630
631        // Clean up
632        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        // Overwrite existing variable
640        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        // Delete existing variable
652        assert!(manager.delete("RUST_LOG").is_ok());
653        assert!(manager.get("RUST_LOG").is_none());
654
655        // Try to delete non-existing variable
656        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        // Search in names
687        assert_eq!(manager.search("api").len(), 2);
688        assert_eq!(manager.search("PATH").len(), 1);
689
690        // Search in values
691        assert_eq!(manager.search("secret").len(), 2);
692        assert_eq!(manager.search("localhost").len(), 1);
693
694        // Case-insensitive search
695        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        // Set a variable
704        manager.set("VAR1", "value1", false).unwrap();
705        assert_eq!(manager.history.len(), 1);
706
707        // Update the variable
708        manager.set("VAR1", "value2", false).unwrap();
709        assert_eq!(manager.history.len(), 2);
710
711        // Delete the variable
712        manager.delete("VAR1").unwrap();
713        assert_eq!(manager.history.len(), 3);
714
715        // Verify history entries
716        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        // Set a new variable
729        manager.set("UNDO_TEST", "value1", false).unwrap();
730
731        // Update it
732        manager.set("UNDO_TEST", "value2", false).unwrap();
733
734        // Undo the update
735        manager.undo().unwrap();
736        assert_eq!(manager.get("UNDO_TEST").unwrap().value, "value1");
737
738        // Undo the initial set (should delete)
739        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        // Set and then delete a variable
748        manager.set("DELETE_TEST", "value", false).unwrap();
749        manager.delete("DELETE_TEST").unwrap();
750        assert!(manager.get("DELETE_TEST").is_none());
751
752        // Undo the delete
753        manager.undo().unwrap();
754        assert_eq!(manager.get("DELETE_TEST").unwrap().value, "value");
755    }
756
757    #[test]
758    fn test_wildcard_to_regex() {
759        // Test asterisk wildcard
760        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        // Test question mark wildcard
765        assert_eq!(wildcard_to_regex("HOM?"), "^HOM.$");
766        assert_eq!(wildcard_to_regex("??ST"), "^..ST$");
767
768        // Test escaping special regex characters
769        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        // Test combination
780        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        // Set some test environment variables
806        unsafe { std::env::set_var("TEST_LOAD_VAR1", "value1") };
807        unsafe { std::env::set_var("TEST_LOAD_VAR2", "value2") };
808
809        // Load all variables
810        manager.load_all().unwrap();
811
812        // Verify our test variables were loaded
813        assert!(manager.get("TEST_LOAD_VAR1").is_some());
814        assert!(manager.get("TEST_LOAD_VAR2").is_some());
815
816        // Verify they have the correct source
817        assert_eq!(manager.get("TEST_LOAD_VAR1").unwrap().source, EnvVarSource::Process);
818
819        // Clean up
820        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        // Set a mock shell variable
830        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        // Clean up
839        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        // Test with various special characters
847        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        // Add variables in specific order
870        let vars = vec!["ZETA", "ALPHA", "GAMMA", "BETA"];
871        for var in &vars {
872            manager.set(var, "value", false).unwrap();
873        }
874
875        // Verify order is preserved (IndexMap maintains insertion order)
876        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        // Get initial count
885        let initial_count = manager.list().len();
886
887        // Modify while iterating (this should be safe with our implementation)
888        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        // Verify count hasn't changed
895        assert_eq!(manager.list().len(), initial_count);
896    }
897
898    #[test]
899    fn test_empty_operations() {
900        let manager = EnvVarManager::new();
901
902        // Test operations on empty manager
903        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        // Variable names are case-sensitive
916        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        // But search is case-insensitive
924        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        // First set - no original value
933        manager.set("TRACK_VAR", "v1", false).unwrap();
934        assert!(manager.get("TRACK_VAR").unwrap().original_value.is_none());
935
936        // Second set - original value is v1
937        manager.set("TRACK_VAR", "v2", false).unwrap();
938        assert_eq!(manager.get("TRACK_VAR").unwrap().original_value, Some("v1".to_string()));
939
940        // Third set - original value is v2
941        manager.set("TRACK_VAR", "v3", false).unwrap();
942        assert_eq!(manager.get("TRACK_VAR").unwrap().original_value, Some("v2".to_string()));
943    }
944}