use agentfs::{AgentFS, FileSystem, KvStore, ToolRecorder};
use agentsql::SqlBackend;
async fn create_test_agentfs() -> AgentFS {
let backend = SqlBackend::sqlite(":memory:")
.await
.expect("Failed to create SQLite backend");
AgentFS::new(Box::new(backend), "test-agent", "/agent")
.await
.expect("Failed to create AgentFS")
}
#[tokio::test]
async fn test_agentfs_creation() {
let agentfs = create_test_agentfs().await;
assert_eq!(agentfs.agent_id(), "test-agent");
}
#[tokio::test]
async fn test_kv_operations() {
let agentfs = create_test_agentfs().await;
agentfs.kv.set("test_key", b"test_value").await.unwrap();
let value = agentfs.kv.get("test_key").await.unwrap();
assert_eq!(value, Some(b"test_value".to_vec()));
assert!(agentfs.kv.exists("test_key").await.unwrap());
assert!(!agentfs.kv.exists("nonexistent").await.unwrap());
agentfs.kv.delete("test_key").await.unwrap();
let value = agentfs.kv.get("test_key").await.unwrap();
assert_eq!(value, None);
}
#[tokio::test]
async fn test_kv_scan() {
let agentfs = create_test_agentfs().await;
agentfs.kv.set("prefix_1", b"v1").await.unwrap();
agentfs.kv.set("prefix_2", b"v2").await.unwrap();
agentfs.kv.set("other_key", b"v3").await.unwrap();
let keys = agentfs.kv.scan("prefix").await.unwrap();
assert_eq!(keys.len(), 2);
assert!(keys.contains(&"prefix_1".to_string()));
assert!(keys.contains(&"prefix_2".to_string()));
}
#[tokio::test]
async fn test_filesystem_mkdir() {
let agentfs = create_test_agentfs().await;
agentfs.fs.mkdir("/test_dir").await.unwrap();
assert!(agentfs.fs.exists("/test_dir").await.unwrap());
let stats = agentfs.fs.stat("/test_dir").await.unwrap();
assert!(stats.is_some());
let stats = stats.unwrap();
assert!(stats.is_directory());
assert_eq!(stats.ino, 2); }
#[tokio::test]
async fn test_filesystem_write_and_read() {
let agentfs = create_test_agentfs().await;
agentfs.fs.mkdir("/test_dir").await.unwrap();
let data = b"Hello, AgentFS!";
agentfs
.fs
.write_file("/test_dir/test.txt", data)
.await
.unwrap();
let read_data = agentfs
.fs
.read_file("/test_dir/test.txt")
.await
.unwrap()
.unwrap();
assert_eq!(read_data, data);
assert!(agentfs.fs.exists("/test_dir/test.txt").await.unwrap());
}
#[tokio::test]
async fn test_filesystem_readdir() {
let agentfs = create_test_agentfs().await;
agentfs.fs.mkdir("/test_dir").await.unwrap();
agentfs
.fs
.write_file("/test_dir/file1.txt", b"content1")
.await
.unwrap();
agentfs
.fs
.write_file("/test_dir/file2.txt", b"content2")
.await
.unwrap();
agentfs.fs.mkdir("/test_dir/subdir").await.unwrap();
let entries = agentfs.fs.readdir("/test_dir").await.unwrap().unwrap();
assert_eq!(entries.len(), 3);
assert!(entries.contains(&"file1.txt".to_string()));
assert!(entries.contains(&"file2.txt".to_string()));
assert!(entries.contains(&"subdir".to_string()));
}
#[tokio::test]
async fn test_filesystem_stat() {
let agentfs = create_test_agentfs().await;
agentfs.fs.mkdir("/test_dir").await.unwrap();
let data = b"Test content";
agentfs
.fs
.write_file("/test_dir/test.txt", data)
.await
.unwrap();
let stats = agentfs.fs.stat("/test_dir/test.txt").await.unwrap();
assert!(stats.is_some());
let stats = stats.unwrap();
assert!(stats.is_file());
assert_eq!(stats.size, data.len() as i64);
assert_eq!(stats.nlink, 1);
let dir_stats = agentfs.fs.stat("/test_dir").await.unwrap().unwrap();
assert!(dir_stats.is_directory());
}
#[tokio::test]
async fn test_filesystem_remove() {
let agentfs = create_test_agentfs().await;
agentfs.fs.mkdir("/test_dir").await.unwrap();
agentfs
.fs
.write_file("/test_dir/test.txt", b"content")
.await
.unwrap();
assert!(agentfs.fs.exists("/test_dir/test.txt").await.unwrap());
agentfs.fs.remove("/test_dir/test.txt").await.unwrap();
assert!(!agentfs.fs.exists("/test_dir/test.txt").await.unwrap());
agentfs.fs.remove("/test_dir").await.unwrap();
assert!(!agentfs.fs.exists("/test_dir").await.unwrap());
}
#[tokio::test]
async fn test_filesystem_remove_non_empty_directory() {
let agentfs = create_test_agentfs().await;
agentfs.fs.mkdir("/test_dir").await.unwrap();
agentfs
.fs
.write_file("/test_dir/test.txt", b"content")
.await
.unwrap();
let result = agentfs.fs.remove("/test_dir").await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_filesystem_overwrite() {
let agentfs = create_test_agentfs().await;
agentfs.fs.mkdir("/test_dir").await.unwrap();
agentfs
.fs
.write_file("/test_dir/test.txt", b"original")
.await
.unwrap();
agentfs
.fs
.write_file("/test_dir/test.txt", b"updated")
.await
.unwrap();
let data = agentfs
.fs
.read_file("/test_dir/test.txt")
.await
.unwrap()
.unwrap();
assert_eq!(data, b"updated");
}
#[tokio::test]
async fn test_filesystem_symlink() {
let agentfs = create_test_agentfs().await;
agentfs.fs.mkdir("/test_dir").await.unwrap();
agentfs
.fs
.write_file("/test_dir/original.txt", b"content")
.await
.unwrap();
agentfs
.fs
.symlink("/test_dir/original.txt", "/test_dir/link.txt")
.await
.unwrap();
assert!(agentfs.fs.exists("/test_dir/link.txt").await.unwrap());
let lstat = agentfs.fs.lstat("/test_dir/link.txt").await.unwrap().unwrap();
assert!(lstat.is_symlink());
let stat = agentfs.fs.stat("/test_dir/link.txt").await.unwrap().unwrap();
assert!(stat.is_file());
let target = agentfs
.fs
.readlink("/test_dir/link.txt")
.await
.unwrap()
.unwrap();
assert_eq!(target, "/test_dir/original.txt");
let data = agentfs
.fs
.read_file("/test_dir/link.txt")
.await
.unwrap()
.unwrap();
assert_eq!(data, b"content");
}
#[tokio::test]
async fn test_filesystem_path_normalization() {
let agentfs = create_test_agentfs().await;
agentfs.fs.mkdir("/test_dir").await.unwrap();
agentfs
.fs
.write_file("/test_dir/file.txt", b"content")
.await
.unwrap();
assert!(agentfs.fs.exists("/test_dir/file.txt").await.unwrap());
assert!(agentfs.fs.exists("/test_dir//file.txt").await.unwrap());
assert!(agentfs.fs.exists("/test_dir/./file.txt").await.unwrap());
}
#[tokio::test]
async fn test_root_directory_operations() {
let agentfs = create_test_agentfs().await;
assert!(agentfs.fs.exists("/").await.unwrap());
let stats = agentfs.fs.stat("/").await.unwrap().unwrap();
assert!(stats.is_directory());
assert_eq!(stats.ino, 1);
let result = agentfs.fs.remove("/").await;
assert!(result.is_err());
let entries = agentfs.fs.readdir("/").await.unwrap().unwrap();
assert!(entries.is_empty()); }
#[tokio::test]
async fn test_inode_reuse_after_delete() {
let agentfs = create_test_agentfs().await;
agentfs.fs.mkdir("/test").await.unwrap();
agentfs
.fs
.write_file("/test/file.txt", b"content")
.await
.unwrap();
let original_stats = agentfs.fs.stat("/test/file.txt").await.unwrap().unwrap();
let original_ino = original_stats.ino;
agentfs.fs.remove("/test/file.txt").await.unwrap();
agentfs
.fs
.write_file("/test/file2.txt", b"new content")
.await
.unwrap();
let new_stats = agentfs.fs.stat("/test/file2.txt").await.unwrap().unwrap();
assert_ne!(new_stats.ino, original_ino);
}
#[tokio::test]
async fn test_empty_file() {
let agentfs = create_test_agentfs().await;
agentfs.fs.mkdir("/test").await.unwrap();
agentfs
.fs
.write_file("/test/empty.txt", b"")
.await
.unwrap();
let data = agentfs
.fs
.read_file("/test/empty.txt")
.await
.unwrap()
.unwrap();
assert_eq!(data.len(), 0);
let stats = agentfs.fs.stat("/test/empty.txt").await.unwrap().unwrap();
assert_eq!(stats.size, 0);
}
#[tokio::test]
async fn test_nested_directories() {
let agentfs = create_test_agentfs().await;
agentfs.fs.mkdir("/level1").await.unwrap();
agentfs.fs.mkdir("/level1/level2").await.unwrap();
agentfs.fs.mkdir("/level1/level2/level3").await.unwrap();
agentfs
.fs
.write_file("/level1/level2/level3/deep.txt", b"deep content")
.await
.unwrap();
let data = agentfs
.fs
.read_file("/level1/level2/level3/deep.txt")
.await
.unwrap()
.unwrap();
assert_eq!(data, b"deep content");
assert!(agentfs.fs.exists("/level1").await.unwrap());
assert!(agentfs.fs.exists("/level1/level2").await.unwrap());
assert!(agentfs.fs.exists("/level1/level2/level3").await.unwrap());
}
#[tokio::test]
async fn test_file_in_root() {
let agentfs = create_test_agentfs().await;
agentfs
.fs
.write_file("/root_file.txt", b"in root")
.await
.unwrap();
let data = agentfs
.fs
.read_file("/root_file.txt")
.await
.unwrap()
.unwrap();
assert_eq!(data, b"in root");
let entries = agentfs.fs.readdir("/").await.unwrap().unwrap();
assert!(entries.contains(&"root_file.txt".to_string()));
}
#[tokio::test]
async fn test_tool_calls_workflow() {
let agentfs = create_test_agentfs().await;
let params = serde_json::json!({"query": "test query"});
let id = agentfs.tools.start("web_search", Some(params.clone())).await.unwrap();
let tool_call = agentfs.tools.get(id).await.unwrap().unwrap();
assert_eq!(tool_call.name, "web_search");
assert_eq!(tool_call.parameters, Some(params));
assert_eq!(tool_call.status, agentfs::tools::ToolCallStatus::Pending);
assert!(tool_call.completed_at.is_none());
assert!(tool_call.duration_ms.is_none());
let result = serde_json::json!({"results": ["result1", "result2"]});
agentfs.tools.success(id, Some(result.clone())).await.unwrap();
let tool_call = agentfs.tools.get(id).await.unwrap().unwrap();
assert_eq!(tool_call.status, agentfs::tools::ToolCallStatus::Success);
assert_eq!(tool_call.result, Some(result));
assert!(tool_call.completed_at.is_some());
assert!(tool_call.duration_ms.is_some());
assert!(tool_call.duration_ms.unwrap() >= 0);
}
#[tokio::test]
async fn test_tool_calls_error() {
let agentfs = create_test_agentfs().await;
let id = agentfs.tools.start("failing_tool", None).await.unwrap();
agentfs.tools.error(id, "Connection timeout").await.unwrap();
let tool_call = agentfs.tools.get(id).await.unwrap().unwrap();
assert_eq!(tool_call.status, agentfs::tools::ToolCallStatus::Error);
assert_eq!(tool_call.error, Some("Connection timeout".to_string()));
assert!(tool_call.result.is_none());
assert!(tool_call.completed_at.is_some());
assert!(tool_call.duration_ms.is_some());
}
#[tokio::test]
async fn test_tool_calls_record() {
let agentfs = create_test_agentfs().await;
let params = serde_json::json!({"url": "https://example.com"});
let result = serde_json::json!({"status": 200});
let started_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
let completed_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
let id = agentfs.tools.record(
"http_request",
started_at,
completed_at,
Some(params.clone()),
Some(result.clone()),
None,
).await.unwrap();
let tool_call = agentfs.tools.get(id).await.unwrap().unwrap();
assert_eq!(tool_call.name, "http_request");
assert_eq!(tool_call.parameters, Some(params));
assert_eq!(tool_call.result, Some(result));
assert_eq!(tool_call.status, agentfs::tools::ToolCallStatus::Success);
assert_eq!(tool_call.started_at, started_at);
assert_eq!(tool_call.completed_at, Some(completed_at));
}
#[tokio::test]
async fn test_tool_calls_stats() {
let agentfs = create_test_agentfs().await;
let id1 = agentfs.tools.start("api_call", None).await.unwrap();
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
agentfs.tools.success(id1, None).await.unwrap();
let id2 = agentfs.tools.start("api_call", None).await.unwrap();
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
agentfs.tools.success(id2, None).await.unwrap();
let id3 = agentfs.tools.start("api_call", None).await.unwrap();
agentfs.tools.error(id3, "Failed").await.unwrap();
let stats = agentfs.tools.stats_for("api_call").await.unwrap().unwrap();
assert_eq!(stats.name, "api_call");
assert_eq!(stats.total_calls, 3);
assert_eq!(stats.successful, 2);
assert_eq!(stats.failed, 1);
assert!(stats.avg_duration_ms >= 0.0);
let no_stats = agentfs.tools.stats_for("nonexistent").await.unwrap();
assert!(no_stats.is_none());
}
#[tokio::test]
async fn test_tool_calls_list() {
let agentfs = create_test_agentfs().await;
agentfs.tools.start("tool1", None).await.unwrap();
agentfs.tools.start("tool2", None).await.unwrap();
agentfs.tools.start("tool3", None).await.unwrap();
let all_calls = agentfs.tools.list(None).await.unwrap();
assert_eq!(all_calls.len(), 3);
let limited_calls = agentfs.tools.list(Some(2)).await.unwrap();
assert_eq!(limited_calls.len(), 2);
}
#[tokio::test]
async fn test_path_sandboxing() {
let agentfs = create_test_agentfs().await;
agentfs.fs.write_file("/test.txt", b"content").await.unwrap();
let content = agentfs.fs.read_file("/test.txt").await.unwrap();
assert_eq!(content, Some(b"content".to_vec()));
agentfs.fs.write_file("relative.txt", b"relative").await.unwrap();
let content = agentfs.fs.read_file("relative.txt").await.unwrap();
assert_eq!(content, Some(b"relative".to_vec()));
agentfs.fs.write_file("/agent/prefixed.txt", b"prefixed").await.unwrap();
let content = agentfs.fs.read_file("/prefixed.txt").await.unwrap();
assert_eq!(content, Some(b"prefixed".to_vec()));
let content2 = agentfs.fs.read_file("/agent/prefixed.txt").await.unwrap();
assert_eq!(content2, Some(b"prefixed".to_vec()));
agentfs.fs.mkdir("/sandbox").await.unwrap();
agentfs.fs.write_file("/sandbox/file.txt", b"safe").await.unwrap();
agentfs.fs.write_file("/sandbox/../escape_attempt.txt", b"normalized").await.unwrap();
let content = agentfs.fs.read_file("/escape_attempt.txt").await.unwrap();
assert_eq!(content, Some(b"normalized".to_vec()));
agentfs.fs.mkdir("/etc").await.unwrap();
agentfs.fs.write_file("/../../../etc/passwd", b"blocked").await.unwrap();
let content = agentfs.fs.read_file("/etc/passwd").await.unwrap();
assert_eq!(content, Some(b"blocked".to_vec()));
agentfs.fs.mkdir("/agent/sandboxed").await.unwrap();
agentfs.fs.write_file("/agent/sandboxed/test.txt", b"data").await.unwrap();
assert!(agentfs.fs.exists("/sandboxed/test.txt").await.unwrap());
assert!(agentfs.fs.exists("/agent/sandboxed/test.txt").await.unwrap());
let entries = agentfs.fs.readdir("/").await.unwrap().unwrap();
assert!(entries.len() >= 6);
let stats = agentfs.fs.stat("/test.txt").await.unwrap();
assert!(stats.is_some());
let stats = agentfs.fs.stat("/agent/test.txt").await.unwrap();
assert!(stats.is_some());
}