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    /// the current process environment, and the system environment (if it was
322    /// a permanent variable).
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.clone(),
337            },
338        ));
339
340        // Remove from current process
341        unsafe { std::env::remove_var(name) };
342
343        // Remove from system if it was a permanent variable
344        match old_var.source {
345            EnvVarSource::System | EnvVarSource::User => {
346                #[cfg(windows)]
347                delete_windows_var(name, matches!(old_var.source, EnvVarSource::System));
348
349                #[cfg(unix)]
350                delete_unix_var(name);
351            }
352            _ => {
353                // Process, Shell, or Application variables don't need system removal
354            }
355        }
356
357        Ok(())
358    }
359
360    #[must_use]
361    pub fn list(&self) -> Vec<&EnvVar> {
362        self.vars.values().collect()
363    }
364
365    #[must_use]
366    pub fn filter_by_source(&self, source: &EnvVarSource) -> Vec<&EnvVar> {
367        self.vars.values().filter(|v| v.source == *source).collect()
368    }
369
370    #[must_use]
371    pub fn search(&self, query: &str) -> Vec<&EnvVar> {
372        let query_lower = query.to_lowercase();
373        self.vars
374            .values()
375            .filter(|v| v.name.to_lowercase().contains(&query_lower) || v.value.to_lowercase().contains(&query_lower))
376            .collect()
377    }
378
379    /// Undoes the last environment variable operation.
380    ///
381    /// This method reverses the most recent operation (set or delete) by restoring
382    /// the previous state from the history. For set operations, it either restores
383    /// the previous value or removes the variable if it didn't exist before. For
384    /// delete operations, it restores the deleted variable with its previous value.
385    ///
386    /// # Errors
387    ///
388    /// Currently, this method always returns `Ok(())` and does not produce errors,
389    /// but it returns a `Result` for future extensibility and consistency with
390    /// other methods in the API.
391    pub fn undo(&mut self) -> Result<()> {
392        if let Some(entry) = self.history.pop() {
393            // Implement undo logic based on history entry
394            match entry.action {
395                crate::history::HistoryAction::Set { name, old_value, .. } => {
396                    if let Some(old) = old_value {
397                        // Variable existed before - restore old value without adding to history
398                        let var = EnvVar {
399                            name: name.clone(),
400                            value: old.clone(),
401                            source: EnvVarSource::Process,
402                            modified: Utc::now(),
403                            original_value: self.vars.get(&name).map(|v| v.value.clone()),
404                        };
405                        self.vars.insert(name.clone(), var);
406                        unsafe { std::env::set_var(&name, &old) };
407                    } else {
408                        // Variable didn't exist before - remove it without adding to history
409                        self.vars.swap_remove(&name);
410                        unsafe { std::env::remove_var(&name) };
411                    }
412                }
413                crate::history::HistoryAction::Delete { name, old_value } => {
414                    // Restore deleted variable without adding to history
415                    let var = EnvVar {
416                        name: name.clone(),
417                        value: old_value.clone(),
418                        source: EnvVarSource::Process,
419                        modified: Utc::now(),
420                        original_value: None,
421                    };
422                    self.vars.insert(name.clone(), var);
423                    unsafe { std::env::set_var(&name, &old_value) };
424                }
425                crate::history::HistoryAction::BatchUpdate { .. } => {}
426            }
427        }
428        Ok(())
429    }
430
431    pub fn clear(&mut self) {
432        self.vars.clear();
433    }
434
435    /// Rename environment variables based on pattern
436    /// Supports wildcards (*) for batch renaming
437    ///
438    /// # Errors
439    ///
440    /// Returns an error if:
441    /// - The pattern contains multiple wildcards (not supported)
442    /// - A target variable name already exists when attempting to rename
443    /// - The source variable specified by the pattern doesn't exist (for exact matches)
444    /// - System-level operations fail when updating environment variables
445    pub fn rename(&mut self, pattern: &str, replacement: &str) -> Result<Vec<(String, String)>> {
446        let mut renamed = Vec::new();
447
448        if pattern.contains('*') {
449            // Wildcard rename
450            let (prefix, suffix) = split_wildcard_pattern(pattern)?;
451            let (new_prefix, new_suffix) = split_wildcard_pattern(replacement)?;
452
453            // Find all matching variables
454            let matching_vars: Vec<(String, EnvVar)> = self
455                .vars
456                .iter()
457                .filter(|(name, _)| {
458                    name.starts_with(&prefix) && name.ends_with(&suffix) && name.len() >= prefix.len() + suffix.len()
459                })
460                .map(|(k, v)| (k.clone(), v.clone()))
461                .collect();
462
463            for (old_name, var) in matching_vars {
464                // Extract the middle part that the wildcard matched
465                let middle = &old_name[prefix.len()..old_name.len() - suffix.len()];
466                let new_name = format!("{new_prefix}{middle}{new_suffix}");
467
468                // Check if new name already exists
469                if self.vars.contains_key(&new_name) {
470                    return Err(EnvxError::Other(format!(
471                        "Cannot rename '{old_name}' to '{new_name}': target variable already exists"
472                    ))
473                    .into());
474                }
475
476                // Set new variable (this handles system updates)
477                self.set(&new_name, &var.value, true)?;
478
479                // Delete old variable (this also handles system updates)
480                self.delete(&old_name)?;
481
482                renamed.push((old_name, new_name));
483            }
484        } else {
485            // Exact match rename
486            if let Some(var) = self.vars.get(pattern).cloned() {
487                if self.vars.contains_key(replacement) {
488                    return Err(EnvxError::Other(format!(
489                        "Cannot rename '{pattern}' to '{replacement}': target variable already exists"
490                    ))
491                    .into());
492                }
493
494                // Set new variable
495                self.set(replacement, &var.value, true)?;
496
497                // Delete old variable
498                self.delete(pattern)?;
499
500                renamed.push((pattern.to_string(), replacement.to_string()));
501            } else {
502                return Err(EnvxError::Other(format!("Variable '{pattern}' not found")).into());
503            }
504        }
505
506        Ok(renamed)
507    }
508
509    /// Replace the value of environment variables matching a pattern
510    ///
511    /// # Arguments
512    /// * `pattern` - Variable name pattern (supports wildcards with *)
513    /// * `new_value` - The new value to set
514    ///
515    /// # Errors
516    ///
517    /// Returns an error if:
518    /// - The pattern contains multiple wildcards (not supported)
519    /// - System-level operations fail when updating environment variables
520    pub fn replace(&mut self, pattern: &str, new_value: &str) -> Result<Vec<(String, String, String)>> {
521        let mut replaced = Vec::new();
522
523        if pattern.contains('*') {
524            // Wildcard pattern
525            let (prefix, suffix) = split_wildcard_pattern(pattern)?;
526
527            // Find all matching variables
528            let matching_vars: Vec<(String, String)> = self
529                .vars
530                .iter()
531                .filter(|(name, _)| {
532                    name.starts_with(&prefix) && name.ends_with(&suffix) && name.len() >= prefix.len() + suffix.len()
533                })
534                .map(|(name, var)| (name.clone(), var.value.clone()))
535                .collect();
536
537            for (name, old_value) in matching_vars {
538                self.set(&name, new_value, true)?;
539                replaced.push((name, old_value, new_value.to_string()));
540            }
541        } else {
542            // Exact match
543            if let Some(var) = self.vars.get(pattern).cloned() {
544                let old_value = var.value;
545                self.set(pattern, new_value, true)?;
546                replaced.push((pattern.to_string(), old_value, new_value.to_string()));
547            } else {
548                return Err(EnvxError::VarNotFound(pattern.to_string()).into());
549            }
550        }
551
552        Ok(replaced)
553    }
554
555    /// Find and replace text within environment variable values
556    ///
557    /// # Arguments
558    /// * `search` - Text to search for in values
559    /// * `replacement` - Text to replace with
560    /// * `pattern` - Optional variable name pattern to limit the search (supports wildcards)
561    ///
562    /// # Errors
563    ///
564    /// Returns an error if:
565    /// - The pattern contains multiple wildcards (not supported)
566    /// - System-level operations fail when updating environment variables
567    pub fn find_replace(
568        &mut self,
569        search: &str,
570        replacement: &str,
571        pattern: Option<&str>,
572    ) -> Result<Vec<(String, String, String)>> {
573        let mut replaced = Vec::new();
574
575        let vars_to_update: Vec<(String, EnvVar)> = if let Some(pat) = pattern {
576            // Filter by pattern
577            if pat.contains('*') {
578                let (prefix, suffix) = split_wildcard_pattern(pat)?;
579                self.vars
580                    .iter()
581                    .filter(|(name, _)| {
582                        name.starts_with(&prefix)
583                            && name.ends_with(&suffix)
584                            && name.len() >= prefix.len() + suffix.len()
585                    })
586                    .filter(|(_, var)| var.value.contains(search))
587                    .map(|(k, v)| (k.clone(), v.clone()))
588                    .collect()
589            } else {
590                // Exact match pattern
591                self.vars
592                    .iter()
593                    .filter(|(name, _)| name == &pat)
594                    .filter(|(_, var)| var.value.contains(search))
595                    .map(|(k, v)| (k.clone(), v.clone()))
596                    .collect()
597            }
598        } else {
599            // All variables containing the search string
600            self.vars
601                .iter()
602                .filter(|(_, var)| var.value.contains(search))
603                .map(|(k, v)| (k.clone(), v.clone()))
604                .collect()
605        };
606
607        for (name, var) in vars_to_update {
608            let old_value = var.value.clone();
609            let new_value = var.value.replace(search, replacement);
610
611            // Use set method which handles all updates including system
612            self.set(&name, &new_value, true)?;
613
614            replaced.push((name, old_value, new_value));
615        }
616
617        Ok(replaced)
618    }
619}
620fn wildcard_to_regex(pattern: &str) -> String {
621    let mut regex = String::new();
622    regex.push('^');
623
624    for ch in pattern.chars() {
625        match ch {
626            '*' => regex.push_str(".*"),
627            '?' => regex.push('.'),
628            '.' | '+' | '^' | '$' | '(' | ')' | '[' | ']' | '{' | '}' | '|' | '\\' => {
629                regex.push('\\');
630                regex.push(ch);
631            }
632            _ => regex.push(ch),
633        }
634    }
635
636    regex.push('$');
637    regex
638}
639
640/// Split a wildcard pattern into prefix and suffix
641///
642/// # Errors
643///
644/// Returns an error if the pattern contains multiple wildcards (not supported).
645pub fn split_wildcard_pattern(pattern: &str) -> Result<(String, String)> {
646    if let Some(pos) = pattern.find('*') {
647        let prefix = pattern[..pos].to_string();
648        let suffix = pattern[pos + 1..].to_string();
649
650        // Ensure only one wildcard
651        if suffix.contains('*') {
652            return Err(EnvxError::Other("Multiple wildcards not supported".to_string()).into());
653        }
654
655        Ok((prefix, suffix))
656    } else {
657        Ok((pattern.to_string(), String::new()))
658    }
659}
660
661#[cfg(windows)]
662fn delete_windows_var(name: &str, system: bool) {
663    use winreg::RegKey;
664    use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_SET_VALUE};
665
666    let (key, subkey) = if system {
667        (
668            HKEY_LOCAL_MACHINE,
669            "System\\CurrentControlSet\\Control\\Session Manager\\Environment",
670        )
671    } else {
672        (HKEY_CURRENT_USER, "Environment")
673    };
674
675    let hkey = RegKey::predef(key);
676    match hkey.open_subkey_with_flags(subkey, KEY_SET_VALUE) {
677        Ok(env_key) => {
678            match env_key.delete_value(name) {
679                Ok(()) => {
680                    // Broadcast WM_SETTINGCHANGE to notify other processes
681                    // (commented out as in set_windows_var)
682                }
683                Err(e) => {
684                    // Variable might not exist in registry even though it was in our list
685                    // This is not necessarily an error
686                    tracing::debug!("Could not delete {} from registry: {}", name, e);
687                }
688            }
689        }
690        Err(e) => {
691            if system {
692                // System variables require admin privileges
693                tracing::warn!(
694                    "Cannot delete system variable {} (requires admin privileges): {}",
695                    name,
696                    e
697                );
698            }
699            // Continue anyway - we've at least removed it from the current process
700        }
701    }
702}
703
704#[cfg(unix)]
705fn delete_unix_var(name: &str) {
706    // On Unix, we'd typically need to modify shell config files
707    // This is a simplified version - real implementation would handle
708    // .bashrc, .zshrc, etc.
709    println!("Note: To permanently remove this on Unix, remove from your shell config:");
710    println!("unset {name}");
711    println!("And remove any 'export {name}=...' lines from .bashrc/.zshrc/etc.");
712}
713
714#[cfg(test)]
715mod tests {
716    #![allow(clippy::unwrap_used)]
717    #![allow(clippy::expect_used)]
718    #![allow(clippy::panic)]
719    use chrono::Utc;
720
721    use crate::{EnvVarManager, EnvVarSource};
722
723    use super::*;
724
725    // Helper function to create a test EnvVar
726    fn create_test_var(name: &str, value: &str, source: EnvVarSource) -> EnvVar {
727        EnvVar {
728            name: name.to_string(),
729            value: value.to_string(),
730            source,
731            modified: Utc::now(),
732            original_value: None,
733        }
734    }
735
736    // Helper to create a manager with test data
737    fn create_test_manager() -> EnvVarManager {
738        let mut manager = EnvVarManager::new();
739
740        // Add some test variables
741        manager.vars.insert(
742            "PATH".to_string(),
743            create_test_var("PATH", "/usr/bin:/usr/local/bin", EnvVarSource::System),
744        );
745        manager.vars.insert(
746            "HOME".to_string(),
747            create_test_var("HOME", "/home/user", EnvVarSource::User),
748        );
749        manager.vars.insert(
750            "RUST_LOG".to_string(),
751            create_test_var("RUST_LOG", "debug", EnvVarSource::Process),
752        );
753        manager.vars.insert(
754            "API_KEY".to_string(),
755            create_test_var("API_KEY", "secret123", EnvVarSource::User),
756        );
757        manager.vars.insert(
758            "API_SECRET".to_string(),
759            create_test_var("API_SECRET", "supersecret", EnvVarSource::User),
760        );
761        manager.vars.insert(
762            "DATABASE_URL".to_string(),
763            create_test_var("DATABASE_URL", "postgres://localhost", EnvVarSource::Process),
764        );
765        manager.vars.insert(
766            "APP_VERSION".to_string(),
767            create_test_var("APP_VERSION", "1.0.0", EnvVarSource::Application("myapp".to_string())),
768        );
769
770        manager
771    }
772
773    #[test]
774    fn test_new() {
775        let manager = EnvVarManager::new();
776        assert!(manager.vars.is_empty());
777        assert!(manager.history.is_empty());
778    }
779
780    #[test]
781    fn test_get() {
782        let manager = create_test_manager();
783
784        // Test existing variable
785        let var = manager.get("PATH").unwrap();
786        assert_eq!(var.name, "PATH");
787        assert_eq!(var.value, "/usr/bin:/usr/local/bin");
788        assert_eq!(var.source, EnvVarSource::System);
789
790        // Test non-existing variable
791        assert!(manager.get("NON_EXISTENT").is_none());
792    }
793
794    #[test]
795    fn test_get_pattern_exact_match() {
796        let manager = create_test_manager();
797
798        let vars = manager.get_pattern("PATH");
799        assert_eq!(vars.len(), 1);
800        assert_eq!(vars[0].name, "PATH");
801    }
802
803    #[test]
804    fn test_get_pattern_wildcard() {
805        let manager = create_test_manager();
806
807        // Test asterisk wildcard
808        let vars = manager.get_pattern("API_*");
809        assert_eq!(vars.len(), 2);
810        let names: Vec<&str> = vars.iter().map(|v| v.name.as_str()).collect();
811        assert!(names.contains(&"API_KEY"));
812        assert!(names.contains(&"API_SECRET"));
813
814        // Test question mark wildcard
815        let vars = manager.get_pattern("HOM?");
816        assert_eq!(vars.len(), 1);
817        assert_eq!(vars[0].name, "HOME");
818
819        // Test combination
820        let vars = manager.get_pattern("*_*");
821        assert!(vars.len() >= 4); // API_KEY, API_SECRET, DATABASE_URL, APP_VERSION
822    }
823
824    #[test]
825    fn test_get_pattern_regex() {
826        let manager = create_test_manager();
827
828        // Test regex pattern
829        let vars = manager.get_pattern("/^API.*/");
830        assert_eq!(vars.len(), 2);
831
832        // Test case-insensitive regex
833        let vars = manager.get_pattern("/(?i)^api.*/");
834        assert_eq!(vars.len(), 2);
835    }
836
837    #[test]
838    fn test_get_wildcard() {
839        let manager = create_test_manager();
840
841        // Test various wildcard patterns
842        assert_eq!(manager.get_wildcard("*").len(), 7); // All variables
843        assert_eq!(manager.get_wildcard("A*").len(), 3); // API_KEY, API_SECRET, APP_VERSION
844        assert_eq!(manager.get_wildcard("*URL").len(), 1); // DATABASE_URL
845        assert_eq!(manager.get_wildcard("????").len(), 2); // PATH, HOME
846    }
847
848    #[test]
849    fn test_get_regex() {
850        let manager = create_test_manager();
851
852        // Test valid regex
853        assert_eq!(manager.get_regex("^API.*").len(), 2);
854        assert_eq!(manager.get_regex(".*URL$").len(), 1);
855        assert_eq!(manager.get_regex("^[A-Z]+$").len(), 2); // PATH, HOME
856
857        // Test invalid regex - should return empty
858        assert_eq!(manager.get_regex("[").len(), 0);
859    }
860
861    #[test]
862    fn test_get_prefix() {
863        let manager = create_test_manager();
864
865        assert_eq!(manager.get_prefix("API_").len(), 2);
866        assert_eq!(manager.get_prefix("DATA").len(), 1);
867        assert_eq!(manager.get_prefix("NON").len(), 0);
868    }
869
870    #[test]
871    fn test_get_suffix() {
872        let manager = create_test_manager();
873
874        assert_eq!(manager.get_suffix("_URL").len(), 1);
875        assert_eq!(manager.get_suffix("KEY").len(), 1);
876        assert_eq!(manager.get_suffix("SECRET").len(), 1);
877        assert_eq!(manager.get_suffix("XYZ").len(), 0);
878    }
879
880    #[test]
881    fn test_get_containing() {
882        let manager = create_test_manager();
883
884        // Case-insensitive search
885        assert_eq!(manager.get_containing("api").len(), 2);
886        assert_eq!(manager.get_containing("API").len(), 2);
887        assert_eq!(manager.get_containing("_").len(), 5);
888        assert_eq!(manager.get_containing("URL").len(), 1);
889    }
890
891    #[test]
892    fn test_set_temporary() {
893        let mut manager = EnvVarManager::new();
894
895        // Set a new variable temporarily
896        manager.set("TEST_VAR", "test_value", false).unwrap();
897
898        let var = manager.get("TEST_VAR").unwrap();
899        assert_eq!(var.value, "test_value");
900        assert_eq!(var.source, EnvVarSource::Process);
901        assert!(var.original_value.is_none());
902
903        // Verify it was set in the process environment
904        assert_eq!(std::env::var("TEST_VAR").unwrap(), "test_value");
905
906        // Clean up
907        unsafe { std::env::remove_var("TEST_VAR") };
908    }
909
910    #[test]
911    fn test_set_overwrite_existing() {
912        let mut manager = create_test_manager();
913
914        // Overwrite existing variable
915        manager.set("RUST_LOG", "info", false).unwrap();
916
917        let var = manager.get("RUST_LOG").unwrap();
918        assert_eq!(var.value, "info");
919        assert_eq!(var.original_value, Some("debug".to_string()));
920    }
921
922    #[test]
923    fn test_delete() {
924        let mut manager = create_test_manager();
925
926        // Delete existing variable
927        assert!(manager.delete("RUST_LOG").is_ok());
928        assert!(manager.get("RUST_LOG").is_none());
929
930        // Try to delete non-existing variable
931        assert!(manager.delete("NON_EXISTENT").is_err());
932    }
933
934    #[test]
935    fn test_list() {
936        let manager = create_test_manager();
937        let vars = manager.list();
938        assert_eq!(vars.len(), 7);
939    }
940
941    #[test]
942    fn test_filter_by_source() {
943        let manager = create_test_manager();
944
945        assert_eq!(manager.filter_by_source(&EnvVarSource::System).len(), 1);
946        assert_eq!(manager.filter_by_source(&EnvVarSource::User).len(), 3);
947        assert_eq!(manager.filter_by_source(&EnvVarSource::Process).len(), 2);
948        assert_eq!(manager.filter_by_source(&EnvVarSource::Shell).len(), 0);
949        assert_eq!(
950            manager
951                .filter_by_source(&EnvVarSource::Application("myapp".to_string()))
952                .len(),
953            1
954        );
955    }
956
957    #[test]
958    fn test_search() {
959        let manager = create_test_manager();
960
961        // Search in names
962        assert_eq!(manager.search("api").len(), 2);
963        assert_eq!(manager.search("PATH").len(), 1);
964
965        // Search in values
966        assert_eq!(manager.search("secret").len(), 2);
967        assert_eq!(manager.search("localhost").len(), 1);
968
969        // Case-insensitive search
970        assert_eq!(manager.search("API").len(), 2);
971        assert_eq!(manager.search("SECRET").len(), 2);
972    }
973
974    #[test]
975    fn test_history_tracking() {
976        let mut manager = EnvVarManager::new();
977
978        // Set a variable
979        manager.set("VAR1", "value1", false).unwrap();
980        assert_eq!(manager.history.len(), 1);
981
982        // Update the variable
983        manager.set("VAR1", "value2", false).unwrap();
984        assert_eq!(manager.history.len(), 2);
985
986        // Delete the variable
987        manager.delete("VAR1").unwrap();
988        assert_eq!(manager.history.len(), 3);
989
990        // Verify history entries
991        if let crate::history::HistoryAction::Delete { name, old_value } = &manager.history[2].action {
992            assert_eq!(name, "VAR1");
993            assert_eq!(old_value, "value2");
994        } else {
995            panic!("Expected Delete action");
996        }
997    }
998
999    #[test]
1000    fn test_undo_set() {
1001        let mut manager = EnvVarManager::new();
1002
1003        // Set a new variable
1004        manager.set("UNDO_TEST", "value1", false).unwrap();
1005
1006        // Update it
1007        manager.set("UNDO_TEST", "value2", false).unwrap();
1008
1009        // Undo the update
1010        manager.undo().unwrap();
1011        assert_eq!(manager.get("UNDO_TEST").unwrap().value, "value1");
1012
1013        // Undo the initial set (should delete)
1014        manager.undo().unwrap();
1015        assert!(manager.get("UNDO_TEST").is_none());
1016    }
1017
1018    #[test]
1019    fn test_undo_delete() {
1020        let mut manager = EnvVarManager::new();
1021
1022        // Set and then delete a variable
1023        manager.set("DELETE_TEST", "value", false).unwrap();
1024        manager.delete("DELETE_TEST").unwrap();
1025        assert!(manager.get("DELETE_TEST").is_none());
1026
1027        // Undo the delete
1028        manager.undo().unwrap();
1029        assert_eq!(manager.get("DELETE_TEST").unwrap().value, "value");
1030    }
1031
1032    #[test]
1033    fn test_wildcard_to_regex() {
1034        // Test asterisk wildcard
1035        assert_eq!(wildcard_to_regex("API_*"), "^API_.*$");
1036        assert_eq!(wildcard_to_regex("*_KEY"), "^.*_KEY$");
1037        assert_eq!(wildcard_to_regex("*TEST*"), "^.*TEST.*$");
1038
1039        // Test question mark wildcard
1040        assert_eq!(wildcard_to_regex("HOM?"), "^HOM.$");
1041        assert_eq!(wildcard_to_regex("??ST"), "^..ST$");
1042
1043        // Test escaping special regex characters
1044        assert_eq!(wildcard_to_regex("TEST.VAR"), "^TEST\\.VAR$");
1045        assert_eq!(wildcard_to_regex("VAR[1]"), "^VAR\\[1\\]$");
1046        assert_eq!(wildcard_to_regex("A+B"), "^A\\+B$");
1047        assert_eq!(wildcard_to_regex("^START"), "^\\^START$");
1048        assert_eq!(wildcard_to_regex("END$"), "^END\\$$");
1049        assert_eq!(wildcard_to_regex("(GROUP)"), "^\\(GROUP\\)$");
1050        assert_eq!(wildcard_to_regex("{BRACE}"), "^\\{BRACE\\}$");
1051        assert_eq!(wildcard_to_regex("A|B"), "^A\\|B$");
1052        assert_eq!(wildcard_to_regex("C\\D"), "^C\\\\D$");
1053
1054        // Test combination
1055        assert_eq!(wildcard_to_regex("*.txt"), "^.*\\.txt$");
1056        assert_eq!(wildcard_to_regex("file?.log"), "^file.\\.log$");
1057    }
1058
1059    #[test]
1060    fn test_env_var_source_equality() {
1061        assert_eq!(EnvVarSource::System, EnvVarSource::System);
1062        assert_eq!(EnvVarSource::User, EnvVarSource::User);
1063        assert_eq!(EnvVarSource::Process, EnvVarSource::Process);
1064        assert_eq!(EnvVarSource::Shell, EnvVarSource::Shell);
1065        assert_eq!(
1066            EnvVarSource::Application("app1".to_string()),
1067            EnvVarSource::Application("app1".to_string())
1068        );
1069        assert_ne!(
1070            EnvVarSource::Application("app1".to_string()),
1071            EnvVarSource::Application("app2".to_string())
1072        );
1073        assert_ne!(EnvVarSource::System, EnvVarSource::User);
1074    }
1075
1076    #[test]
1077    fn test_load_all() {
1078        let mut manager = EnvVarManager::new();
1079
1080        // Set some test environment variables
1081        unsafe { std::env::set_var("TEST_LOAD_VAR1", "value1") };
1082        unsafe { std::env::set_var("TEST_LOAD_VAR2", "value2") };
1083
1084        // Load all variables
1085        manager.load_all().unwrap();
1086
1087        // Verify our test variables were loaded
1088        assert!(manager.get("TEST_LOAD_VAR1").is_some());
1089        assert!(manager.get("TEST_LOAD_VAR2").is_some());
1090
1091        // Verify they have the correct source
1092        assert_eq!(manager.get("TEST_LOAD_VAR1").unwrap().source, EnvVarSource::Process);
1093
1094        // Clean up
1095        unsafe { std::env::remove_var("TEST_LOAD_VAR1") };
1096        unsafe { std::env::remove_var("TEST_LOAD_VAR2") };
1097    }
1098
1099    #[test]
1100    #[cfg(unix)]
1101    fn test_unix_shell_detection() {
1102        let mut manager = EnvVarManager::new();
1103
1104        // Set a mock shell variable
1105        unsafe { std::env::set_var("BASH_VERSION", "5.0.0") };
1106
1107        manager.load_unix_vars();
1108
1109        if let Some(var) = manager.get("BASH_VERSION") {
1110            assert_eq!(var.source, EnvVarSource::Shell);
1111        }
1112
1113        // Clean up
1114        unsafe { std::env::remove_var("BASH_VERSION") };
1115    }
1116
1117    #[test]
1118    fn test_special_characters_in_values() {
1119        let mut manager = EnvVarManager::new();
1120
1121        // Test with various special characters
1122        let special_values = vec![
1123            ("NEWLINE_VAR", "line1\nline2"),
1124            ("TAB_VAR", "col1\tcol2"),
1125            ("QUOTE_VAR", "value with \"quotes\""),
1126            ("BACKSLASH_VAR", "C:\\path\\to\\file"),
1127            ("UNICODE_VAR", "Hello δΈ–η•Œ 🌍"),
1128            ("EMPTY_VAR", ""),
1129            ("SPACE_VAR", "  spaces around  "),
1130        ];
1131
1132        for (name, value) in special_values {
1133            manager.set(name, value, false).unwrap();
1134            assert_eq!(manager.get(name).unwrap().value, value);
1135            assert_eq!(std::env::var(name).unwrap(), value);
1136            unsafe { std::env::remove_var(name) };
1137        }
1138    }
1139
1140    #[test]
1141    fn test_variable_ordering() {
1142        let mut manager = EnvVarManager::new();
1143
1144        // Add variables in specific order
1145        let vars = vec!["ZETA", "ALPHA", "GAMMA", "BETA"];
1146        for var in &vars {
1147            manager.set(var, "value", false).unwrap();
1148        }
1149
1150        // Verify order is preserved (IndexMap maintains insertion order)
1151        let list: Vec<&str> = manager.list().iter().map(|v| v.name.as_str()).collect();
1152        assert_eq!(list, vars);
1153    }
1154
1155    #[test]
1156    fn test_concurrent_modification_safety() {
1157        let mut manager = create_test_manager();
1158
1159        // Get initial count
1160        let initial_count = manager.list().len();
1161
1162        // Modify while iterating (this should be safe with our implementation)
1163        let vars_to_modify: Vec<String> = manager.get_prefix("API_").iter().map(|v| v.name.clone()).collect();
1164
1165        for name in vars_to_modify {
1166            manager.set(&name, "modified", false).unwrap();
1167        }
1168
1169        // Verify count hasn't changed
1170        assert_eq!(manager.list().len(), initial_count);
1171    }
1172
1173    #[test]
1174    fn test_empty_operations() {
1175        let manager = EnvVarManager::new();
1176
1177        // Test operations on empty manager
1178        assert_eq!(manager.list().len(), 0);
1179        assert_eq!(manager.get_pattern("*").len(), 0);
1180        assert_eq!(manager.get_prefix("").len(), 0);
1181        assert_eq!(manager.get_suffix("").len(), 0);
1182        assert_eq!(manager.get_containing("").len(), 0);
1183        assert_eq!(manager.search("anything").len(), 0);
1184    }
1185
1186    #[test]
1187    fn test_case_sensitivity() {
1188        let mut manager = EnvVarManager::new();
1189
1190        // Variable names are case-sensitive
1191        manager.set("test_var", "lower", false).unwrap();
1192        manager.set("TEST_VAR", "upper", false).unwrap();
1193
1194        assert_eq!(manager.get("test_var").unwrap().value, "lower");
1195        assert_eq!(manager.get("TEST_VAR").unwrap().value, "upper");
1196        assert!(manager.get("Test_Var").is_none());
1197
1198        // But search is case-insensitive
1199        assert_eq!(manager.search("test_var").len(), 2);
1200        assert_eq!(manager.get_containing("test_var").len(), 2);
1201    }
1202
1203    #[test]
1204    fn test_original_value_tracking() {
1205        let mut manager = EnvVarManager::new();
1206
1207        // First set - no original value
1208        manager.set("TRACK_VAR", "v1", false).unwrap();
1209        assert!(manager.get("TRACK_VAR").unwrap().original_value.is_none());
1210
1211        // Second set - original value is v1
1212        manager.set("TRACK_VAR", "v2", false).unwrap();
1213        assert_eq!(manager.get("TRACK_VAR").unwrap().original_value, Some("v1".to_string()));
1214
1215        // Third set - original value is v2
1216        manager.set("TRACK_VAR", "v3", false).unwrap();
1217        assert_eq!(manager.get("TRACK_VAR").unwrap().original_value, Some("v2".to_string()));
1218    }
1219
1220    #[test]
1221    fn test_rename_exact_match() {
1222        let mut manager = EnvVarManager::new();
1223        manager.set("OLD_VAR", "value", false).unwrap();
1224
1225        let renamed = manager.rename("OLD_VAR", "NEW_VAR").unwrap();
1226
1227        assert_eq!(renamed.len(), 1);
1228        assert_eq!(renamed[0], ("OLD_VAR".to_string(), "NEW_VAR".to_string()));
1229
1230        assert!(manager.get("OLD_VAR").is_none());
1231        assert_eq!(manager.get("NEW_VAR").unwrap().value, "value");
1232    }
1233
1234    #[test]
1235    fn test_rename_wildcard_prefix() {
1236        let mut manager = EnvVarManager::new();
1237        manager.set("APP_VAR1", "value1", false).unwrap();
1238        manager.set("APP_VAR2", "value2", false).unwrap();
1239        manager.set("OTHER_VAR", "other", false).unwrap();
1240
1241        let renamed = manager.rename("APP_*", "MY_APP_*").unwrap();
1242
1243        assert_eq!(renamed.len(), 2);
1244        assert!(manager.get("MY_APP_VAR1").is_some());
1245        assert!(manager.get("MY_APP_VAR2").is_some());
1246        assert!(manager.get("APP_VAR1").is_none());
1247        assert!(manager.get("APP_VAR2").is_none());
1248        assert!(manager.get("OTHER_VAR").is_some());
1249    }
1250
1251    #[test]
1252    fn test_rename_target_exists_error() {
1253        let mut manager = EnvVarManager::new();
1254        manager.set("VAR1", "value1", false).unwrap();
1255        manager.set("VAR2", "value2", false).unwrap();
1256
1257        let result = manager.rename("VAR1", "VAR2");
1258        assert!(result.is_err());
1259        assert!(result.unwrap_err().to_string().contains("already exists"));
1260    }
1261
1262    #[test]
1263    fn test_rename_not_found_error() {
1264        let mut manager = EnvVarManager::new();
1265
1266        let result = manager.rename("NONEXISTENT", "NEW_VAR");
1267        assert!(result.is_err());
1268        assert!(result.unwrap_err().to_string().contains("not found"));
1269    }
1270
1271    #[test]
1272    fn test_replace_single_variable() {
1273        let mut manager = EnvVarManager::new();
1274        manager.set("MY_VAR", "old_value", false).unwrap();
1275
1276        let replaced = manager.replace("MY_VAR", "new_value").unwrap();
1277
1278        assert_eq!(replaced.len(), 1);
1279        assert_eq!(
1280            replaced[0],
1281            ("MY_VAR".to_string(), "old_value".to_string(), "new_value".to_string())
1282        );
1283        assert_eq!(manager.get("MY_VAR").unwrap().value, "new_value");
1284    }
1285
1286    #[test]
1287    fn test_replace_wildcard_pattern() {
1288        let mut manager = EnvVarManager::new();
1289        manager.set("API_KEY", "old_key", false).unwrap();
1290        manager.set("API_SECRET", "old_secret", false).unwrap();
1291        manager.set("OTHER_VAR", "other", false).unwrap();
1292
1293        let replaced = manager.replace("API_*", "REDACTED").unwrap();
1294
1295        assert_eq!(replaced.len(), 2);
1296        assert_eq!(manager.get("API_KEY").unwrap().value, "REDACTED");
1297        assert_eq!(manager.get("API_SECRET").unwrap().value, "REDACTED");
1298        assert_eq!(manager.get("OTHER_VAR").unwrap().value, "other");
1299    }
1300
1301    #[test]
1302    fn test_replace_not_found() {
1303        let mut manager = EnvVarManager::new();
1304
1305        let result = manager.replace("NONEXISTENT", "value");
1306        assert!(result.is_err());
1307    }
1308
1309    #[test]
1310    fn test_find_replace_in_values() {
1311        let mut manager = EnvVarManager::new();
1312        manager.set("DATABASE_URL", "localhost:5432", false).unwrap();
1313        manager.set("API_URL", "localhost:8080", false).unwrap();
1314
1315        let replaced = manager
1316            .find_replace("localhost", "production.server.com", None)
1317            .unwrap();
1318
1319        assert_eq!(replaced.len(), 2);
1320        assert_eq!(manager.get("DATABASE_URL").unwrap().value, "production.server.com:5432");
1321        assert_eq!(manager.get("API_URL").unwrap().value, "production.server.com:8080");
1322    }
1323
1324    #[test]
1325    fn test_split_wildcard_pattern() {
1326        assert_eq!(
1327            split_wildcard_pattern("APP_*").unwrap(),
1328            ("APP_".to_string(), String::new())
1329        );
1330        assert_eq!(
1331            split_wildcard_pattern("*_SUFFIX").unwrap(),
1332            (String::new(), "_SUFFIX".to_string())
1333        );
1334        assert_eq!(
1335            split_wildcard_pattern("PREFIX_*_SUFFIX").unwrap(),
1336            ("PREFIX_".to_string(), "_SUFFIX".to_string())
1337        );
1338        assert_eq!(
1339            split_wildcard_pattern("NO_WILDCARD").unwrap(),
1340            ("NO_WILDCARD".to_string(), String::new())
1341        );
1342
1343        // Multiple wildcards should error
1344        assert!(split_wildcard_pattern("*_*").is_err());
1345    }
1346
1347    #[test]
1348    fn test_delete_permanent_variable() {
1349        let mut manager = EnvVarManager::new();
1350
1351        // Set a permanent variable (this would normally write to registry/shell config)
1352        manager.set("DELETE_PERM_TEST", "value", true).unwrap();
1353
1354        // Verify it exists
1355        assert!(manager.get("DELETE_PERM_TEST").is_some());
1356        assert_eq!(std::env::var("DELETE_PERM_TEST").unwrap(), "value");
1357
1358        // Delete it
1359        manager.delete("DELETE_PERM_TEST").unwrap();
1360
1361        // Verify it's gone from manager
1362        assert!(manager.get("DELETE_PERM_TEST").is_none());
1363
1364        // Verify it's gone from process environment
1365        assert!(std::env::var("DELETE_PERM_TEST").is_err());
1366
1367        // Note: We can't easily test registry deletion in unit tests,
1368        // but the implementation will handle it
1369    }
1370
1371    #[test]
1372    fn test_delete_tracks_source() {
1373        let mut manager = EnvVarManager::new();
1374
1375        // Add variables with different sources
1376        manager.vars.insert(
1377            "SYS_VAR".to_string(),
1378            create_test_var("SYS_VAR", "sys_value", EnvVarSource::System),
1379        );
1380        manager.vars.insert(
1381            "USER_VAR".to_string(),
1382            create_test_var("USER_VAR", "user_value", EnvVarSource::User),
1383        );
1384        manager.vars.insert(
1385            "PROC_VAR".to_string(),
1386            create_test_var("PROC_VAR", "proc_value", EnvVarSource::Process),
1387        );
1388
1389        // Delete each type - only System and User should trigger system deletion
1390        assert!(manager.delete("SYS_VAR").is_ok());
1391        assert!(manager.delete("USER_VAR").is_ok());
1392        assert!(manager.delete("PROC_VAR").is_ok());
1393
1394        // All should be removed from manager
1395        assert!(manager.get("SYS_VAR").is_none());
1396        assert!(manager.get("USER_VAR").is_none());
1397        assert!(manager.get("PROC_VAR").is_none());
1398    }
1399}