mod common;
use agentos_client::fs::{BatchWriteEntry, DirEntryType, FileContent, MkdirOptions, RemoveOptions};
use agentos_client::ClientError;
#[tokio::test]
async fn base_layer_exposes_default_files() {
if !common::sidecar_available() {
eprintln!("skipping base_layer_exposes_default_files: sidecar binary not built");
return;
}
let os = common::new_vm_with_sidecar_pool("fs-base-layer").await;
let hostname = os
.read_file("/etc/hostname")
.await
.expect("read /etc/hostname");
assert!(!hostname.is_empty());
os.shutdown().await.expect("shutdown");
}
#[tokio::test]
async fn filesystem_surface_round_trips() {
if !common::require_sidecar("filesystem_surface_round_trips") {
return;
}
let os = common::new_vm_with_sidecar_pool("fs-round-trips").await;
os.write_file("/tmp/a.txt", FileContent::Text("hello".to_string()))
.await
.expect("write text");
assert_eq!(
os.read_file("/tmp/a.txt").await.expect("read text"),
b"hello"
);
let blob: Vec<u8> = vec![0, 159, 146, 150, 255, 254, 0, 1, 2];
os.write_file("/tmp/blob.bin", FileContent::Bytes(blob.clone()))
.await
.expect("write binary");
assert_eq!(
os.read_file("/tmp/blob.bin").await.expect("read binary"),
blob,
"binary content must round-trip byte-for-byte"
);
let snapshot = os
.export_root_filesystem(64 * 1024 * 1024)
.await
.expect("snapshot root filesystem");
assert_eq!(snapshot.source.format, "agentos-filesystem-snapshot-v1");
assert!(
snapshot
.source
.filesystem
.entries
.iter()
.any(|entry| entry.path == "/tmp/blob.bin" && entry.entry_type == DirEntryType::File),
"snapshot must include the binary file written through the filesystem surface"
);
os.mkdir("/tmp/d1/d2", MkdirOptions { recursive: true })
.await
.expect("mkdir -p");
assert!(os.exists("/tmp/d1/d2").await.expect("exists dir"));
assert!(os.stat("/tmp/d1/d2").await.expect("stat dir").is_directory);
let results = os
.write_files(vec![
BatchWriteEntry {
path: "/tmp/d1/d2/x.txt".to_string(),
content: FileContent::Text("x".to_string()),
},
BatchWriteEntry {
path: "relative-bad".to_string(),
content: FileContent::Text("y".to_string()),
},
])
.await;
assert!(results[0].success, "valid entry should succeed");
assert!(
!results[1].success && results[1].error.is_some(),
"guarded entry should fail per-entry, not reject the batch"
);
let entries = os.readdir("/tmp/d1/d2").await.expect("readdir");
assert!(entries.iter().any(|e| e == "x.txt"));
let recursive = os
.readdir_recursive("/tmp/d1", Default::default())
.await
.expect("readdir_recursive");
assert!(recursive.iter().any(|e| e.path == "/tmp/d1/d2/x.txt"));
os.move_path("/tmp/a.txt", "/tmp/a2.txt")
.await
.expect("move file");
assert!(!os.exists("/tmp/a.txt").await.expect("old gone"));
assert!(os.exists("/tmp/a2.txt").await.expect("new present"));
os.remove("/tmp/a2.txt", RemoveOptions { recursive: false })
.await
.expect("delete file");
assert!(!os.exists("/tmp/a2.txt").await.expect("deleted"));
os.remove("/tmp/d1", RemoveOptions { recursive: true })
.await
.expect("delete -r");
assert!(!os.exists("/tmp/d1").await.expect("dir removed"));
let guard_err = os
.read_file("relative")
.await
.expect_err("relative path must error");
assert!(
matches!(
guard_err.downcast_ref::<ClientError>(),
Some(ClientError::PathNotAbsolute(_))
),
"expected PathNotAbsolute, got {guard_err:?}"
);
os.shutdown().await.expect("shutdown");
}