use std::path::{Path, PathBuf};
use openmoat_core::Action;
use openmoat_hosts::{HookRequest, Host};
use super::in_session;
use crate::context::path_string;
fn shell(host: Host, cwd: &Path, command: &str) -> HookRequest {
let payload = serde_json::json!({
"hook_event_name": "PreToolUse",
"session_id": "s1",
"cwd": path_string(cwd),
"tool_name": "Bash",
"tool_input": { "command": command },
});
host.parse_request(&payload.to_string()).unwrap()
}
fn placed(host: Host, session: &Path, tool: &Path, command: &str) -> HookRequest {
let session = session.to_path_buf();
let fixed = move || -> std::io::Result<PathBuf> { Ok(session.clone()) };
in_session(host, shell(host, tool, command), &fixed).unwrap()
}
#[test]
fn a_codex_workdir_outside_the_project_never_becomes_the_project() {
let dir = tempfile::tempdir().unwrap();
let proj = dir.path().join("proj");
let agents = dir.path().join("Library/LaunchAgents");
std::fs::create_dir_all(proj.join(".git")).unwrap();
std::fs::create_dir_all(proj.join("sub")).unwrap();
std::fs::create_dir_all(&agents).unwrap();
let inside = placed(Host::Codex, &proj, &proj.join("sub"), "make");
assert_eq!(
inside.cwd.as_deref(),
Some(path_string(&proj.join("sub")).as_str())
);
assert_eq!(
inside.action,
Some(Action::Shell {
command: "make".to_owned()
})
);
let outside = placed(Host::Codex, &proj, &agents, "printf x > evil.plist");
assert_eq!(outside.cwd.as_deref(), Some(path_string(&proj).as_str()));
let expected = format!("cd '{}' && printf x > evil.plist", path_string(&agents));
assert_eq!(outside.action, Some(Action::Shell { command: expected }));
let claude = placed(Host::ClaudeCode, &proj, &agents, "ls");
assert_eq!(claude.cwd.as_deref(), Some(path_string(&agents).as_str()));
}
#[test]
fn a_session_outside_any_project_keeps_only_a_git_work_tree() {
let home = crate::home::user_home().unwrap();
let dir = tempfile::tempdir().unwrap();
let repo = dir.path().join("repo");
let plain = dir.path().join("plain");
std::fs::create_dir_all(repo.join(".git")).unwrap();
std::fs::create_dir_all(&plain).unwrap();
let kept = placed(Host::Codex, &home, &repo, "ls");
assert_eq!(kept.cwd.as_deref(), Some(path_string(&repo).as_str()));
let moved = placed(Host::Codex, &home, &plain, "ls");
assert_eq!(moved.cwd.as_deref(), Some(path_string(&home).as_str()));
}
#[cfg(unix)]
#[test]
fn the_project_is_the_same_through_a_link() {
let dir = tempfile::tempdir().unwrap();
let real = dir.path().join("real");
std::fs::create_dir_all(real.join("proj/.git")).unwrap();
let link = dir.path().join("link");
std::os::unix::fs::symlink(&real, &link).unwrap();
let session = std::fs::canonicalize(real.join("proj")).unwrap();
let spelled = link.join("proj");
let kept = placed(Host::Codex, &session, &spelled, "make");
assert_eq!(kept.cwd.as_deref(), Some(path_string(&spelled).as_str()));
assert_eq!(
kept.action,
Some(Action::Shell {
command: "make".to_owned()
})
);
let outside = dir.path().join("other");
std::fs::create_dir_all(&outside).unwrap();
let moved = placed(Host::Codex, &session, &outside, "ls");
assert_eq!(moved.cwd.as_deref(), Some(path_string(&session).as_str()));
}