xerv_core/testing/providers/
env.rs1use parking_lot::RwLock;
7use std::collections::HashMap;
8
9pub trait EnvProvider: Send + Sync {
11 fn var(&self, key: &str) -> Option<String>;
13
14 fn set_var(&self, key: &str, value: &str);
16
17 fn remove_var(&self, key: &str);
19
20 fn vars(&self) -> HashMap<String, String>;
22
23 fn is_mock(&self) -> bool;
25}
26
27pub struct RealEnv;
29
30impl RealEnv {
31 pub fn new() -> Self {
33 Self
34 }
35}
36
37impl Default for RealEnv {
38 fn default() -> Self {
39 Self::new()
40 }
41}
42
43impl EnvProvider for RealEnv {
44 fn var(&self, key: &str) -> Option<String> {
45 std::env::var(key).ok()
46 }
47
48 fn set_var(&self, key: &str, value: &str) {
49 unsafe { std::env::set_var(key, value) };
52 }
53
54 fn remove_var(&self, key: &str) {
55 unsafe { std::env::remove_var(key) };
58 }
59
60 fn vars(&self) -> HashMap<String, String> {
61 std::env::vars().collect()
62 }
63
64 fn is_mock(&self) -> bool {
65 false
66 }
67}
68
69pub struct MockEnv {
87 vars: RwLock<HashMap<String, String>>,
88}
89
90impl MockEnv {
91 pub fn new() -> Self {
93 Self {
94 vars: RwLock::new(HashMap::new()),
95 }
96 }
97
98 pub fn with_vars(vars: HashMap<String, String>) -> Self {
100 Self {
101 vars: RwLock::new(vars),
102 }
103 }
104
105 pub fn with_var(self, key: impl Into<String>, value: impl Into<String>) -> Self {
107 self.vars.write().insert(key.into(), value.into());
108 self
109 }
110
111 pub fn from_pairs(pairs: &[(&str, &str)]) -> Self {
113 let vars: HashMap<String, String> = pairs
114 .iter()
115 .map(|(k, v)| (k.to_string(), v.to_string()))
116 .collect();
117 Self::with_vars(vars)
118 }
119
120 pub fn clear(&self) {
122 self.vars.write().clear();
123 }
124
125 pub fn len(&self) -> usize {
127 self.vars.read().len()
128 }
129
130 pub fn is_empty(&self) -> bool {
132 self.vars.read().is_empty()
133 }
134}
135
136impl Default for MockEnv {
137 fn default() -> Self {
138 Self::new()
139 }
140}
141
142impl EnvProvider for MockEnv {
143 fn var(&self, key: &str) -> Option<String> {
144 self.vars.read().get(key).cloned()
145 }
146
147 fn set_var(&self, key: &str, value: &str) {
148 self.vars.write().insert(key.to_string(), value.to_string());
149 }
150
151 fn remove_var(&self, key: &str) {
152 self.vars.write().remove(key);
153 }
154
155 fn vars(&self) -> HashMap<String, String> {
156 self.vars.read().clone()
157 }
158
159 fn is_mock(&self) -> bool {
160 true
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 #[test]
169 fn mock_env_basic() {
170 let env = MockEnv::new().with_var("FOO", "bar").with_var("BAZ", "qux");
171
172 assert_eq!(env.var("FOO"), Some("bar".to_string()));
173 assert_eq!(env.var("BAZ"), Some("qux".to_string()));
174 assert_eq!(env.var("MISSING"), None);
175 }
176
177 #[test]
178 fn mock_env_set_and_remove() {
179 let env = MockEnv::new();
180
181 env.set_var("KEY", "value");
182 assert_eq!(env.var("KEY"), Some("value".to_string()));
183
184 env.remove_var("KEY");
185 assert_eq!(env.var("KEY"), None);
186 }
187
188 #[test]
189 fn mock_env_from_pairs() {
190 let env = MockEnv::from_pairs(&[("A", "1"), ("B", "2"), ("C", "3")]);
191
192 assert_eq!(env.var("A"), Some("1".to_string()));
193 assert_eq!(env.var("B"), Some("2".to_string()));
194 assert_eq!(env.var("C"), Some("3".to_string()));
195 assert_eq!(env.len(), 3);
196 }
197
198 #[test]
199 fn mock_env_vars() {
200 let env = MockEnv::new().with_var("X", "1").with_var("Y", "2");
201
202 let vars = env.vars();
203 assert_eq!(vars.len(), 2);
204 assert_eq!(vars.get("X"), Some(&"1".to_string()));
205 assert_eq!(vars.get("Y"), Some(&"2".to_string()));
206 }
207
208 #[test]
209 fn mock_env_clear() {
210 let env = MockEnv::new().with_var("A", "1").with_var("B", "2");
211
212 assert_eq!(env.len(), 2);
213 env.clear();
214 assert!(env.is_empty());
215 }
216
217 #[test]
218 fn mock_env_isolation() {
219 let key = "XERV_TEST_ISOLATION_KEY";
221 let env = MockEnv::new().with_var(key, "mock_value");
222
223 assert_eq!(env.var(key), Some("mock_value".to_string()));
224 assert!(std::env::var(key).is_err());
225 }
226}