Skip to main content

vfs_kit/vfs/
map_fs.rs

1//! This module provides a virtual filesystem (VFS) implementation that maps to a memory storage.
2
3use std::collections::BTreeMap;
4use std::path::{Path, PathBuf};
5
6use anyhow::anyhow;
7
8use crate::core::{FsBackend, Result, utils};
9use crate::{Entry, EntryType};
10
11/// A virtual file system (VFS) implementation that stores file and directory entries in memory
12/// using a hierarchical map structure.
13///
14/// `MapFS` provides a POSIX‑like file system interface where all data is kept in‑process,
15/// allowing operations such as file creation, directory traversal, path resolution, and metadata
16/// inspection without touching the host filesystem.
17///
18/// ### Internal state
19///
20/// * `root` — An absolute, normalized path associated with the host that serves as the physical
21/// anchor of the virtual file system (VFS). It has no effect on VFS operation under typical usage
22/// scenarios. This path determines how virtual paths are mapped to host paths
23/// (e.g., for synchronization or persistent storage layers).
24///   - Must be absolute and normalized (no `..`, no redundant separators).
25///   - Example: `/tmp/my_vfs_root` on Unix, `C:\\vfs\\root` on Windows.
26///
27/// * `cwd` — Current Working Directory, expressed as an **inner absolute normalized path**
28///   within the virtual file system.
29///   - Determines how relative paths (e.g., `docs/file.txt`) are resolved.
30///   - Always starts with `/` (or `\` on Windows) and is normalized.
31///   - Default value: `/` (the virtual root).
32///   - Changed via methods like `cd()`.
33///
34/// * `entries` — The core storage map that holds all virtual file and directory entries.
35///   - Key: `PathBuf` representing **inner absolute normalized paths** (always start with `/`).
36///   - Value: `Entry` struct containing type, metadata, and (for files) content.
37///
38/// ### Invariants
39///
40/// 1. **Root existence**: The path `/` is always present in `entries` and has type `Directory`.
41/// 2. **Path normalization**: All keys in `entries`, as well as `cwd` and `root`, are normalized
42///    (no `..`, no `//`, trailing `/` removed except for root).
43/// 3. **Parent consistency**: For any entry at `/a/b/c`, there must exist an entry `/a/b` of type
44///    `Directory` (except for the root `/`).
45/// 4. **Uniqueness**: No duplicate paths; each path maps to exactly one `Entry`.
46///
47/// ### Lifecycle
48///
49/// - On creation: `root` and `cwd` is set to `/`.
50///   If you want, you may set `root` to a user‑supplied host path;
51/// - As files/directories are added via methods (e.g., `mkfile()`, `mkdir()`, `add()`), they are
52///   inserted into `entries` with inner absolute paths.
53/// - Path resolution (e.g., in `is_file()`, `ls()`) combines `cwd` with input paths to produce
54///   inner absolute paths before querying `entries`.
55///
56/// ### Thread Safety
57///
58/// This struct is **not thread‑safe by default**. If concurrent access is required, wrap it in
59/// a synchronization primitive (e.g., `Arc<Mutex<MapFS>>` or `RwLock<MapFS>`) at the application level.
60///
61/// ### Example
62///
63/// ```no_run
64/// let fs = MapFS::new();
65///
66/// fs.mkdir("/docs").unwrap();
67/// fs.mkfile("/docs/note.txt", Some(b"Hello")).unwrap();
68///
69/// assert!(fs.exists("/docs/note.txt"));
70///
71/// fs.rm("/docs/note.txt").unwrap();
72/// ```
73pub struct MapFS {
74    root: PathBuf,                     // host-related absolute normalized path
75    cwd: PathBuf,                      // inner absolute normalized path
76    entries: BTreeMap<PathBuf, Entry>, // inner absolute normalized paths
77}
78
79impl MapFS {
80    /// Creates new MapFS instance.
81    /// By default, the root directory and current working directory are set to `/`.
82    pub fn new() -> Self {
83        Self {
84            root: PathBuf::from("/"),
85            cwd: PathBuf::from("/"),
86            entries: BTreeMap::new(),
87        }
88    }
89
90    /// Changes root path.
91    /// * `path` must be an absolute
92    /// If `path` isn't an absolute error returns.
93    pub fn set_root<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
94        let path = path.as_ref();
95        if !path.is_absolute() {
96            return Err(anyhow!("root path must be an absolute"));
97        }
98        self.root = path.to_path_buf();
99        Ok(())
100    }
101
102    fn to_inner<P: AsRef<Path>>(&self, inner_path: P) -> PathBuf {
103        utils::normalize(self.cwd.join(inner_path))
104    }
105}
106
107impl FsBackend for MapFS {
108    /// Returns root path.
109    fn root(&self) -> &Path {
110        self.root.as_path()
111    }
112
113    /// Returns current working directory related to the vfs root.
114    fn cwd(&self) -> &Path {
115        self.cwd.as_path()
116    }
117
118    /// Returns a hypothetical "host-path" joining `root` and `inner_path`.
119    /// * `inner_path` must exist in VFS
120    fn to_host<P: AsRef<Path>>(&self, inner_path: P) -> Result<PathBuf> {
121        let inner = self.to_inner(inner_path);
122        Ok(self.root.join(inner.strip_prefix("/")?))
123    }
124
125    /// Changes the current working directory.
126    /// * `path` can be in relative or absolute form, but in both cases it must exist in VFS.
127    /// An error is returned if the specified `path` does not exist.
128    fn cd<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
129        let target = self.to_inner(path);
130        if !self.is_dir(&target)? {
131            return Err(anyhow!("{} not a directory", target.display()));
132        }
133        self.cwd = target;
134        Ok(())
135    }
136
137    /// Checks if a `path` exists in the VFS.
138    /// The `path` can be in relative or absolute form.
139    fn exists<P: AsRef<Path>>(&self, path: P) -> bool {
140        let inner = self.to_inner(path);
141        utils::is_virtual_root(&inner) || self.entries.contains_key(&inner)
142    }
143
144    /// Checks if `path` is a directory.
145    fn is_dir<P: AsRef<Path>>(&self, path: P) -> Result<bool> {
146        let path = path.as_ref();
147        let inner = self.to_inner(path);
148        if !self.exists(&inner) {
149            return Err(anyhow!("{} does not exist", path.display()));
150        }
151        Ok(utils::is_virtual_root(&inner) || self.entries[&inner].is_dir())
152    }
153
154    /// Checks if `path` is a regular file.
155    fn is_file<P: AsRef<Path>>(&self, path: P) -> Result<bool> {
156        let path = path.as_ref();
157        let inner = self.to_inner(path);
158        if !self.exists(&inner) {
159            return Err(anyhow!("{} does not exist", path.display()));
160        }
161        Ok(!utils::is_virtual_root(&inner) && self.entries[&inner].is_file())
162    }
163
164    /// Returns an iterator over directory entries at a specific depth (shallow listing).
165    ///
166    /// This method lists only the **immediate children** of the given directory,
167    /// i.e., entries that are exactly one level below the specified path.
168    /// It does *not* recurse into subdirectories (see `tree()` if you need recurse).
169    ///
170    /// # Arguments
171    /// * `path` - path to the directory to list (must exist in VFS).
172    ///
173    /// # Returns
174    /// * `Ok(impl Iterator<Item = &Path>)` - Iterator over entries of immediate children
175    ///   (relative to VFS root). The yielded paths are *inside* the target directory
176    ///   but do not include deeper nesting.
177    /// * `Err(anyhow::Error)` - If the specified path does not exist in VFS.
178    ///
179    /// # Example:
180    ///```no_run
181    /// fs.mkdir("/docs/subdir");
182    /// fs.mkfile("/docs/document.txt", None);
183    ///
184    /// // List root contents
185    /// for path in fs.ls("/").unwrap() {
186    ///     println!("{:?}", path);
187    /// }
188    ///
189    /// // List contents of "/docs"
190    /// for path in fs.ls("/docs").unwrap() {
191    ///     if path.is_file() {
192    ///         println!("File: {:?}", path);
193    ///     } else {
194    ///         println!("Dir:  {:?}", path);
195    ///     }
196    /// }
197    /// ```
198    ///
199    /// # Notes
200    /// - **No recursion:** Unlike `tree()`, this method does *not* traverse subdirectories.
201    /// - **Path ownership:** The returned iterator borrows from the VFS's internal state.
202    ///   It is valid as long as `self` lives.
203    /// - **Excludes root:** The input directory itself is not included in the output.
204    /// - **Error handling:** If `path` does not exist, an error is returned before iteration.
205    /// - **Performance:** The filtering is done in‑memory; no additional filesystem I/O occurs
206    ///   during iteration.
207    fn ls<P: AsRef<Path>>(&self, path: P) -> Result<impl Iterator<Item = &Path>> {
208        let inner_path = self.to_inner(path);
209        if !self.exists(&inner_path) {
210            return Err(anyhow!("{} does not exist", inner_path.display()));
211        }
212        let is_file = self.is_file(&inner_path)?;
213        let component_count = if is_file {
214            inner_path.components().count()
215        } else {
216            inner_path.components().count() + 1
217        };
218        Ok(self
219            .entries
220            .iter()
221            .map(|(pb, _)| pb.as_path())
222            .filter(move |&path| {
223                path.starts_with(&inner_path)
224                    && (path != inner_path || is_file)
225                    && path.components().count() == component_count
226            }))
227    }
228
229    /// Returns a recursive iterator over the directory tree starting from a given path.
230    ///
231    /// The iterator yields all entries (files and directories) that are *inside* the specified
232    /// directory (i.e., the starting directory itself is **not** included).
233    ///
234    /// # Arguments
235    /// * `path` - path to the directory to traverse (must exist in VFS).
236    ///
237    /// # Returns
238    /// * `Ok(impl Iterator<Item = &Path>)` - Iterator over all entries *within* the tree
239    ///   (relative to VFS root), excluding the root of the traversal.
240    /// * `Err(anyhow::Error)` - If:
241    ///   - The specified path does not exist in VFS.
242    ///   - The path is not a directory (implicitly checked via `exists` and tree structure).
243    ///
244    /// # Behavior
245    /// - **Recursive traversal**: Includes all nested files and directories.
246    /// - **Excludes root**: The starting directory path is not yielded (only its contents).
247    /// - **Path normalization**: Input path is normalized.
248    /// - **VFS-only**: Only returns paths tracked in VFS.
249    /// - **Performance:** The filtering is done in‑memory; no additional filesystem I/O occurs
250    ///   during iteration.
251    ///
252    /// # Example:
253    /// ```no_run
254    /// fs.mkdir("/docs/subdir");
255    /// fs.mkfile("/docs/document.txt", None);
256    ///
257    /// // Iterate over current working directory
258    /// for path in fs.tree("/").unwrap() {
259    ///     println!("{:?}", path);
260    /// }
261    ///
262    /// // Iterate over a specific directory
263    /// for path in fs.tree("/docs").unwrap() {
264    ///     if path.is_file() {
265    ///         println!("File: {:?}", path);
266    ///     }
267    /// }
268    /// ```
269    ///
270    /// # Notes
271    /// - The iterator borrows data from VFS. The returned iterator is valid as long
272    ///   as `self` is alive.
273    /// - Symbolic links are treated as regular entries (no follow/resolve).
274    /// - Use `MapFS` methods (e.g., `is_file()`, `is_dir()`) for yielded items for type checks.
275    fn tree<P: AsRef<Path>>(&self, path: P) -> Result<impl Iterator<Item = &Path>> {
276        let inner_path = self.to_inner(path);
277        if !self.exists(&inner_path) {
278            return Err(anyhow!("{} does not exist", inner_path.display()));
279        }
280        let is_file = self.is_file(&inner_path)?;
281        Ok(self
282            .entries
283            .iter()
284            .map(|(pb, _)| pb.as_path())
285            .filter(move |&path| path.starts_with(&inner_path) && (path != inner_path || is_file)))
286    }
287
288    /// Creates directory and all it parents (if needed).
289    /// * `path` - inner vfs path.
290    fn mkdir<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
291        if path.as_ref().as_os_str().is_empty() {
292            return Err(anyhow!("invalid path: empty"));
293        }
294
295        let inner_path = self.to_inner(path);
296
297        if self.exists(&inner_path) {
298            return Err(anyhow!("path already exists: {}", inner_path.display()));
299        }
300
301        // Looking for the first existing parent
302        let mut existed_parent = inner_path.clone();
303        while let Some(parent) = existed_parent.parent() {
304            let parent_buf = parent.to_path_buf();
305            if self.exists(parent) {
306                existed_parent = parent_buf;
307                break;
308            }
309            existed_parent = parent_buf;
310        }
311
312        // Create from the closest existing parent to the target path
313        let need_to_create: Vec<_> = inner_path
314            .strip_prefix(&existed_parent)?
315            .components()
316            .collect();
317
318        let mut built = PathBuf::from(&existed_parent);
319        for component in need_to_create {
320            built.push(component);
321            if !self.exists(&built) {
322                self.entries
323                    .insert(built.clone(), Entry::new(EntryType::Directory));
324            }
325        }
326
327        Ok(())
328    }
329
330    /// Creates new file in VFS.
331    /// * `file_path` must be inner VFS path. It must contain the name of the file,
332    /// optionally preceded by parent directory.
333    /// If the parent directory does not exist, it will be created.
334    fn mkfile<P: AsRef<Path>>(&mut self, file_path: P, content: Option<&[u8]>) -> Result<()> {
335        let file_path = self.to_inner(file_path);
336        if self.exists(&file_path) {
337            return Err(anyhow!("{} already exist", file_path.display()));
338        }
339        if let Some(parent) = file_path.parent() {
340            if !self.exists(parent) {
341                self.mkdir(parent)?;
342            }
343        }
344
345        let mut entry = Entry::new(EntryType::File);
346        if let Some(content) = content {
347            entry.set_content(content);
348        }
349        self.entries.insert(file_path.clone(), entry);
350
351        Ok(())
352    }
353
354    /// Reads the entire contents of a file into a byte vector.
355    /// * `path` is the inner VFS path.
356    ///
357    /// # Returns
358    /// * `Ok(Vec<u8>)` - File content as a byte vector if successful.
359    /// * `Err(anyhow::Error)` - If any of the following occurs:
360    ///   - File does not exist in VFS (`file does not exist: ...`)
361    ///   - Path points to a directory (`... is a directory`)
362    ///
363    /// # Notes
364    /// - Does **not** follow symbolic links on the host filesystem (reads the link itself).
365    /// - Returns an empty vector for empty files.
366    fn read<P: AsRef<Path>>(&self, path: P) -> Result<Vec<u8>> {
367        let path = path.as_ref();
368        if self.is_dir(path)? {
369            // checks for existent too
370            return Err(anyhow!("{} is a directory", path.display()));
371        }
372        Ok(self.entries[path].content().cloned().unwrap_or(Vec::new()))
373    }
374
375    /// Writes bytes to an existing file, replacing its entire contents.
376    /// * `path` - Path to the file.
377    /// * `content` - Byte slice (`&[u8]`) to write to the file.
378    ///
379    /// # Returns
380    /// * `Ok(())` - If the write operation succeeded.
381    /// * `Err(anyhow::Error)` - If any of the following occurs:
382    ///   - File does not exist in VFS (`file does not exist: ...`)
383    ///   - Path points to a directory (`... is a directory`)
384    ///
385    /// # Behavior
386    /// - **Overwrites completely**: The entire existing content is replaced.
387    /// - **No file creation**: File must exist (use `mkfile()` first).
388    fn write<P: AsRef<Path>>(&mut self, path: P, content: &[u8]) -> Result<()> {
389        let path = path.as_ref();
390        if self.is_dir(path)? {
391            // checks for existent too
392            return Err(anyhow!("{} is a directory", path.display()));
393        }
394        self.entries.get_mut(path).unwrap().set_content(content); // safe unwrap()
395        Ok(())
396    }
397
398    /// Appends bytes to the end of an existing file, preserving its old contents.
399    ///
400    /// # Arguments
401    /// * `path` - Path to the existing file.
402    /// * `content` - Byte slice (`&[u8]`) to append to the file.
403    ///
404    /// # Returns
405    /// * `Ok(())` - If the append operation succeeded.
406    /// * `Err(anyhow::Error)` - If any of the following occurs:
407    ///   - File does not exist in VFS (`file does not exist: ...`)
408    ///   - Path points to a directory (`... is a directory`)
409    ///
410    /// # Behavior
411    /// - **Appends only**: Existing content is preserved; new bytes are added at the end.
412    /// - **File creation**: Does NOT create the file if it doesn't exist (returns error).
413    fn append<P: AsRef<Path>>(&mut self, path: P, content: &[u8]) -> Result<()> {
414        let path = path.as_ref();
415        if self.is_dir(path)? {
416            // checks for existent too
417            return Err(anyhow!("{} is a directory", path.display()));
418        }
419        self.entries.get_mut(path).unwrap().append_content(content); // safe unwrap()
420        Ok(())
421    }
422
423    /// Removes a file or directory at the specified path.
424    ///
425    /// - `path`: can be absolute (starting with '/') or relative to the current working
426    /// directory (cwd). If the path is a directory, all its contents are removed recursively.
427    ///
428    /// Returns:
429    /// - `Ok(())` on successful removal.
430    /// - `Err(_)` if the path does not exist in the VFS;
431    fn rm<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
432        if path.as_ref().as_os_str().is_empty() {
433            return Err(anyhow!("invalid path: empty"));
434        }
435        if utils::is_virtual_root(&path) {
436            return Err(anyhow!("invalid path: the root cannot be removed"));
437        }
438
439        let inner_path = self.to_inner(path); // Convert to VFS-internal normalized path
440
441        // Check if the path exists in the virtual filesystem
442        if !self.exists(&inner_path) {
443            return Err(anyhow!("{} does not exist", inner_path.display()));
444        }
445
446        // Update internal state: collect all entries that start with `inner_path`
447        let removed: Vec<PathBuf> = self
448            .entries
449            .iter()
450            .map(|(pb, _)| pb)
451            .filter(|&pb| pb.starts_with(&inner_path)) // Match prefix (includes subpaths)
452            .cloned()
453            .collect();
454
455        // Remove all matched entries from the set
456        for p in &removed {
457            self.entries.remove(p);
458        }
459
460        Ok(())
461    }
462
463    /// Removes all artifacts (dirs and files) in vfs.
464    fn cleanup(&mut self) -> bool {
465        self.entries.clear();
466        true
467    }
468}
469
470#[cfg(test)]
471mod tests {
472    use super::*;
473
474    mod creations {
475        use super::*;
476
477        #[test]
478        fn test_new_map_fs() {
479            let mut fs = MapFS::new();
480            assert_eq!(fs.root(), "/");
481            assert_eq!(fs.cwd(), "/");
482
483            #[cfg(unix)]
484            {
485                fs.set_root("/new/root").unwrap();
486                assert_eq!(fs.root(), "/new/root");
487
488                let host_path = fs.to_host("/inner/path").unwrap();
489                assert_eq!(host_path.as_path(), "/new/root/inner/path");
490            }
491            #[cfg(windows)]
492            {
493                fs.set_root("c:\\new\\root").unwrap();
494                assert_eq!(fs.root(), "c:\\new\\root");
495
496                let host_path = fs.to_host("\\inner\\path").unwrap();
497                assert_eq!(host_path.as_path(), "c:\\new\\root\\inner\\path");
498            }
499
500            let result = fs.set_root("new/relative/root");
501            assert!(result.is_err());
502        }
503    }
504
505    mod cd {
506        use super::*;
507
508        /// Helper function to set up a test VFS with a predefined structure
509        fn setup_test_vfs() -> MapFS {
510            let mut vfs = MapFS::new(); // Assume MapFS has a new() constructor
511
512            // Create a sample directory structure
513            vfs.mkdir("/home").unwrap();
514            vfs.mkdir("/home/user").unwrap();
515            vfs.mkdir("/etc").unwrap();
516            vfs.mkfile("/home/user/config.txt", Some(b"Config content"))
517                .unwrap();
518
519            vfs
520        }
521
522        #[test]
523        fn test_cd_absolute_path_success() -> Result<()> {
524            let mut vfs = setup_test_vfs();
525
526            assert_eq!(vfs.cwd, Path::new("/")); // Initial CWD is root
527
528            vfs.cd("/home/user")?;
529
530            assert_eq!(vfs.cwd, Path::new("/home/user"));
531            Ok(())
532        }
533
534        #[test]
535        fn test_cd_relative_path_success() -> Result<()> {
536            let mut vfs = setup_test_vfs();
537
538            vfs.cd("/home")?; // Change to /home first
539            assert_eq!(vfs.cwd, Path::new("/home"));
540
541            vfs.cd("user")?; // Relative path from current CWD
542
543            assert_eq!(vfs.cwd, Path::new("/home/user"));
544            Ok(())
545        }
546
547        #[test]
548        fn test_cd_root_directory() -> Result<()> {
549            let mut vfs = setup_test_vfs();
550
551            vfs.cd("/")?;
552
553            assert_eq!(vfs.cwd, Path::new("/"));
554            Ok(())
555        }
556
557        #[test]
558        fn test_cd_nonexistent_path_error() -> Result<()> {
559            let mut vfs = setup_test_vfs();
560
561            let result = vfs.cd("/nonexistent/path");
562            assert!(result.is_err());
563            assert!(
564                result.unwrap_err().to_string().contains("does not exist"),
565                "Error message should indicate path does not exist"
566            );
567
568            // CWD should remain unchanged
569            assert_eq!(vfs.cwd, Path::new("/"));
570            Ok(())
571        }
572
573        #[test]
574        fn test_cd_file_path_error() -> Result<()> {
575            let mut vfs = setup_test_vfs();
576
577            let result = vfs.cd("/home/user/config.txt");
578            assert!(result.is_err());
579            assert!(
580                result.unwrap_err().to_string().contains("not a directory"),
581                "Even though the file exists, cd() should fail because it's not a directory"
582            );
583
584            // CWD should remain unchanged
585            assert_eq!(vfs.cwd, Path::new("/"));
586            Ok(())
587        }
588
589        #[test]
590        fn test_cd_same_directory() -> Result<()> {
591            let mut vfs = setup_test_vfs();
592
593            vfs.cd("/home")?;
594            assert_eq!(vfs.cwd, Path::new("/home"));
595
596            vfs.cd("/home")?; // CD to same directory
597
598            assert_eq!(vfs.cwd, Path::new("/home")); // Should remain unchanged
599            Ok(())
600        }
601
602        #[test]
603        fn test_cd_deep_nested_path() -> Result<()> {
604            let mut vfs = setup_test_vfs();
605
606            vfs.cd("/home/user")?;
607
608            assert_eq!(vfs.cwd, Path::new("/home/user"));
609            Ok(())
610        }
611
612        #[test]
613        fn test_cd_sequential_changes() -> Result<()> {
614            let mut vfs = setup_test_vfs();
615
616            vfs.cd("/etc")?;
617            assert_eq!(vfs.cwd, Path::new("/etc"));
618
619            vfs.cd("/home")?;
620            assert_eq!(vfs.cwd, Path::new("/home"));
621
622            vfs.cd("/")?;
623            assert_eq!(vfs.cwd, Path::new("/"));
624
625            Ok(())
626        }
627
628        #[test]
629        fn test_cd_with_trailing_slash() -> Result<()> {
630            let mut vfs = setup_test_vfs();
631
632            // Test that trailing slash is handled correctly
633            vfs.cd("/home/")?;
634            assert_eq!(vfs.cwd, Path::new("/home"));
635
636            vfs.cd("/home/user//")?;
637            assert_eq!(vfs.cwd, Path::new("/home/user"));
638            Ok(())
639        }
640    }
641
642    mod exists {
643        use super::*;
644
645        /// Helper to create a pre‑populated MapFS instance for testing
646        fn setup_test_vfs() -> MapFS {
647            let mut vfs = MapFS::new();
648
649            // Create a sample hierarchy
650            vfs.mkdir("/etc").unwrap();
651            vfs.mkdir("/home").unwrap();
652            vfs.mkdir("/home/user").unwrap();
653            vfs.mkfile("/home/user/file.txt", Some(b"Hello")).unwrap();
654            vfs.mkfile("/readme.md", Some(b"Project docs")).unwrap();
655
656            vfs
657        }
658
659        #[test]
660        fn test_exists_absolute_path_file() {
661            let vfs = setup_test_vfs();
662            assert!(vfs.exists("/home/user/file.txt"));
663        }
664
665        #[test]
666        fn test_exists_absolute_path_directory() {
667            let vfs = setup_test_vfs();
668            assert!(vfs.exists("/home/user"));
669        }
670
671        #[test]
672        fn test_exists_root_directory() {
673            let vfs = setup_test_vfs();
674            assert!(vfs.exists("/"));
675        }
676
677        #[test]
678        fn test_exists_relative_path_from_root() {
679            let vfs = setup_test_vfs();
680            // Current CWD is "/" by default
681            assert!(vfs.exists("home/user/file.txt"));
682        }
683
684        #[test]
685        fn test_exists_relative_path_nested() {
686            let mut vfs = setup_test_vfs();
687            vfs.cd("/home/user").unwrap(); // Change CWD
688            assert!(vfs.exists("file.txt")); // Relative to current CWD
689        }
690
691        #[test]
692        fn test_exists_nonexistent_file() {
693            let vfs = setup_test_vfs();
694            assert!(!vfs.exists("/home/user/nonexistent.txt"));
695        }
696
697        #[test]
698        fn test_exists_nonexistent_directory() {
699            let vfs = setup_test_vfs();
700            assert!(!vfs.exists("/tmp"));
701        }
702
703        #[test]
704        fn test_exists_partial_path() {
705            let vfs = setup_test_vfs();
706            // "/home/us" is not a complete path in our hierarchy
707            assert!(!vfs.exists("/home/us"));
708        }
709
710        #[test]
711        fn test_exists_with_trailing_slash() {
712            let vfs = setup_test_vfs();
713            assert!(vfs.exists("/home/")); // Should normalize to /home
714            assert!(vfs.exists("/home/user/")); // Should normalize to /home/user
715            assert!(vfs.exists("/readme.md/")); // File with trailing slash
716        }
717
718        #[test]
719        fn test_exists_case_sensitivity() {
720            #[cfg(unix)]
721            {
722                let mut vfs = setup_test_vfs();
723                // Add a mixed-case path
724                vfs.mkdir("/Home/User").unwrap();
725
726                assert!(vfs.exists("/Home/User"));
727                assert!(!vfs.exists("/home/User")); // Different case
728            }
729        }
730
731        #[test]
732        fn test_exists_empty_string() {
733            let vfs = setup_test_vfs();
734            // Empty string should resolve to CWD (which is "/")
735            assert!(vfs.exists(""));
736        }
737
738        #[test]
739        fn test_exists_dot_path() {
740            let vfs = setup_test_vfs();
741            assert!(vfs.exists(".")); // Current directory
742            assert!(vfs.exists("./home")); // Relative with dot
743        }
744
745        #[test]
746        fn test_exists_double_dot_path() {
747            let mut vfs = setup_test_vfs();
748            vfs.cd("/home/user").unwrap();
749            assert!(vfs.exists("..")); // Parent directory
750            assert!(vfs.exists("../user")); // Sibling
751            assert!(vfs.exists("../../etc")); // Up two levels
752        }
753    }
754
755    mod is_dir_file {
756        use super::*;
757
758        /// Helper to create a pre‑populated MapFS instance for testing
759        fn setup_test_vfs() -> MapFS {
760            let mut vfs = MapFS::new();
761
762            // Create a sample hierarchy
763            vfs.mkdir("/etc").unwrap();
764            vfs.mkdir("/home").unwrap();
765            vfs.mkdir("/home/user").unwrap();
766            vfs.mkfile("/home/user/file.txt", Some(b"Hello")).unwrap();
767            vfs.mkfile("/readme.md", Some(b"Project docs")).unwrap();
768            vfs.mkfile("/empty.bin", None).unwrap(); // Empty file
769
770            vfs
771        }
772
773        #[test]
774        fn test_is_dir_existing_directory_absolute() -> Result<()> {
775            let vfs = setup_test_vfs();
776            assert!(vfs.is_dir("/home")?);
777            assert!(vfs.is_dir("/home/user")?);
778            assert!(vfs.is_dir("/")?); // Root
779            Ok(())
780        }
781
782        #[test]
783        fn test_is_dir_existing_directory_relative() -> Result<()> {
784            let vfs = setup_test_vfs();
785            // From root
786            assert!(vfs.is_dir("home")?);
787            // After changing CWD
788            let mut vfs2 = setup_test_vfs();
789            vfs2.cd("/home").unwrap();
790            assert!(vfs2.is_dir("user")?);
791            Ok(())
792        }
793
794        #[test]
795        fn test_is_dir_file_path() -> Result<()> {
796            let vfs = setup_test_vfs();
797            assert!(!vfs.is_dir("/home/user/file.txt")?);
798            assert!(!vfs.is_dir("/readme.md")?);
799            Ok(())
800        }
801
802        #[test]
803        fn test_is_dir_nonexistent_path() {
804            let vfs = setup_test_vfs();
805            let result = vfs.is_dir("/nonexistent");
806            assert!(result.is_err());
807            assert!(
808                result.unwrap_err().to_string().contains("does not exist"),
809                "Error should mention path does not exist"
810            );
811        }
812
813        #[test]
814        fn test_is_file_existing_file_absolute() -> Result<()> {
815            let vfs = setup_test_vfs();
816            assert!(vfs.is_file("/home/user/file.txt")?);
817            assert!(vfs.is_file("/readme.md")?);
818            assert!(vfs.is_file("/empty.bin")?); // Empty file is still a file
819            Ok(())
820        }
821
822        #[test]
823        fn test_is_file_existing_file_relative() -> Result<()> {
824            let vfs = setup_test_vfs();
825            // From root
826            assert!(vfs.is_file("readme.md")?);
827            // After changing CWD
828            let mut vfs2 = setup_test_vfs();
829            vfs2.cd("/home/user").unwrap();
830            assert!(vfs2.is_file("file.txt")?);
831            Ok(())
832        }
833
834        #[test]
835        fn test_is_file_directory_path() -> Result<()> {
836            let vfs = setup_test_vfs();
837            assert!(!vfs.is_file("/home")?);
838            assert!(!vfs.is_file("/home/user")?);
839            assert!(!vfs.is_file("/")?); // Root is a directory
840            Ok(())
841        }
842
843        #[test]
844        fn test_is_file_nonexistent_path() {
845            let vfs = setup_test_vfs();
846            let result = vfs.is_file("/nonexistent.txt");
847            assert!(result.is_err());
848            assert!(
849                result.unwrap_err().to_string().contains("does not exist"),
850                "Error should mention path does not exist"
851            );
852        }
853
854        #[test]
855        fn test_is_dir_and_is_file_on_same_file() -> Result<()> {
856            let vfs = setup_test_vfs();
857            let file_path = "/home/user/file.txt";
858
859            assert!(!vfs.is_dir(file_path)?); // Not a directory
860            assert!(vfs.is_file(file_path)?); // Is a file
861
862            Ok(())
863        }
864
865        #[test]
866        fn test_is_dir_and_is_file_on_same_directory() -> Result<()> {
867            let vfs = setup_test_vfs();
868            let dir_path = "/home/user";
869
870            assert!(vfs.is_dir(dir_path)?); // Is a directory
871            assert!(!vfs.is_file(dir_path)?); // Not a file
872
873            Ok(())
874        }
875
876        #[test]
877        fn test_is_dir_with_trailing_slash() -> Result<()> {
878            let vfs = setup_test_vfs();
879            assert!(vfs.is_dir("/home/")?); // Trailing slash
880            assert!(vfs.is_dir("/home/user/")?);
881            Ok(())
882        }
883
884        #[test]
885        fn test_is_file_with_trailing_slash() -> Result<()> {
886            let vfs = setup_test_vfs();
887            // Even with trailing slash, it should still be recognized as a file
888            assert!(vfs.is_file("/readme.md/")?);
889            assert!(vfs.is_file("/home/user/file.txt/")?);
890            Ok(())
891        }
892
893        #[test]
894        fn test_is_dir_dot_path() -> Result<()> {
895            let mut vfs = setup_test_vfs();
896            vfs.cd("/home").unwrap();
897
898            assert!(vfs.is_dir(".")?); // Current directory
899            assert!(vfs.is_dir("./user")?); // Subdirectory
900            Ok(())
901        }
902
903        #[test]
904        fn test_is_file_dot_path() -> Result<()> {
905            let mut vfs = setup_test_vfs();
906            vfs.cd("/home/user").unwrap();
907
908            assert!(vfs.is_file("./file.txt")?);
909            Ok(())
910        }
911
912        #[test]
913        fn test_is_dir_double_dot_path() -> Result<()> {
914            let mut vfs = setup_test_vfs();
915            vfs.cd("/home/user").unwrap();
916
917            assert!(vfs.is_dir("..")?); // Parent (/home)
918
919            let result = vfs.is_dir("../etc");
920            assert!(result.is_err()); // Sibling directory (not existed)
921            // Note: ../etc doesn't exist in our setup, so this would fail
922            // But .. itself should pass
923            Ok(())
924        }
925    }
926
927    mod ls {
928        use super::*;
929
930        /// Helper to create a pre‑populated MapFS instance for testing
931        fn setup_test_vfs() -> MapFS {
932            let mut vfs = MapFS::new();
933
934            // Create a sample hierarchy
935            vfs.mkdir("/etc").unwrap();
936            vfs.mkdir("/home").unwrap();
937            vfs.mkdir("/home/user").unwrap();
938            vfs.mkdir("/home/guest").unwrap();
939            vfs.mkfile("/home/user/file1.txt", Some(b"Content 1"))
940                .unwrap();
941            vfs.mkfile("/home/user/file2.txt", Some(b"Content 2"))
942                .unwrap();
943            vfs.mkfile("/home/guest/note.txt", Some(b"Note")).unwrap();
944            vfs.mkfile("/readme.md", Some(b"Docs")).unwrap();
945
946            vfs
947        }
948
949        #[test]
950        fn test_ls_root_directory() -> Result<()> {
951            let vfs = setup_test_vfs();
952            let entries: Vec<_> = vfs.ls("/")?.collect();
953
954            assert_eq!(entries.len(), 3);
955            assert!(entries.contains(&Path::new("/etc")));
956            assert!(entries.contains(&Path::new("/home")));
957            assert!(entries.contains(&Path::new("/readme.md")));
958
959            Ok(())
960        }
961
962        #[test]
963        fn test_ls_home_directory() -> Result<()> {
964            let vfs = setup_test_vfs();
965            let entries: Vec<_> = vfs.ls("/home")?.collect();
966
967            assert_eq!(entries.len(), 2);
968            assert!(entries.contains(&Path::new("/home/user")));
969            assert!(entries.contains(&Path::new("/home/guest")));
970
971            Ok(())
972        }
973
974        #[test]
975        fn test_ls_user_directory() -> Result<()> {
976            let vfs = setup_test_vfs();
977            let entries: Vec<_> = vfs.ls("/home/user")?.collect();
978
979            assert_eq!(entries.len(), 2);
980            assert!(entries.contains(&Path::new("/home/user/file1.txt")));
981            assert!(entries.contains(&Path::new("/home/user/file2.txt")));
982
983            Ok(())
984        }
985
986        #[test]
987        fn test_ls_nonexistent_path() {
988            let vfs = setup_test_vfs();
989            let result: Result<Vec<_>> = vfs.ls("/nonexistent").map(|iter| iter.collect());
990            assert!(result.is_err());
991            assert!(
992                result.unwrap_err().to_string().contains("does not exist"),
993                "Error should mention path does not exist"
994            );
995        }
996
997        #[test]
998        fn test_ls_file_path() {
999            let vfs = setup_test_vfs();
1000            let result: Result<Vec<_>> = vfs.ls("/home/user/file1.txt").map(|iter| iter.collect());
1001            assert!(result.is_ok());
1002            assert_eq!(result.unwrap(), vec!["/home/user/file1.txt"]);
1003        }
1004
1005        #[test]
1006        fn test_ls_empty_directory() -> Result<()> {
1007            let mut vfs = setup_test_vfs();
1008            vfs.mkdir("/empty_dir").unwrap(); // Create empty dir
1009
1010            let entries: Vec<_> = vfs.ls("/empty_dir")?.collect();
1011            assert_eq!(entries.len(), 0); // Should return empty iterator
1012
1013            Ok(())
1014        }
1015
1016        #[test]
1017        fn test_ls_relative_path_from_root() -> Result<()> {
1018            let vfs = setup_test_vfs();
1019            let entries: Vec<_> = vfs.ls("home")?.collect(); // Relative path
1020
1021            assert_eq!(entries.len(), 2);
1022            assert!(entries.contains(&Path::new("/home/user")));
1023            assert!(entries.contains(&Path::new("/home/guest")));
1024
1025            Ok(())
1026        }
1027
1028        #[test]
1029        fn test_ls_relative_path_nested() -> Result<()> {
1030            let mut vfs = setup_test_vfs();
1031            vfs.cd("/home").unwrap();
1032
1033            let entries: Vec<_> = vfs.ls("user")?.collect();
1034
1035            assert_eq!(entries.len(), 2);
1036            assert!(entries.contains(&Path::new("/home/user/file1.txt")));
1037            assert!(entries.contains(&Path::new("/home/user/file2.txt")));
1038
1039            Ok(())
1040        }
1041
1042        #[test]
1043        fn test_ls_with_trailing_slash() -> Result<()> {
1044            let vfs = setup_test_vfs();
1045            let entries1: Vec<_> = vfs.ls("/home/")?.collect(); // With slash
1046            let entries2: Vec<_> = vfs.ls("/home")?.collect(); // Without slash
1047
1048            assert_eq!(entries1, entries2); // Results should be identical
1049            Ok(())
1050        }
1051
1052        #[test]
1053        fn test_ls_dot_path() -> Result<()> {
1054            let mut vfs = setup_test_vfs();
1055            vfs.cd("/home/user").unwrap();
1056
1057            let entries: Vec<_> = vfs.ls(".")?.collect();
1058            assert_eq!(entries.len(), 2);
1059            assert!(entries.contains(&Path::new("/home/user/file1.txt")));
1060            assert!(entries.contains(&Path::new("/home/user/file2.txt")));
1061
1062            Ok(())
1063        }
1064
1065        #[test]
1066        fn test_ls_double_dot_path() -> Result<()> {
1067            let mut vfs = setup_test_vfs();
1068            vfs.cd("/home/user").unwrap();
1069
1070            let entries: Vec<_> = vfs.ls("..")?.collect(); // Parent directory
1071            assert_eq!(entries.len(), 2);
1072            assert!(entries.contains(&Path::new("/home/user")));
1073            assert!(entries.contains(&Path::new("/home/guest")));
1074
1075            Ok(())
1076        }
1077
1078        #[test]
1079        fn test_ls_iterator_lazy_evaluation() -> Result<()> {
1080            let vfs = setup_test_vfs();
1081            let mut iter = vfs.ls("/home/user")?;
1082
1083            // Test that iterator doesn't panic on immediate creation
1084            assert!(iter.next().is_some());
1085
1086            // Consume all items
1087            let count = iter.count();
1088            assert_eq!(count + 1, 2); // +1 because we already took one with next()
1089
1090            Ok(())
1091        }
1092    }
1093
1094    mod tree {
1095        use super::*;
1096
1097        /// Helper to create a pre‑populated MapFS instance for testing
1098        fn setup_test_vfs() -> MapFS {
1099            let mut vfs = MapFS::new();
1100
1101            // Create a nested hierarchy
1102            vfs.mkdir("/etc").unwrap();
1103            vfs.mkdir("/home").unwrap();
1104            vfs.mkdir("/home/user").unwrap();
1105            vfs.mkdir("/home/user/projects").unwrap();
1106            vfs.mkdir("/home/guest").unwrap();
1107            vfs.mkfile("/home/user/file1.txt", Some(b"Content 1"))
1108                .unwrap();
1109            vfs.mkfile("/home/user/projects/proj1.rs", Some(b"Code 1"))
1110                .unwrap();
1111            vfs.mkfile("/home/user/projects/proj2.rs", Some(b"Code 2"))
1112                .unwrap();
1113            vfs.mkfile("/home/guest/note.txt", Some(b"Note")).unwrap();
1114            vfs.mkfile("/readme.md", Some(b"Docs")).unwrap();
1115
1116            vfs
1117        }
1118
1119        #[test]
1120        fn test_tree_root() -> Result<()> {
1121            let vfs = setup_test_vfs();
1122            let entries: Vec<_> = vfs.tree("/")?.collect();
1123
1124            assert_eq!(entries.len(), 10);
1125            assert!(entries.contains(&Path::new("/etc")));
1126            assert!(entries.contains(&Path::new("/home")));
1127            assert!(entries.contains(&Path::new("/home/user")));
1128            assert!(entries.contains(&Path::new("/home/user/file1.txt")));
1129            assert!(entries.contains(&Path::new("/home/user/projects")));
1130            assert!(entries.contains(&Path::new("/home/user/projects/proj1.rs")));
1131            assert!(entries.contains(&Path::new("/home/user/projects/proj2.rs")));
1132            assert!(entries.contains(&Path::new("/home/guest")));
1133            assert!(entries.contains(&Path::new("/home/guest/note.txt")));
1134
1135            Ok(())
1136        }
1137
1138        #[test]
1139        fn test_tree_home_directory() -> Result<()> {
1140            let vfs = setup_test_vfs();
1141            let entries: Vec<_> = vfs.tree("/home")?.collect();
1142
1143            assert_eq!(entries.len(), 7);
1144            assert!(entries.contains(&Path::new("/home/user")));
1145            assert!(entries.contains(&Path::new("/home/user/file1.txt")));
1146            assert!(entries.contains(&Path::new("/home/user/projects")));
1147            assert!(entries.contains(&Path::new("/home/user/projects/proj1.rs")));
1148            assert!(entries.contains(&Path::new("/home/user/projects/proj2.rs")));
1149            assert!(entries.contains(&Path::new("/home/guest")));
1150            assert!(entries.contains(&Path::new("/home/guest/note.txt")));
1151
1152            Ok(())
1153        }
1154
1155        #[test]
1156        fn test_tree_user_directory() -> Result<()> {
1157            let vfs = setup_test_vfs();
1158            let entries: Vec<_> = vfs.tree("/home/user")?.collect();
1159
1160            assert_eq!(entries.len(), 4);
1161            assert!(entries.contains(&Path::new("/home/user/file1.txt")));
1162            assert!(entries.contains(&Path::new("/home/user/projects")));
1163            assert!(entries.contains(&Path::new("/home/user/projects/proj1.rs")));
1164            assert!(entries.contains(&Path::new("/home/user/projects/proj2.rs")));
1165
1166            Ok(())
1167        }
1168
1169        #[test]
1170        fn test_tree_nonexistent_path() {
1171            let vfs = setup_test_vfs();
1172            let result: Result<Vec<_>> = vfs.tree("/nonexistent").map(|iter| iter.collect());
1173            assert!(result.is_err());
1174            assert!(
1175                result.unwrap_err().to_string().contains("does not exist"),
1176                "Error should mention path does not exist"
1177            );
1178        }
1179
1180        #[test]
1181        fn test_tree_file_path() {
1182            let vfs = setup_test_vfs();
1183            let result: Result<Vec<_>> =
1184                vfs.tree("/home/user/file1.txt").map(|iter| iter.collect());
1185            assert!(result.is_ok());
1186            assert_eq!(result.unwrap(), vec!["/home/user/file1.txt"]);
1187        }
1188
1189        #[test]
1190        fn test_tree_empty_directory() -> Result<()> {
1191            let mut vfs = setup_test_vfs();
1192            vfs.mkdir("/empty_dir").unwrap();
1193
1194            let entries: Vec<_> = vfs.tree("/empty_dir")?.collect();
1195            assert_eq!(entries.len(), 0); // Empty directory → empty iterator
1196
1197            Ok(())
1198        }
1199
1200        #[test]
1201        fn test_tree_relative_path_from_root() -> Result<()> {
1202            let vfs = setup_test_vfs();
1203            let entries: Vec<_> = vfs.tree("home")?.collect(); // Relative path
1204
1205            assert_eq!(entries.len(), 7);
1206            assert!(entries.contains(&Path::new("/home/user")));
1207            assert!(entries.contains(&Path::new("/home/user/file1.txt")));
1208            assert!(entries.contains(&Path::new("/home/user/projects")));
1209            assert!(entries.contains(&Path::new("/home/user/projects/proj1.rs")));
1210            assert!(entries.contains(&Path::new("/home/user/projects/proj2.rs")));
1211            assert!(entries.contains(&Path::new("/home/guest")));
1212            assert!(entries.contains(&Path::new("/home/guest/note.txt")));
1213
1214            Ok(())
1215        }
1216
1217        #[test]
1218        fn test_tree_relative_path_nested() -> Result<()> {
1219            let mut vfs = setup_test_vfs();
1220            vfs.cd("/home").unwrap();
1221
1222            let entries: Vec<_> = vfs.tree("user")?.collect();
1223
1224            assert_eq!(entries.len(), 4);
1225            assert!(entries.contains(&Path::new("/home/user/file1.txt")));
1226            assert!(entries.contains(&Path::new("/home/user/projects")));
1227            assert!(entries.contains(&Path::new("/home/user/projects/proj1.rs")));
1228            assert!(entries.contains(&Path::new("/home/user/projects/proj2.rs")));
1229
1230            Ok(())
1231        }
1232
1233        #[test]
1234        fn test_tree_with_trailing_slash() -> Result<()> {
1235            let vfs = setup_test_vfs();
1236            let entries1: Vec<_> = vfs.tree("/home/")?.collect(); // With slash
1237            let entries2: Vec<_> = vfs.tree("/home")?.collect(); // Without slash
1238
1239            assert_eq!(entries1, entries2); // Results should be identical
1240            Ok(())
1241        }
1242
1243        #[test]
1244        fn test_tree_dot_path() -> Result<()> {
1245            let mut vfs = setup_test_vfs();
1246            vfs.cd("/home/user").unwrap();
1247
1248            let entries: Vec<_> = vfs.tree(".")?.collect();
1249            assert_eq!(entries.len(), 4);
1250            assert!(entries.contains(&Path::new("/home/user/file1.txt")));
1251            assert!(entries.contains(&Path::new("/home/user/projects")));
1252            assert!(entries.contains(&Path::new("/home/user/projects/proj1.rs")));
1253            assert!(entries.contains(&Path::new("/home/user/projects/proj2.rs")));
1254
1255            Ok(())
1256        }
1257
1258        #[test]
1259        fn test_tree_double_dot_path() -> Result<()> {
1260            let mut vfs = setup_test_vfs();
1261            vfs.cd("/home/user/projects").unwrap();
1262
1263            let entries: Vec<_> = vfs.tree("..")?.collect(); // Parent directory
1264            assert_eq!(entries.len(), 4);
1265            assert!(entries.contains(&Path::new("/home/user/file1.txt")));
1266            assert!(entries.contains(&Path::new("/home/user/projects")));
1267            assert!(entries.contains(&Path::new("/home/user/projects/proj1.rs")));
1268            assert!(entries.contains(&Path::new("/home/user/projects/proj2.rs")));
1269
1270            Ok(())
1271        }
1272
1273        #[test]
1274        fn test_tree_single_entry() -> Result<()> {
1275            let mut vfs = setup_test_vfs();
1276            vfs.mkdir("/single").unwrap();
1277
1278            let entries: Vec<_> = vfs.tree("/single")?.collect();
1279            assert_eq!(entries.len(), 0); // No children → empty
1280
1281            Ok(())
1282        }
1283
1284        #[test]
1285        fn test_tree_iterator_lazy_evaluation() -> Result<()> {
1286            let vfs = setup_test_vfs();
1287            let mut iter = vfs.tree("/home/user")?;
1288
1289            // Test that iterator doesn't panic on immediate creation
1290            assert!(iter.next().is_some());
1291
1292            // Consume remaining items
1293            let count = iter.count();
1294            assert_eq!(count + 1, 4); // +1 because we already took one with next()
1295
1296            Ok(())
1297        }
1298
1299        #[test]
1300        fn test_tree_case_sensitivity() -> Result<()> {
1301            let mut vfs = setup_test_vfs();
1302            vfs.mkdir("/CASE_TEST").unwrap();
1303            vfs.mkfile("/CASE_TEST/file.txt", Some(b"Data")).unwrap();
1304
1305            let entries: Vec<_> = vfs.tree("/CASE_TEST")?.collect();
1306
1307            assert_eq!(entries.len(), 1);
1308            assert!(entries.contains(&Path::new("/CASE_TEST/file.txt")));
1309
1310            Ok(())
1311        }
1312    }
1313
1314    mod mkdir_mkfile {
1315        use super::*;
1316
1317        /// Helper to create a fresh MapFS instance
1318        fn setup_vfs() -> MapFS {
1319            MapFS::new()
1320        }
1321
1322        #[test]
1323        fn test_mkdir_simple_directory() -> Result<()> {
1324            let mut vfs = setup_vfs();
1325            vfs.mkdir("/test")?;
1326
1327            assert!(vfs.exists("/test"));
1328            assert!(vfs.is_dir("/test")?);
1329
1330            Ok(())
1331        }
1332
1333        #[test]
1334        fn test_mkdir_nested_directories() -> Result<()> {
1335            let mut vfs = setup_vfs();
1336            vfs.mkdir("/a/b/c/d")?;
1337
1338            assert!(vfs.exists("/a"));
1339            assert!(vfs.exists("/a/b"));
1340            assert!(vfs.exists("/a/b/c"));
1341            assert!(vfs.exists("/a/b/c/d"));
1342
1343            Ok(())
1344        }
1345
1346        #[test]
1347        fn test_mkdir_existing_path() {
1348            let mut vfs = setup_vfs();
1349            vfs.mkdir("/existing").unwrap();
1350
1351            let result = vfs.mkdir("/existing");
1352            assert!(result.is_err());
1353            assert!(
1354                result
1355                    .unwrap_err()
1356                    .to_string()
1357                    .contains("path already exists"),
1358                "Should error when path exists"
1359            );
1360        }
1361
1362        #[test]
1363        fn test_mkdir_empty_path() {
1364            let mut vfs = setup_vfs();
1365            let result = vfs.mkdir("");
1366            assert!(result.is_err());
1367            assert!(
1368                result
1369                    .unwrap_err()
1370                    .to_string()
1371                    .contains("invalid path: empty"),
1372                "Empty path should be rejected"
1373            );
1374        }
1375
1376        #[test]
1377        fn test_mkdir_root_path() {
1378            let mut vfs = setup_vfs();
1379            let result = vfs.mkdir("/");
1380            assert!(result.is_err());
1381            assert!(
1382                result
1383                    .unwrap_err()
1384                    .to_string()
1385                    .contains("path already exists"),
1386                "Root always exists, should error"
1387            );
1388        }
1389
1390        #[test]
1391        fn test_mkdir_with_trailing_slash() -> Result<()> {
1392            let mut vfs = setup_vfs();
1393            vfs.mkdir("/test/")?; // Trailing slash
1394
1395            assert!(vfs.exists("/test"));
1396            assert!(vfs.is_dir("/test")?);
1397
1398            Ok(())
1399        }
1400
1401        #[test]
1402        fn test_mkfile_simple_file() -> Result<()> {
1403            let mut vfs = setup_vfs();
1404            vfs.mkfile("/file.txt", Some(b"Hello World"))?;
1405
1406            assert!(vfs.exists("/file.txt"));
1407            assert!(vfs.is_file("/file.txt")?);
1408            assert_eq!(vfs.read("/file.txt")?, b"Hello World");
1409
1410            Ok(())
1411        }
1412
1413        #[test]
1414        fn test_mkfile_in_nested_directory() -> Result<()> {
1415            let mut vfs = setup_vfs();
1416            vfs.mkfile("/a/b/c/file.txt", Some(b"Content"))?;
1417
1418            // All parent directories should be created
1419            assert!(vfs.exists("/a"));
1420            assert!(vfs.exists("/a/b"));
1421            assert!(vfs.exists("/a/b/c"));
1422            assert!(vfs.exists("/a/b/c/file.txt"));
1423
1424            assert_eq!(vfs.read("/a/b/c/file.txt")?, b"Content");
1425
1426            Ok(())
1427        }
1428
1429        #[test]
1430        fn test_mkfile_empty_content() -> Result<()> {
1431            let mut vfs = setup_vfs();
1432            vfs.mkfile("/empty.txt", None)?; // No content
1433
1434            assert!(vfs.exists("/empty.txt"));
1435            assert!(vfs.is_file("/empty.txt")?);
1436            assert_eq!(vfs.read("/empty.txt")?, &[]);
1437
1438            Ok(())
1439        }
1440
1441        #[test]
1442        fn test_mkfile_existing_file() -> Result<()> {
1443            let mut vfs = setup_vfs();
1444            vfs.mkfile("/test.txt", Some(b"Original"))?;
1445
1446            // Try to create same file again
1447            let result = vfs.mkfile("/test.txt", Some(b"New"));
1448
1449            assert!(result.is_err());
1450            assert_eq!(vfs.read("/test.txt")?, b"Original");
1451
1452            Ok(())
1453        }
1454
1455        #[test]
1456        fn test_mkfile_to_existing_directory() {
1457            let mut vfs = setup_vfs();
1458            vfs.mkdir("/dir").unwrap();
1459
1460            let result = vfs.mkfile("/dir", Some(b"Content"));
1461            assert!(result.is_err());
1462            // Depending on design, this might be allowed or not
1463            // Current implementation tries to create file at existing dir path
1464            // Consider whether this should be an error
1465        }
1466
1467        #[test]
1468        fn test_mkfile_with_trailing_slash() -> Result<()> {
1469            let mut vfs = setup_vfs();
1470            vfs.mkfile("/file.txt/", Some(b"With slash"))?;
1471
1472            assert!(vfs.exists("/file.txt")); // Should normalize
1473            assert_eq!(vfs.read("/file.txt")?, b"With slash");
1474
1475            Ok(())
1476        }
1477
1478        #[test]
1479        fn test_mkfile_relative_path() -> Result<()> {
1480            let mut vfs = setup_vfs();
1481            vfs.mkdir("/home")?;
1482            vfs.cd("/home")?; // Assume /home exists
1483
1484            vfs.mkfile("file.txt", Some(b"Relative"))?;
1485
1486            assert!(vfs.exists("/home/file.txt"));
1487            assert_eq!(vfs.read("/home/file.txt")?, b"Relative");
1488
1489            Ok(())
1490        }
1491
1492        #[test]
1493        fn test_mkdir_and_mkfile_combination() -> Result<()> {
1494            let mut vfs = setup_vfs();
1495
1496            vfs.mkdir("/projects")?;
1497            vfs.mkfile("/projects/main.rs", Some(b"fn main() {}"))?;
1498            vfs.mkdir("/projects/tests")?;
1499            vfs.mkfile("/projects/tests/test1.rs", Some(b"#[test]"))?;
1500
1501            assert!(vfs.exists("/projects"));
1502            assert!(vfs.exists("/projects/main.rs"));
1503            assert!(vfs.exists("/projects/tests"));
1504            assert!(vfs.exists("/projects/tests/test1.rs"));
1505
1506            Ok(())
1507        }
1508
1509        #[test]
1510        fn test_mkdir_case_sensitivity() -> Result<()> {
1511            let mut vfs = setup_vfs();
1512            vfs.mkdir("/CaseDir")?;
1513
1514            assert!(vfs.exists("/CaseDir"));
1515            assert!(!vfs.exists("/casedir")); // Case-sensitive
1516
1517            Ok(())
1518        }
1519    }
1520
1521    mod read_write_append {
1522        use super::*;
1523
1524        /// Helper to create a pre‑populated MapFS instance for testing
1525        fn setup_test_vfs() -> MapFS {
1526            let mut vfs = MapFS::new();
1527
1528            // Create sample files and directories
1529            vfs.mkdir("/etc").unwrap();
1530            vfs.mkfile("/readme.md", Some(b"Project docs")).unwrap();
1531            vfs.mkfile("/data.bin", Some(b"\x00\x01\x02")).unwrap();
1532            vfs.mkfile("/empty.txt", None).unwrap(); // Empty file
1533            vfs.mkfile("/home/user/file.txt", Some(b"Hello World"))
1534                .unwrap();
1535
1536            vfs
1537        }
1538
1539        #[test]
1540        fn test_read_existing_file() -> Result<()> {
1541            let vfs = setup_test_vfs();
1542            let content = vfs.read("/readme.md")?;
1543            assert_eq!(content, b"Project docs");
1544            Ok(())
1545        }
1546
1547        #[test]
1548        fn test_read_binary_file() -> Result<()> {
1549            let vfs = setup_test_vfs();
1550            let content = vfs.read("/data.bin")?;
1551            assert_eq!(content, vec![0x00, 0x01, 0x02]);
1552            Ok(())
1553        }
1554
1555        #[test]
1556        fn test_read_empty_file() -> Result<()> {
1557            let vfs = setup_test_vfs();
1558            let content = vfs.read("/empty.txt")?;
1559            assert!(content.is_empty());
1560            Ok(())
1561        }
1562
1563        #[test]
1564        fn test_read_nonexistent_file() {
1565            let vfs = setup_test_vfs();
1566            let result = vfs.read("/nonexistent.txt");
1567            assert!(result.is_err());
1568            assert!(
1569                result.unwrap_err().to_string().contains("does not exist"),
1570                "Error should mention file does not exist"
1571            );
1572        }
1573
1574        #[test]
1575        fn test_read_directory_as_file() {
1576            let vfs = setup_test_vfs();
1577            let result = vfs.read("/etc");
1578            assert!(result.is_err());
1579            assert!(
1580                result.unwrap_err().to_string().contains("is a directory"),
1581                "Reading directory as file should error"
1582            );
1583        }
1584
1585        #[test]
1586        fn test_write_existing_file() -> Result<()> {
1587            let mut vfs = setup_test_vfs();
1588            vfs.write("/readme.md", b"Updated content")?;
1589
1590            let content = vfs.read("/readme.md")?;
1591            assert_eq!(content, b"Updated content");
1592            Ok(())
1593        }
1594
1595        #[test]
1596        fn test_write_binary_content() -> Result<()> {
1597            let mut vfs = setup_test_vfs();
1598            vfs.write("/data.bin", &[0xFF, 0xFE, 0xFD])?;
1599
1600            let content = vfs.read("/data.bin")?;
1601            assert_eq!(content, vec![0xFF, 0xFE, 0xFD]);
1602            Ok(())
1603        }
1604
1605        #[test]
1606        fn test_write_empty_content() -> Result<()> {
1607            let mut vfs = setup_test_vfs();
1608            vfs.write("/empty.txt", &[])?;
1609
1610            let content = vfs.read("/empty.txt")?;
1611            assert!(content.is_empty());
1612            Ok(())
1613        }
1614
1615        #[test]
1616        fn test_write_nonexistent_file() {
1617            let mut vfs = setup_test_vfs();
1618            let result = vfs.write("/newfile.txt", b"Content");
1619            assert!(result.is_err());
1620            assert!(
1621                result.unwrap_err().to_string().contains("does not exist"),
1622                "Writing to nonexistent file should fail"
1623            );
1624        }
1625
1626        #[test]
1627        fn test_write_directory_as_file() {
1628            let mut vfs = setup_test_vfs();
1629            let result = vfs.write("/etc", b"Content");
1630            assert!(result.is_err());
1631            assert!(
1632                result.unwrap_err().to_string().contains("is a directory"),
1633                "Writing to directory should error"
1634            );
1635        }
1636
1637        #[test]
1638        fn test_append_to_file() -> Result<()> {
1639            let mut vfs = setup_test_vfs();
1640            vfs.append("/readme.md", b" - appended")?;
1641
1642            let content = vfs.read("/readme.md")?;
1643            assert_eq!(content, b"Project docs - appended");
1644            Ok(())
1645        }
1646
1647        #[test]
1648        fn test_append_binary_data() -> Result<()> {
1649            let mut vfs = setup_test_vfs();
1650            vfs.append("/data.bin", &[0xAA, 0xBB])?;
1651
1652            let content = vfs.read("/data.bin")?;
1653            assert_eq!(content, vec![0x00, 0x01, 0x02, 0xAA, 0xBB]);
1654            Ok(())
1655        }
1656
1657        #[test]
1658        fn test_append_empty_slice() -> Result<()> {
1659            let mut vfs = setup_test_vfs();
1660            vfs.append("/empty.txt", &[])?; // Append nothing
1661
1662            let content = vfs.read("/empty.txt")?;
1663            assert!(content.is_empty()); // Still empty
1664            Ok(())
1665        }
1666
1667        #[test]
1668        fn test_append_nonexistent_file() {
1669            let mut vfs = setup_test_vfs();
1670            let result = vfs.append("/newfile.txt", b"More content");
1671            assert!(result.is_err());
1672            assert!(
1673                result.unwrap_err().to_string().contains("does not exist"),
1674                "Appending to nonexistent file should fail"
1675            );
1676        }
1677
1678        #[test]
1679        fn test_append_directory_as_file() {
1680            let mut vfs = setup_test_vfs();
1681            let result = vfs.append("/etc", b"Data");
1682            assert!(result.is_err());
1683            assert!(
1684                result.unwrap_err().to_string().contains("is a directory"),
1685                "Appending to directory should error"
1686            );
1687        }
1688
1689        #[test]
1690        fn test_write_and_append_sequence() -> Result<()> {
1691            let mut vfs = setup_test_vfs();
1692
1693            // Start with initial content
1694            vfs.mkfile("/test.txt", None)?;
1695            vfs.write("/test.txt", b"Initial")?;
1696
1697            // Append some data
1698            vfs.append("/test.txt", b" + appended")?;
1699
1700            // Overwrite completely
1701            vfs.write("/test.txt", b"Overwritten")?;
1702
1703            let final_content = vfs.read("/test.txt")?;
1704            assert_eq!(final_content, b"Overwritten");
1705
1706            Ok(())
1707        }
1708
1709        #[test]
1710        fn test_read_after_write_and_append() -> Result<()> {
1711            let mut vfs = setup_test_vfs();
1712
1713            vfs.mkfile("/log.txt", None)?;
1714            vfs.write("/log.txt", b"Entry 1\n")?;
1715            vfs.append("/log.txt", b"Entry 2\n")?;
1716            vfs.write("/log.txt", b"Overwritten log\n")?;
1717            vfs.append("/log.txt", b"Final entry\n")?;
1718
1719            let content = vfs.read("/log.txt")?;
1720            assert_eq!(content, b"Overwritten log\nFinal entry\n");
1721
1722            Ok(())
1723        }
1724    }
1725
1726    mod rm {
1727        use super::*;
1728
1729        /// Helper to create a pre‑populated MapFS instance for testing
1730        fn setup_test_vfs() -> MapFS {
1731            let mut vfs = MapFS::new();
1732
1733            // Create a sample hierarchy with files and nested directories
1734            vfs.mkdir("/etc").unwrap();
1735            vfs.mkdir("/home").unwrap();
1736            vfs.mkdir("/home/user").unwrap();
1737            vfs.mkdir("/home/user/projects").unwrap();
1738            vfs.mkfile("/home/user/file1.txt", Some(b"Content 1"))
1739                .unwrap();
1740            vfs.mkfile("/home/user/projects/proj1.rs", Some(b"Code 1"))
1741                .unwrap();
1742            vfs.mkfile("/readme.md", Some(b"Docs")).unwrap();
1743            vfs.mkfile("/data.bin", Some(b"\x00\x01")).unwrap();
1744
1745            vfs
1746        }
1747
1748        #[test]
1749        fn test_rm_file() -> Result<()> {
1750            let mut vfs = setup_test_vfs();
1751            vfs.rm("/readme.md")?;
1752
1753            assert!(!vfs.exists("/readme.md"));
1754            assert!(vfs.exists("/data.bin")); // Other files untouched
1755
1756            Ok(())
1757        }
1758
1759        #[test]
1760        fn test_rm_directory_recursive() -> Result<()> {
1761            let mut vfs = setup_test_vfs();
1762            vfs.rm("/home/user")?;
1763
1764            // Entire subtree should be gone
1765            assert!(!vfs.exists("/home/user"));
1766            assert!(!vfs.exists("/home/user/file1.txt"));
1767            assert!(!vfs.exists("/home/user/projects"));
1768            assert!(!vfs.exists("/home/user/projects/proj1.rs"));
1769
1770            // Sibling directories remain
1771            assert!(vfs.exists("/home"));
1772            assert!(vfs.exists("/etc"));
1773
1774            Ok(())
1775        }
1776
1777        #[test]
1778        fn test_rm_nonexistent_path() {
1779            let mut vfs = setup_test_vfs();
1780            let result = vfs.rm("/nonexistent");
1781            assert!(result.is_err());
1782            assert!(
1783                result.unwrap_err().to_string().contains("does not exist"),
1784                "Should error for non‑existent path"
1785            );
1786        }
1787
1788        #[test]
1789        fn test_rm_empty_path() {
1790            let mut vfs = setup_test_vfs();
1791            let result = vfs.rm("");
1792            assert!(result.is_err());
1793            assert!(
1794                result
1795                    .unwrap_err()
1796                    .to_string()
1797                    .contains("invalid path: empty"),
1798                "Empty path should be rejected"
1799            );
1800        }
1801
1802        #[test]
1803        fn test_rm_root_directory() {
1804            let mut vfs = setup_test_vfs();
1805            let result = vfs.rm("/");
1806            assert!(result.is_err());
1807            assert!(
1808                result
1809                    .unwrap_err()
1810                    .to_string()
1811                    .contains("invalid path: the root cannot be removed"),
1812                "Root directory cannot be removed"
1813            );
1814        }
1815
1816        #[test]
1817        fn test_rm_with_trailing_slash() -> Result<()> {
1818            let mut vfs = setup_test_vfs();
1819            vfs.rm("/etc/")?; // Trailing slash
1820
1821            assert!(!vfs.exists("/etc"));
1822            Ok(())
1823        }
1824
1825        #[test]
1826        fn test_rm_relative_path_from_root() -> Result<()> {
1827            let mut vfs = setup_test_vfs();
1828            vfs.cd("/home").unwrap(); // Change CWD to /home
1829
1830            vfs.rm("user")?; // Relative path
1831
1832            assert!(!vfs.exists("/home/user"));
1833            assert!(vfs.exists("/etc")); // Unrelated paths intact
1834
1835            Ok(())
1836        }
1837
1838        #[test]
1839        fn test_rm_relative_path_nested() -> Result<()> {
1840            let mut vfs = setup_test_vfs();
1841            vfs.cd("/home/user/projects").unwrap();
1842
1843            vfs.rm("../file1.txt")?; // Go up and remove sibling
1844
1845            assert!(!vfs.exists("/home/user/file1.txt"));
1846            assert!(vfs.exists("/home/user/projects/proj1.rs")); // Project files intact
1847
1848            Ok(())
1849        }
1850
1851        #[test]
1852        fn test_rm_dot_path() -> Result<()> {
1853            let mut vfs = setup_test_vfs();
1854            vfs.cd("/home/user")?;
1855
1856            let result = vfs.rm(".");
1857            assert!(result.is_ok());
1858            assert!(!vfs.exists("/home/user"));
1859
1860            Ok(())
1861        }
1862
1863        #[test]
1864        fn test_rm_double_dot_path() -> Result<()> {
1865            let mut vfs = setup_test_vfs();
1866            vfs.cd("/home/user/projects").unwrap();
1867
1868            let result = vfs.rm("..");
1869            assert!(result.is_ok());
1870            assert!(!vfs.exists("/home/user"));
1871            assert!(vfs.exists("/home"));
1872
1873            Ok(())
1874        }
1875
1876        #[test]
1877        fn test_rm_single_file_in_dir() -> Result<()> {
1878            let mut vfs = setup_test_vfs();
1879            vfs.rm("/home/user/file1.txt")?;
1880
1881            assert!(!vfs.exists("/home/user/file1.txt"));
1882            assert!(vfs.exists("/home/user/projects/proj1.rs")); // Other files remain
1883            assert!(vfs.exists("/home/user")); // Directory still exists
1884
1885            Ok(())
1886        }
1887
1888        #[test]
1889        fn test_rm_idempotent() -> Result<()> {
1890            let mut vfs = setup_test_vfs();
1891
1892            // First removal succeeds
1893            vfs.rm("/data.bin")?;
1894
1895            // Second removal should fail (already gone)
1896            let result = vfs.rm("/data.bin");
1897            assert!(result.is_err());
1898
1899            Ok(())
1900        }
1901
1902        #[test]
1903        fn test_rm_case_sensitivity() -> Result<()> {
1904            let mut vfs = setup_test_vfs();
1905            vfs.mkdir("/CaseDir").unwrap();
1906            vfs.mkfile("/CaseDir/file.txt", Some(b"Content")).unwrap();
1907
1908            vfs.rm("/CaseDir")?; // Correct case
1909
1910            assert!(!vfs.exists("/CaseDir"));
1911            assert!(!vfs.exists("/casedir")); // Case‑sensitive check
1912
1913            Ok(())
1914        }
1915    }
1916
1917    mod cleanup {
1918        use super::*;
1919
1920        /// Helper to create a pre‑populated MapFS instance for testing
1921        fn setup_test_vfs() -> MapFS {
1922            let mut vfs = MapFS::new();
1923
1924            // Create a diverse hierarchy with files and directories
1925            vfs.mkdir("/etc").unwrap();
1926            vfs.mkdir("/home").unwrap();
1927            vfs.mkdir("/home/user").unwrap();
1928            vfs.mkdir("/home/user/projects").unwrap();
1929            vfs.mkfile("/home/user/file1.txt", Some(b"Content 1"))
1930                .unwrap();
1931            vfs.mkfile("/home/user/projects/proj1.rs", Some(b"Code 1"))
1932                .unwrap();
1933            vfs.mkfile("/readme.md", Some(b"Docs")).unwrap();
1934            vfs.mkfile("/data.bin", Some(b"\x00\x01")).unwrap();
1935            vfs.mkfile("/empty.txt", None).unwrap();
1936
1937            vfs
1938        }
1939
1940        #[test]
1941        fn test_cleanup_removes_all_entries() {
1942            let mut vfs = setup_test_vfs();
1943            let result = vfs.cleanup();
1944
1945            assert!(result); // Should return true on success
1946            assert_eq!(vfs.entries.len(), 0); // All entries removed
1947        }
1948
1949        #[test]
1950        fn test_cleanup_preserves_root() {
1951            let mut vfs = setup_test_vfs();
1952            vfs.cleanup();
1953
1954            assert!(vfs.exists("/"));
1955        }
1956
1957        #[test]
1958        fn test_cleanup_empty_vfs() {
1959            let mut vfs = MapFS::new(); // Already empty
1960            let result = vfs.cleanup();
1961
1962            assert!(result);
1963            assert_eq!(vfs.entries.len(), 0);
1964        }
1965
1966        #[test]
1967        fn test_cleanup_after_partial_removal() {
1968            let mut vfs = setup_test_vfs();
1969
1970            // Remove some entries manually first
1971            vfs.rm("/readme.md").unwrap();
1972            vfs.rm("/home/user/projects").unwrap();
1973
1974            let result = vfs.cleanup();
1975
1976            assert!(result);
1977            assert_eq!(vfs.entries.len(), 0);
1978        }
1979
1980        #[test]
1981        fn test_cleanup_idempotent() {
1982            let mut vfs = setup_test_vfs();
1983
1984            // First cleanup
1985            assert!(vfs.cleanup());
1986
1987            // Second cleanup on already‑clean VFS
1988            assert!(vfs.cleanup());
1989
1990            assert_eq!(vfs.entries.len(), 0);
1991        }
1992
1993        #[test]
1994        fn test_cleanup_with_nested_structure() {
1995            let mut vfs = setup_test_vfs();
1996
1997            // Add deeper nesting to test traversal order
1998            vfs.mkdir("/a/b/c/d").unwrap();
1999            vfs.mkfile("/a/b/c/d/file.txt", Some(b"Deep file")).unwrap();
2000
2001            vfs.cleanup();
2002
2003            assert_eq!(vfs.entries.len(), 0);
2004        }
2005
2006        #[test]
2007        fn test_cleanup_returns_true_always() {
2008            let mut vfs1 = setup_test_vfs();
2009            let mut vfs2 = MapFS::new();
2010
2011            assert!(vfs1.cleanup()); // Non‑empty VFS
2012            assert!(vfs2.cleanup()); // Empty VFS
2013        }
2014    }
2015}