use std::io;
#[test]
fn write_atomic_succeeds_when_user_interrupted_occurred() {
use crate::interrupt::{
request_user_interrupt, reset_user_interrupted_occurred, take_user_interrupt_request,
};
use tempfile::TempDir;
let _lock = crate::interrupt::interrupt_test_lock();
take_user_interrupt_request();
reset_user_interrupted_occurred();
let tmp = TempDir::new().expect("create temp dir");
let ws = WorkspaceFs::new(tmp.path().to_path_buf());
request_user_interrupt();
let result = ws.write_atomic(Path::new("checkpoint.json"), r#"{"test": true}"#);
take_user_interrupt_request();
reset_user_interrupted_occurred();
assert!(
result.is_ok(),
"write_atomic must succeed even when interrupted: {result:?}"
);
assert_eq!(
ws.read(Path::new("checkpoint.json"))
.expect("file must be readable"),
r#"{"test": true}"#
);
}
#[test]
fn test_workspace_fs_root() {
let ws = WorkspaceFs::new(PathBuf::from("/test/repo"));
assert_eq!(ws.root(), Path::new("/test/repo"));
}
#[test]
fn test_workspace_fs_agent_paths() {
let ws = WorkspaceFs::new(PathBuf::from("/test/repo"));
assert_eq!(ws.agent_dir(), PathBuf::from("/test/repo/.agent"));
assert_eq!(ws.agent_tmp(), PathBuf::from("/test/repo/.agent/tmp"));
assert_eq!(ws.plan_md(), PathBuf::from("/test/repo/.agent/PLAN.md"));
assert_eq!(ws.issues_md(), PathBuf::from("/test/repo/.agent/ISSUES.md"));
assert_eq!(
ws.commit_message(),
PathBuf::from("/test/repo/.agent/commit-message.txt")
);
assert_eq!(
ws.checkpoint(),
PathBuf::from("/test/repo/.agent/checkpoint.json")
);
assert_eq!(
ws.start_commit(),
PathBuf::from("/test/repo/.agent/start_commit")
);
assert_eq!(ws.prompt_md(), PathBuf::from("/test/repo/PROMPT.md"));
}
#[test]
fn test_workspace_fs_dynamic_paths() {
let ws = WorkspaceFs::new(PathBuf::from("/test/repo"));
assert_eq!(
ws.xsd_path("plan"),
PathBuf::from("/test/repo/.agent/tmp/plan.xsd")
);
assert_eq!(
ws.xml_path("issues"),
PathBuf::from("/test/repo/.agent/tmp/issues.xml")
);
assert_eq!(
ws.log_path("agent.log"),
PathBuf::from("/test/repo/.agent/logs/agent.log")
);
}
#[test]
fn test_workspace_fs_absolute() {
let ws = WorkspaceFs::new(PathBuf::from("/test/repo"));
let abs = ws.absolute(Path::new(".agent/tmp/plan.xml"));
assert_eq!(abs, PathBuf::from("/test/repo/.agent/tmp/plan.xml"));
let abs_str = ws.absolute_str(".agent/tmp/plan.xml");
assert_eq!(abs_str, "/test/repo/.agent/tmp/plan.xml");
}
#[test]
fn test_memory_workspace_read_write() {
let ws = MemoryWorkspace::new_test();
ws.write(Path::new(".agent/test.txt"), "hello").unwrap();
assert_eq!(ws.read(Path::new(".agent/test.txt")).unwrap(), "hello");
assert!(ws.was_written(".agent/test.txt"));
}
#[test]
fn test_memory_workspace_with_file() {
let ws = MemoryWorkspace::new_test().with_file("existing.txt", "pre-existing content");
assert_eq!(
ws.read(Path::new("existing.txt")).unwrap(),
"pre-existing content"
);
}
#[test]
fn test_memory_workspace_exists() {
let ws = MemoryWorkspace::new_test().with_file("exists.txt", "content");
assert!(ws.exists(Path::new("exists.txt")));
assert!(!ws.exists(Path::new("not_exists.txt")));
}
#[test]
fn test_memory_workspace_remove() {
let ws = MemoryWorkspace::new_test().with_file("to_delete.txt", "content");
assert!(ws.exists(Path::new("to_delete.txt")));
ws.remove(Path::new("to_delete.txt")).unwrap();
assert!(!ws.exists(Path::new("to_delete.txt")));
}
#[test]
fn test_memory_workspace_read_nonexistent_fails() {
let ws = MemoryWorkspace::new_test();
let result = ws.read(Path::new("nonexistent.txt"));
assert!(result.is_err());
assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
}
#[test]
fn test_memory_workspace_written_files() {
let ws = MemoryWorkspace::new_test();
ws.write(Path::new("file1.txt"), "content1").unwrap();
ws.write(Path::new("file2.txt"), "content2").unwrap();
let files = ws.written_files();
assert_eq!(
String::from_utf8_lossy(files.get(&PathBuf::from("file1.txt")).unwrap()),
"content1"
);
assert_eq!(
String::from_utf8_lossy(files.get(&PathBuf::from("file2.txt")).unwrap()),
"content2"
);
}
#[test]
fn test_memory_workspace_get_file() {
let ws = MemoryWorkspace::new_test();
ws.write(Path::new("test.txt"), "test content").unwrap();
assert_eq!(ws.get_file("test.txt"), Some("test content".to_string()));
assert_eq!(ws.get_file("nonexistent.txt"), None);
}
#[test]
fn test_memory_workspace_clear() {
let ws = MemoryWorkspace::new_test().with_file("file.txt", "content");
assert!(ws.exists(Path::new("file.txt")));
ws.clear();
assert!(!ws.exists(Path::new("file.txt")));
}
#[test]
fn test_memory_workspace_absolute_str() {
let ws = MemoryWorkspace::new_test();
assert_eq!(
ws.absolute_str(".agent/tmp/commit_message.xml"),
"/test/repo/.agent/tmp/commit_message.xml"
);
}
#[test]
fn test_memory_workspace_creates_parent_dirs() {
let ws = MemoryWorkspace::new_test();
ws.write(Path::new("a/b/c/file.txt"), "content").unwrap();
assert!(ws.is_dir(Path::new("a")));
assert!(ws.is_dir(Path::new("a/b")));
assert!(ws.is_dir(Path::new("a/b/c")));
assert!(ws.is_file(Path::new("a/b/c/file.txt")));
}
#[test]
fn test_memory_workspace_rename() {
let ws = MemoryWorkspace::new_test().with_file("old.txt", "content");
ws.rename(Path::new("old.txt"), Path::new("new.txt"))
.unwrap();
assert!(!ws.exists(Path::new("old.txt")));
assert!(ws.exists(Path::new("new.txt")));
assert_eq!(ws.read(Path::new("new.txt")).unwrap(), "content");
}
#[test]
fn test_memory_workspace_rename_creates_parent_dirs() {
let ws = MemoryWorkspace::new_test().with_file("old.txt", "content");
ws.rename(Path::new("old.txt"), Path::new("a/b/new.txt"))
.unwrap();
assert!(!ws.exists(Path::new("old.txt")));
assert!(ws.is_dir(Path::new("a")));
assert!(ws.is_dir(Path::new("a/b")));
assert!(ws.exists(Path::new("a/b/new.txt")));
}
#[test]
fn test_memory_workspace_rename_nonexistent_fails() {
let ws = MemoryWorkspace::new_test();
let result = ws.rename(Path::new("nonexistent.txt"), Path::new("new.txt"));
assert!(result.is_err());
assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
}
#[test]
fn test_memory_workspace_set_readonly_noop() {
let ws = MemoryWorkspace::new_test().with_file("test.txt", "content");
ws.set_readonly(Path::new("test.txt")).unwrap();
ws.set_writable(Path::new("test.txt")).unwrap();
assert_eq!(ws.read(Path::new("test.txt")).unwrap(), "content");
}
#[test]
fn test_memory_workspace_write_atomic() {
let ws = MemoryWorkspace::new_test();
ws.write_atomic(Path::new("atomic.txt"), "atomic content")
.unwrap();
assert_eq!(ws.read(Path::new("atomic.txt")).unwrap(), "atomic content");
}
#[test]
fn test_memory_workspace_write_atomic_creates_parent_dirs() {
let ws = MemoryWorkspace::new_test();
ws.write_atomic(Path::new("a/b/c/atomic.txt"), "nested atomic")
.unwrap();
assert!(ws.is_dir(Path::new("a")));
assert!(ws.is_dir(Path::new("a/b")));
assert!(ws.is_dir(Path::new("a/b/c")));
assert_eq!(
ws.read(Path::new("a/b/c/atomic.txt")).unwrap(),
"nested atomic"
);
}
#[test]
fn test_memory_workspace_write_atomic_overwrites() {
let ws = MemoryWorkspace::new_test().with_file("existing.txt", "old content");
ws.write_atomic(Path::new("existing.txt"), "new content")
.unwrap();
assert_eq!(ws.read(Path::new("existing.txt")).unwrap(), "new content");
}