1use super::traits::{DbEnv, FileEnv, GitEnv, ProcessEnv};
7use anyhow::Result;
8use std::collections::HashMap;
9use std::fs::Metadata;
10use std::path::{Path, PathBuf};
11use std::process::{Child, Command, Output};
12use std::sync::{Arc, Mutex};
13
14#[derive(Debug, Clone)]
31pub struct MockFileEnv {
32 files: Arc<Mutex<HashMap<PathBuf, String>>>,
33}
34
35impl MockFileEnv {
36 pub fn new() -> Self {
37 Self {
38 files: Arc::new(Mutex::new(HashMap::new())),
39 }
40 }
41
42 pub fn add_file(&self, path: impl Into<PathBuf>, content: impl Into<String>) {
44 self.files
45 .lock()
46 .unwrap()
47 .insert(path.into(), content.into());
48 }
49
50 pub fn files(&self) -> HashMap<PathBuf, String> {
52 self.files.lock().unwrap().clone()
53 }
54
55 pub fn clear(&self) {
57 self.files.lock().unwrap().clear();
58 }
59}
60
61impl Default for MockFileEnv {
62 fn default() -> Self {
63 Self::new()
64 }
65}
66
67impl FileEnv for MockFileEnv {
68 fn read_to_string(&self, path: &Path) -> std::io::Result<String> {
69 self.files
70 .lock()
71 .unwrap()
72 .get(path)
73 .cloned()
74 .ok_or_else(|| {
75 std::io::Error::new(
76 std::io::ErrorKind::NotFound,
77 format!("File not found: {}", path.display()),
78 )
79 })
80 }
81
82 fn write(&self, path: &Path, content: &str) -> std::io::Result<()> {
83 self.files
84 .lock()
85 .unwrap()
86 .insert(path.to_path_buf(), content.to_string());
87 Ok(())
88 }
89
90 fn exists(&self, path: &Path) -> bool {
91 self.files.lock().unwrap().contains_key(path)
92 }
93
94 fn metadata(&self, _path: &Path) -> std::io::Result<Metadata> {
95 Err(std::io::Error::new(
98 std::io::ErrorKind::Unsupported,
99 "Metadata not supported in mock file system",
100 ))
101 }
102
103 fn create_dir_all(&self, _path: &Path) -> std::io::Result<()> {
104 Ok(())
106 }
107
108 fn remove_file(&self, path: &Path) -> std::io::Result<()> {
109 self.files
110 .lock()
111 .unwrap()
112 .remove(path)
113 .ok_or_else(|| {
114 std::io::Error::new(
115 std::io::ErrorKind::NotFound,
116 format!("File not found: {}", path.display()),
117 )
118 })
119 .map(|_| ())
120 }
121
122 fn remove_dir_all(&self, path: &Path) -> std::io::Result<()> {
123 let mut files = self.files.lock().unwrap();
125 files.retain(|p, _| !p.starts_with(path));
126 Ok(())
127 }
128}
129
130#[derive(Debug, Clone)]
134pub struct MockProcessEnv {
135 commands: Arc<Mutex<Vec<String>>>,
136 outputs: Arc<Mutex<HashMap<String, Output>>>,
137}
138
139impl MockProcessEnv {
140 pub fn new() -> Self {
141 Self {
142 commands: Arc::new(Mutex::new(Vec::new())),
143 outputs: Arc::new(Mutex::new(HashMap::new())),
144 }
145 }
146
147 fn record_command(&self, cmd: &Command) {
149 let cmd_str = format!("{:?}", cmd);
150 self.commands.lock().unwrap().push(cmd_str);
151 }
152
153 pub fn commands(&self) -> Vec<String> {
155 self.commands.lock().unwrap().clone()
156 }
157
158 pub fn set_output(&self, cmd_pattern: impl Into<String>, output: Output) {
160 self.outputs
161 .lock()
162 .unwrap()
163 .insert(cmd_pattern.into(), output);
164 }
165}
166
167impl Default for MockProcessEnv {
168 fn default() -> Self {
169 Self::new()
170 }
171}
172
173impl ProcessEnv for MockProcessEnv {
174 fn spawn(&self, cmd: &mut Command) -> std::io::Result<Child> {
175 self.record_command(cmd);
176 Err(std::io::Error::new(
178 std::io::ErrorKind::Unsupported,
179 "Spawn not supported in mock environment, use run() instead",
180 ))
181 }
182
183 fn run(&self, cmd: &mut Command) -> std::io::Result<Output> {
184 self.record_command(cmd);
185
186 let cmd_str = format!("{:?}", cmd);
188 if let Some(output) = self.outputs.lock().unwrap().get(&cmd_str) {
189 return Ok(output.clone());
190 }
191
192 Ok(Output {
194 status: std::process::ExitStatus::default(),
195 stdout: Vec::new(),
196 stderr: Vec::new(),
197 })
198 }
199}
200
201#[derive(Debug, Clone)]
205pub struct MockGitEnv {
206 operations: Arc<Mutex<Vec<String>>>,
207 branches: Arc<Mutex<Vec<String>>>,
208 current_branch: Arc<Mutex<String>>,
209 head_sha: Arc<Mutex<String>>,
210 is_clean: Arc<Mutex<bool>>,
211}
212
213impl MockGitEnv {
214 pub fn new() -> Self {
215 Self {
216 operations: Arc::new(Mutex::new(Vec::new())),
217 branches: Arc::new(Mutex::new(vec!["main".to_string()])),
218 current_branch: Arc::new(Mutex::new("main".to_string())),
219 head_sha: Arc::new(Mutex::new("abc123".to_string())),
220 is_clean: Arc::new(Mutex::new(true)),
221 }
222 }
223
224 fn record_operation(&self, operation: String) {
226 self.operations.lock().unwrap().push(operation);
227 }
228
229 pub fn operations(&self) -> Vec<String> {
231 self.operations.lock().unwrap().clone()
232 }
233
234 pub fn set_current_branch(&self, branch: impl Into<String>) {
236 *self.current_branch.lock().unwrap() = branch.into();
237 }
238
239 pub fn set_head_sha(&self, sha: impl Into<String>) {
241 *self.head_sha.lock().unwrap() = sha.into();
242 }
243
244 pub fn set_is_clean(&self, clean: bool) {
246 *self.is_clean.lock().unwrap() = clean;
247 }
248}
249
250impl Default for MockGitEnv {
251 fn default() -> Self {
252 Self::new()
253 }
254}
255
256impl GitEnv for MockGitEnv {
257 fn worktree_add(&self, path: &Path, branch: &str) -> Result<()> {
258 self.record_operation(format!("worktree add {} {}", path.display(), branch));
259 self.branches.lock().unwrap().push(branch.to_string());
260 Ok(())
261 }
262
263 fn worktree_remove(&self, path: &Path) -> Result<()> {
264 self.record_operation(format!("worktree remove {}", path.display()));
265 Ok(())
266 }
267
268 fn worktree_list(&self) -> Result<Vec<String>> {
269 self.record_operation("worktree list".to_string());
270 Ok(self.branches.lock().unwrap().clone())
271 }
272
273 fn merge(&self, branch: &str) -> Result<()> {
274 self.record_operation(format!("merge {}", branch));
275 Ok(())
276 }
277
278 fn commit(&self, message: &str) -> Result<String> {
279 self.record_operation(format!("commit: {}", message));
280 let sha = self.head_sha.lock().unwrap().clone();
281 Ok(sha)
282 }
283
284 fn head_sha(&self) -> Result<String> {
285 Ok(self.head_sha.lock().unwrap().clone())
286 }
287
288 fn create_branch(&self, name: &str) -> Result<()> {
289 self.record_operation(format!("create branch {}", name));
290 self.branches.lock().unwrap().push(name.to_string());
291 Ok(())
292 }
293
294 fn checkout(&self, branch: &str) -> Result<()> {
295 self.record_operation(format!("checkout {}", branch));
296 *self.current_branch.lock().unwrap() = branch.to_string();
297 Ok(())
298 }
299
300 fn current_branch(&self) -> Result<String> {
301 Ok(self.current_branch.lock().unwrap().clone())
302 }
303
304 fn is_clean(&self) -> Result<bool> {
305 Ok(*self.is_clean.lock().unwrap())
306 }
307}
308
309#[derive(Debug, Clone, Default)]
311pub struct MockDbEnv;
312
313impl MockDbEnv {
314 pub fn new() -> Self {
315 Self
316 }
317}
318
319impl DbEnv for MockDbEnv {
320 }