use async_trait::async_trait;
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use super::limits::{FsLimits, FsUsage};
use super::traits::{DirEntry, Metadata};
use crate::error::Result;
#[async_trait]
pub trait FsBackend: Send + Sync {
async fn read(&self, path: &Path) -> Result<Vec<u8>>;
async fn write(&self, path: &Path, content: &[u8]) -> Result<()>;
async fn append(&self, path: &Path, content: &[u8]) -> Result<()>;
async fn mkdir(&self, path: &Path, recursive: bool) -> Result<()>;
async fn remove(&self, path: &Path, recursive: bool) -> Result<()>;
async fn stat(&self, path: &Path) -> Result<Metadata>;
async fn read_dir(&self, path: &Path) -> Result<Vec<DirEntry>>;
async fn exists(&self, path: &Path) -> Result<bool>;
async fn rename(&self, from: &Path, to: &Path) -> Result<()>;
async fn copy(&self, from: &Path, to: &Path) -> Result<()>;
async fn symlink(&self, target: &Path, link: &Path) -> Result<()>;
async fn read_link(&self, path: &Path) -> Result<PathBuf>;
async fn chmod(&self, path: &Path, mode: u32) -> Result<()>;
async fn set_modified_time(&self, _path: &Path, _time: SystemTime) -> Result<()> {
Err(std::io::Error::other("set_modified_time not supported").into())
}
fn usage(&self) -> FsUsage {
FsUsage::default()
}
fn limits(&self) -> FsLimits {
FsLimits::unlimited()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::Result;
struct StubBackend;
#[async_trait]
impl FsBackend for StubBackend {
async fn read(&self, _path: &Path) -> Result<Vec<u8>> {
Err(std::io::Error::from(std::io::ErrorKind::NotFound).into())
}
async fn write(&self, _path: &Path, _content: &[u8]) -> Result<()> {
Ok(())
}
async fn append(&self, _path: &Path, _content: &[u8]) -> Result<()> {
Ok(())
}
async fn mkdir(&self, _path: &Path, _recursive: bool) -> Result<()> {
Ok(())
}
async fn remove(&self, _path: &Path, _recursive: bool) -> Result<()> {
Ok(())
}
async fn stat(&self, _path: &Path) -> Result<Metadata> {
Ok(Metadata::default())
}
async fn read_dir(&self, _path: &Path) -> Result<Vec<DirEntry>> {
Ok(vec![])
}
async fn exists(&self, _path: &Path) -> Result<bool> {
Ok(false)
}
async fn rename(&self, _from: &Path, _to: &Path) -> Result<()> {
Ok(())
}
async fn copy(&self, _from: &Path, _to: &Path) -> Result<()> {
Ok(())
}
async fn symlink(&self, _target: &Path, _link: &Path) -> Result<()> {
Ok(())
}
async fn read_link(&self, _path: &Path) -> Result<PathBuf> {
Ok(PathBuf::new())
}
async fn chmod(&self, _path: &Path, _mode: u32) -> Result<()> {
Ok(())
}
}
#[test]
fn default_usage_returns_zeros() {
let backend = StubBackend;
let usage = backend.usage();
assert_eq!(usage.total_bytes, 0);
assert_eq!(usage.file_count, 0);
assert_eq!(usage.dir_count, 0);
}
#[test]
fn default_limits_returns_unlimited() {
let backend = StubBackend;
let limits = backend.limits();
assert_eq!(limits.max_total_bytes, u64::MAX);
assert_eq!(limits.max_file_size, u64::MAX);
assert_eq!(limits.max_file_count, u64::MAX);
}
#[test]
fn fs_usage_new() {
let usage = FsUsage::new(1024, 5, 2);
assert_eq!(usage.total_bytes, 1024);
assert_eq!(usage.file_count, 5);
assert_eq!(usage.dir_count, 2);
}
#[test]
fn fs_usage_default() {
let usage = FsUsage::default();
assert_eq!(usage.total_bytes, 0);
assert_eq!(usage.file_count, 0);
assert_eq!(usage.dir_count, 0);
}
#[test]
fn fs_usage_debug() {
let usage = FsUsage::new(100, 3, 1);
let dbg = format!("{:?}", usage);
assert!(dbg.contains("100"));
assert!(dbg.contains("3"));
}
#[tokio::test]
async fn stub_backend_read_returns_not_found() {
let backend = StubBackend;
let result = backend.read(Path::new("/nonexistent")).await;
assert!(result.is_err());
}
#[tokio::test]
async fn stub_backend_exists_returns_false() {
let backend = StubBackend;
let exists = backend.exists(Path::new("/anything")).await.unwrap();
assert!(!exists);
}
#[tokio::test]
async fn stub_backend_read_dir_returns_empty() {
let backend = StubBackend;
let entries = backend.read_dir(Path::new("/")).await.unwrap();
assert!(entries.is_empty());
}
}