agentdir 0.1.0

Virtual filesystem for agent-optimized file exploration using CoW reflinks
Documentation
//! Virtual path edge cases: normalization, error handling, catalog operations with boundary paths.

use agentdir::error::AgentdirError;
use agentdir::types::{SourcePath, VirtualPath};
use agentdir::workspace::Workspace;
use tempfile::TempDir;

#[tokio::test]
async fn test_virtual_path_empty_rejected() {
    let result = VirtualPath::new("");

    assert!(matches!(result, Err(AgentdirError::InvalidPath(_))));
}

#[tokio::test]
async fn test_virtual_path_dot_only_relative() {
    let result = VirtualPath::new(".");

    assert!(matches!(result, Err(AgentdirError::InvalidPath(_))));
}

#[tokio::test]
async fn test_virtual_path_double_dot_normalization() {
    let path = VirtualPath::new("/a/b/../c").unwrap();

    assert_eq!(path.as_str(), "/a/c");
}

#[tokio::test]
async fn test_virtual_path_traversal_beyond_root() {
    let path = VirtualPath::new("/a/../../b").unwrap();

    assert_eq!(path.as_str(), "/b");
}

#[tokio::test]
async fn test_virtual_path_double_slashes_normalized() {
    let path = VirtualPath::new("/a//b///c").unwrap();

    assert_eq!(path.as_str(), "/a/b/c");
}

#[tokio::test]
async fn test_virtual_path_trailing_slash_stripped() {
    let path = VirtualPath::new("/foo/bar/").unwrap();

    assert_eq!(path.as_str(), "/foo/bar");
}

#[tokio::test]
async fn test_virtual_path_root_only() {
    let path = VirtualPath::new("/").unwrap();

    assert_eq!(path.as_str(), "/");
}

#[tokio::test]
async fn test_virtual_path_parent_of_root() {
    let path = VirtualPath::new("/").unwrap();

    assert!(path.parent().is_none());
}

#[tokio::test]
async fn test_virtual_path_file_name_of_root() {
    let path = VirtualPath::new("/").unwrap();

    assert_eq!(path.file_name(), None);
}

#[tokio::test]
async fn test_map_to_root_mount() {
    let src = TempDir::new().unwrap();
    let ws_dir = TempDir::new().unwrap();

    std::fs::write(src.path().join("filename.txt"), b"root mounted").unwrap();

    let mut ws = Workspace::init(ws_dir.path().to_path_buf()).unwrap();
    ws.map(
        SourcePath::new(src.path().to_path_buf()),
        VirtualPath::new("/").unwrap(),
    )
    .await
    .unwrap();

    let entry = ws.catalog.get(&VirtualPath::new("/filename.txt").unwrap());
    assert!(entry.is_ok());
    assert_eq!(
        std::fs::read(ws_dir.path().join("filename.txt")).unwrap(),
        b"root mounted"
    );
}

#[tokio::test]
async fn test_mv_to_same_path_is_error() {
    let src = TempDir::new().unwrap();
    let ws_dir = TempDir::new().unwrap();

    std::fs::write(src.path().join("file.txt"), b"content").unwrap();

    let mut ws = Workspace::init(ws_dir.path().to_path_buf()).unwrap();
    ws.map(
        SourcePath::new(src.path().to_path_buf()),
        VirtualPath::new("/docs").unwrap(),
    )
    .await
    .unwrap();

    let path = VirtualPath::new("/docs/file.txt").unwrap();
    let result = ws.mv(&path, &path);

    assert!(matches!(result, Err(AgentdirError::EntryExists(_))));
}

#[tokio::test]
async fn test_mv_nonexistent_source_is_error() {
    let ws_dir = TempDir::new().unwrap();

    let mut ws = Workspace::init(ws_dir.path().to_path_buf()).unwrap();
    let result = ws.mv(
        &VirtualPath::new("/missing.txt").unwrap(),
        &VirtualPath::new("/target.txt").unwrap(),
    );

    assert!(matches!(result, Err(AgentdirError::EntryNotFound(_))));
}

#[tokio::test]
async fn test_cp_to_existing_path_is_error() {
    let src = TempDir::new().unwrap();
    let ws_dir = TempDir::new().unwrap();

    std::fs::write(src.path().join("file1.txt"), b"one").unwrap();
    std::fs::write(src.path().join("file2.txt"), b"two").unwrap();

    let mut ws = Workspace::init(ws_dir.path().to_path_buf()).unwrap();
    ws.map(
        SourcePath::new(src.path().to_path_buf()),
        VirtualPath::new("/docs").unwrap(),
    )
    .await
    .unwrap();

    let result = ws.cp(
        &VirtualPath::new("/docs/file1.txt").unwrap(),
        &VirtualPath::new("/docs/file2.txt").unwrap(),
    );

    assert!(matches!(result, Err(AgentdirError::EntryExists(_))));
}

#[tokio::test]
async fn test_rename_root_entry_rejected() {
    let ws_dir = TempDir::new().unwrap();

    let mut ws = Workspace::init(ws_dir.path().to_path_buf()).unwrap();
    let result = ws.catalog.rename(&VirtualPath::new("/").unwrap(), "new");

    assert!(matches!(result, Err(AgentdirError::InvalidPath(_))));
}

#[tokio::test]
async fn test_mkdir_duplicate_rejected() {
    let ws_dir = TempDir::new().unwrap();

    let mut ws = Workspace::init(ws_dir.path().to_path_buf()).unwrap();
    let path = VirtualPath::new("/dir").unwrap();

    ws.mkdir(&path).unwrap();
    let result = ws.mkdir(&path);

    assert!(matches!(result, Err(AgentdirError::EntryExists(_))));
}