1use crate::EnvVar;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct Snapshot {
8 pub id: String,
9 pub name: String,
10 pub description: Option<String>,
11 pub created_at: DateTime<Utc>,
12 pub variables: HashMap<String, EnvVar>,
13 pub metadata: HashMap<String, String>,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct Profile {
18 pub name: String,
19 pub description: Option<String>,
20 pub created_at: DateTime<Utc>,
21 pub updated_at: DateTime<Utc>,
22 pub variables: HashMap<String, ProfileVar>,
23 pub parent: Option<String>,
24 pub metadata: HashMap<String, String>,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct ProfileVar {
29 pub value: String,
30 pub enabled: bool,
31 pub override_system: bool,
32}
33
34impl Snapshot {
35 #[must_use]
36 pub fn new(name: String, description: Option<String>) -> Self {
37 Self {
38 id: uuid::Uuid::new_v4().to_string(),
39 name,
40 description,
41 created_at: Utc::now(),
42 variables: HashMap::new(),
43 metadata: HashMap::new(),
44 }
45 }
46
47 #[must_use]
48 pub fn from_vars(name: String, description: Option<String>, vars: Vec<EnvVar>) -> Self {
49 let mut snapshot = Self::new(name, description);
50 for var in vars {
51 snapshot.variables.insert(var.name.clone(), var);
52 }
53 snapshot
54 }
55}
56
57impl Profile {
58 #[must_use]
59 pub fn new(name: String, description: Option<String>) -> Self {
60 Self {
61 name,
62 description,
63 created_at: Utc::now(),
64 updated_at: Utc::now(),
65 variables: HashMap::new(),
66 parent: None,
67 metadata: HashMap::new(),
68 }
69 }
70
71 pub fn add_var(&mut self, name: String, value: String, override_system: bool) {
72 self.variables.insert(
73 name,
74 ProfileVar {
75 value,
76 enabled: true,
77 override_system,
78 },
79 );
80 self.updated_at = Utc::now();
81 }
82
83 pub fn remove_var(&mut self, name: &str) -> Option<ProfileVar> {
84 self.updated_at = Utc::now();
85 self.variables.remove(name)
86 }
87
88 #[must_use]
89 pub fn get_active_vars(&self) -> HashMap<String, String> {
90 self.variables
91 .iter()
92 .filter(|(_, var)| var.enabled)
93 .map(|(name, var)| (name.clone(), var.value.clone()))
94 .collect()
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use crate::{EnvVar, EnvVarSource};
102
103 fn create_test_env_var(name: &str, value: &str) -> EnvVar {
104 EnvVar {
105 name: name.to_string(),
106 value: value.to_string(),
107 source: EnvVarSource::User,
108 modified: Utc::now(),
109 original_value: None,
110 }
111 }
112
113 #[test]
114 fn test_snapshot_new() {
115 let name = "test-snapshot".to_string();
116 let description = Some("Test description".to_string());
117 let snapshot = Snapshot::new(name.clone(), description.clone());
118
119 assert_eq!(snapshot.name, name);
120 assert_eq!(snapshot.description, description);
121 assert!(!snapshot.id.is_empty());
122 assert!(snapshot.variables.is_empty());
123 assert!(snapshot.metadata.is_empty());
124 assert!(snapshot.created_at <= Utc::now());
125 }
126
127 #[test]
128 fn test_snapshot_new_without_description() {
129 let name = "test-snapshot".to_string();
130 let snapshot = Snapshot::new(name.clone(), None);
131
132 assert_eq!(snapshot.name, name);
133 assert!(snapshot.description.is_none());
134 }
135
136 #[test]
137 fn test_snapshot_unique_ids() {
138 let snapshot1 = Snapshot::new("snap1".to_string(), None);
139 let snapshot2 = Snapshot::new("snap2".to_string(), None);
140
141 assert_ne!(snapshot1.id, snapshot2.id);
142 }
143
144 #[test]
145 fn test_snapshot_from_vars_empty() {
146 let vars = vec![];
147 let snapshot = Snapshot::from_vars("empty-snapshot".to_string(), None, vars);
148
149 assert_eq!(snapshot.name, "empty-snapshot");
150 assert!(snapshot.variables.is_empty());
151 }
152
153 #[test]
154 fn test_snapshot_from_vars_single() {
155 let var = create_test_env_var("TEST_VAR", "test_value");
156 let vars = vec![var.clone()];
157 let snapshot = Snapshot::from_vars("single-var-snapshot".to_string(), None, vars);
158
159 assert_eq!(snapshot.variables.len(), 1);
160 assert!(snapshot.variables.contains_key("TEST_VAR"));
161
162 let stored_var = snapshot.variables.get("TEST_VAR").unwrap();
163 assert_eq!(stored_var.name, var.name);
164 assert_eq!(stored_var.value, var.value);
165 }
166
167 #[test]
168 fn test_snapshot_from_vars_multiple() {
169 let vars = vec![
170 create_test_env_var("VAR1", "value1"),
171 create_test_env_var("VAR2", "value2"),
172 create_test_env_var("VAR3", "value3"),
173 ];
174 let snapshot = Snapshot::from_vars(
175 "multi-var-snapshot".to_string(),
176 Some("Multiple variables".to_string()),
177 vars,
178 );
179
180 assert_eq!(snapshot.variables.len(), 3);
181 assert!(snapshot.variables.contains_key("VAR1"));
182 assert!(snapshot.variables.contains_key("VAR2"));
183 assert!(snapshot.variables.contains_key("VAR3"));
184 assert_eq!(snapshot.description, Some("Multiple variables".to_string()));
185 }
186
187 #[test]
188 fn test_snapshot_from_vars_duplicate_names() {
189 let vars = vec![
191 create_test_env_var("DUPLICATE", "first_value"),
192 create_test_env_var("DUPLICATE", "second_value"),
193 ];
194 let snapshot = Snapshot::from_vars("duplicate-test".to_string(), None, vars);
195
196 assert_eq!(snapshot.variables.len(), 1);
197 assert_eq!(snapshot.variables.get("DUPLICATE").unwrap().value, "second_value");
198 }
199
200 #[test]
201 fn test_snapshot_serialization() {
202 let mut snapshot = Snapshot::new("serialize-test".to_string(), Some("Test serialization".to_string()));
203 snapshot.metadata.insert("key1".to_string(), "value1".to_string());
204 snapshot.metadata.insert("key2".to_string(), "value2".to_string());
205
206 let var = create_test_env_var("TEST_VAR", "test_value");
207 snapshot.variables.insert(var.name.clone(), var);
208
209 let json = serde_json::to_string(&snapshot).expect("Failed to serialize snapshot to JSON");
211
212 let deserialized: Snapshot = serde_json::from_str(&json).expect("Failed to deserialize snapshot from JSON");
214
215 assert_eq!(deserialized.id, snapshot.id);
216 assert_eq!(deserialized.name, snapshot.name);
217 assert_eq!(deserialized.description, snapshot.description);
218 assert_eq!(deserialized.variables.len(), snapshot.variables.len());
219 assert_eq!(deserialized.metadata.len(), snapshot.metadata.len());
220 assert_eq!(deserialized.metadata.get("key1"), Some(&"value1".to_string()));
221 }
222
223 #[test]
224 fn test_snapshot_with_special_characters() {
225 let name = "snapshot-with-special-chars!@#$%^&*()".to_string();
226 let description = Some("Description with\nnewlines\tand\ttabs".to_string());
227 let snapshot = Snapshot::new(name.clone(), description.clone());
228
229 assert_eq!(snapshot.name, name);
230 assert_eq!(snapshot.description, description);
231 }
232
233 #[test]
234 fn test_snapshot_with_empty_name() {
235 let snapshot = Snapshot::new(String::new(), None);
237 assert_eq!(snapshot.name, "");
238 }
239
240 #[test]
241 fn test_snapshot_from_vars_preserves_all_env_var_fields() {
242 let mut var = create_test_env_var("FULL_VAR", "full_value");
243 var.source = EnvVarSource::System;
244 var.original_value = Some("original".to_string());
245
246 let vars = vec![var];
247 let snapshot = Snapshot::from_vars("preserve-test".to_string(), None, vars);
248
249 let stored_var = snapshot.variables.get("FULL_VAR").unwrap();
250 assert_eq!(stored_var.source, EnvVarSource::System);
251 assert_eq!(stored_var.original_value, Some("original".to_string()));
252 }
253
254 #[test]
255 fn test_snapshot_metadata_operations() {
256 let mut snapshot = Snapshot::new("metadata-test".to_string(), None);
257
258 assert!(snapshot.metadata.is_empty());
260
261 snapshot.metadata.insert("author".to_string(), "test_user".to_string());
263 snapshot.metadata.insert("version".to_string(), "1.0.0".to_string());
264
265 assert_eq!(snapshot.metadata.len(), 2);
266 assert_eq!(snapshot.metadata.get("author"), Some(&"test_user".to_string()));
267 assert_eq!(snapshot.metadata.get("version"), Some(&"1.0.0".to_string()));
268 }
269
270 #[test]
271 fn test_snapshot_clone() {
272 let mut original = Snapshot::new("original".to_string(), Some("Original snapshot".to_string()));
273 original
274 .variables
275 .insert("VAR1".to_string(), create_test_env_var("VAR1", "value1"));
276 original.metadata.insert("key".to_string(), "value".to_string());
277
278 let cloned = original.clone();
279
280 assert_eq!(cloned.id, original.id);
281 assert_eq!(cloned.name, original.name);
282 assert_eq!(cloned.description, original.description);
283 assert_eq!(cloned.created_at, original.created_at);
284 assert_eq!(cloned.variables.len(), original.variables.len());
285 assert_eq!(cloned.metadata.len(), original.metadata.len());
286 }
287
288 #[test]
289 fn test_snapshot_debug_format() {
290 let snapshot = Snapshot::new("debug-test".to_string(), None);
291 let debug_str = format!("{snapshot:?}");
292
293 assert!(debug_str.contains("Snapshot"));
294 assert!(debug_str.contains("debug-test"));
295 assert!(debug_str.contains(&snapshot.id));
296 }
297}