diff --git a/server/src/http/io.rs b/server/src/http/io.rs index a1ecb1a..85b209e 100644 --- a/server/src/http/io.rs +++ b/server/src/http/io.rs @@ -128,3 +128,221 @@ impl IoApi { Ok(Json(output)) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::http::context::AppContext; + use tempfile::TempDir; + + fn make_api(dir: &TempDir) -> IoApi { + IoApi { + ctx: Arc::new(AppContext::new_test(dir.path().to_path_buf())), + } + } + + // --- list_directory_absolute --- + + #[tokio::test] + async fn list_directory_absolute_returns_entries_for_valid_path() { + let dir = TempDir::new().unwrap(); + std::fs::create_dir(dir.path().join("subdir")).unwrap(); + std::fs::write(dir.path().join("file.txt"), "content").unwrap(); + + let api = make_api(&dir); + let payload = Json(FilePathPayload { + path: dir.path().to_string_lossy().to_string(), + }); + let result = api.list_directory_absolute(payload).await.unwrap(); + let entries = &result.0; + + assert!(entries.len() >= 2); + assert!(entries.iter().any(|e| e.name == "subdir" && e.kind == "dir")); + assert!(entries.iter().any(|e| e.name == "file.txt" && e.kind == "file")); + } + + #[tokio::test] + async fn list_directory_absolute_returns_empty_for_empty_dir() { + let dir = TempDir::new().unwrap(); + let empty = dir.path().join("empty"); + std::fs::create_dir(&empty).unwrap(); + + let api = make_api(&dir); + let payload = Json(FilePathPayload { + path: empty.to_string_lossy().to_string(), + }); + let result = api.list_directory_absolute(payload).await.unwrap(); + assert!(result.0.is_empty()); + } + + #[tokio::test] + async fn list_directory_absolute_errors_on_nonexistent_path() { + let dir = TempDir::new().unwrap(); + let api = make_api(&dir); + let payload = Json(FilePathPayload { + path: dir.path().join("nonexistent").to_string_lossy().to_string(), + }); + let result = api.list_directory_absolute(payload).await; + assert!(result.is_err()); + } + + #[tokio::test] + async fn list_directory_absolute_errors_on_file_path() { + let dir = TempDir::new().unwrap(); + let file = dir.path().join("not_a_dir.txt"); + std::fs::write(&file, "content").unwrap(); + + let api = make_api(&dir); + let payload = Json(FilePathPayload { + path: file.to_string_lossy().to_string(), + }); + let result = api.list_directory_absolute(payload).await; + assert!(result.is_err()); + } + + // --- create_directory_absolute --- + + #[tokio::test] + async fn create_directory_absolute_creates_new_dir() { + let dir = TempDir::new().unwrap(); + let new_dir = dir.path().join("new_dir"); + + let api = make_api(&dir); + let payload = Json(CreateDirectoryPayload { + path: new_dir.to_string_lossy().to_string(), + }); + let result = api.create_directory_absolute(payload).await.unwrap(); + assert!(result.0); + assert!(new_dir.is_dir()); + } + + #[tokio::test] + async fn create_directory_absolute_succeeds_for_existing_dir() { + let dir = TempDir::new().unwrap(); + let existing = dir.path().join("existing"); + std::fs::create_dir(&existing).unwrap(); + + let api = make_api(&dir); + let payload = Json(CreateDirectoryPayload { + path: existing.to_string_lossy().to_string(), + }); + let result = api.create_directory_absolute(payload).await.unwrap(); + assert!(result.0); + } + + #[tokio::test] + async fn create_directory_absolute_creates_nested_dirs() { + let dir = TempDir::new().unwrap(); + let nested = dir.path().join("a").join("b").join("c"); + + let api = make_api(&dir); + let payload = Json(CreateDirectoryPayload { + path: nested.to_string_lossy().to_string(), + }); + let result = api.create_directory_absolute(payload).await.unwrap(); + assert!(result.0); + assert!(nested.is_dir()); + } + + // --- get_home_directory --- + + #[tokio::test] + async fn get_home_directory_returns_a_path() { + let dir = TempDir::new().unwrap(); + let api = make_api(&dir); + let result = api.get_home_directory().await.unwrap(); + let home = &result.0; + assert!(!home.is_empty()); + assert!(std::path::Path::new(home).is_absolute()); + } + + // --- read_file (project-scoped) --- + + #[tokio::test] + async fn read_file_returns_content() { + let dir = TempDir::new().unwrap(); + std::fs::write(dir.path().join("hello.txt"), "hello world").unwrap(); + + let api = make_api(&dir); + let payload = Json(FilePathPayload { + path: "hello.txt".to_string(), + }); + let result = api.read_file(payload).await.unwrap(); + assert_eq!(result.0, "hello world"); + } + + #[tokio::test] + async fn read_file_errors_on_missing_file() { + let dir = TempDir::new().unwrap(); + let api = make_api(&dir); + let payload = Json(FilePathPayload { + path: "nonexistent.txt".to_string(), + }); + let result = api.read_file(payload).await; + assert!(result.is_err()); + } + + // --- write_file (project-scoped) --- + + #[tokio::test] + async fn write_file_creates_file() { + let dir = TempDir::new().unwrap(); + let api = make_api(&dir); + let payload = Json(WriteFilePayload { + path: "output.txt".to_string(), + content: "written content".to_string(), + }); + let result = api.write_file(payload).await.unwrap(); + assert!(result.0); + assert_eq!( + std::fs::read_to_string(dir.path().join("output.txt")).unwrap(), + "written content" + ); + } + + #[tokio::test] + async fn write_file_creates_parent_dirs() { + let dir = TempDir::new().unwrap(); + let api = make_api(&dir); + let payload = Json(WriteFilePayload { + path: "sub/dir/file.txt".to_string(), + content: "nested".to_string(), + }); + let result = api.write_file(payload).await.unwrap(); + assert!(result.0); + assert_eq!( + std::fs::read_to_string(dir.path().join("sub/dir/file.txt")).unwrap(), + "nested" + ); + } + + // --- list_directory (project-scoped) --- + + #[tokio::test] + async fn list_directory_returns_entries() { + let dir = TempDir::new().unwrap(); + std::fs::create_dir(dir.path().join("adir")).unwrap(); + std::fs::write(dir.path().join("bfile.txt"), "").unwrap(); + + let api = make_api(&dir); + let payload = Json(FilePathPayload { + path: ".".to_string(), + }); + let result = api.list_directory(payload).await.unwrap(); + let entries = &result.0; + + assert!(entries.iter().any(|e| e.name == "adir" && e.kind == "dir")); + assert!(entries.iter().any(|e| e.name == "bfile.txt" && e.kind == "file")); + } + + #[tokio::test] + async fn list_directory_errors_on_nonexistent() { + let dir = TempDir::new().unwrap(); + let api = make_api(&dir); + let payload = Json(FilePathPayload { + path: "nonexistent_dir".to_string(), + }); + let result = api.list_directory(payload).await; + assert!(result.is_err()); + } +}