use super::support::*;
#[test]
fn resolve_child_cwd_allows_absolute_by_default_and_false_rejects_outside() {
let parent = tempfile::TempDir::new().unwrap();
let outside = tempfile::TempDir::new().unwrap();
let allowed = resolve_child_cwd(parent.path(), Some(outside.path()), true).unwrap();
assert_eq!(allowed, outside.path().canonicalize().unwrap());
let absolute_err = resolve_child_cwd(parent.path(), Some(outside.path()), false)
.unwrap_err()
.to_string();
assert!(absolute_err.contains("tools.subagents.absolute_paths"));
let relative_err = resolve_child_cwd(parent.path(), Some(Path::new("..")), true)
.unwrap_err()
.to_string();
assert!(relative_err.contains("escapes parent cwd"));
}
#[test]
fn child_cwd_write_stays_inside_requested_child_directory() {
let temp = tempfile::TempDir::new().unwrap();
std::fs::create_dir(temp.path().join("child-dir")).unwrap();
let provider = Arc::new(WritingProvider::new());
let output = run_subagents(
SubagentsArgs {
concurrency: Some(1),
tasks: vec![SubagentTask {
intent: "write child file".into(),
agent: None,
identity: None,
context: None,
cwd: Some(PathBuf::from("child-dir")),
}],
},
config(provider, temp.path()),
)
.unwrap();
let changed_file = temp
.path()
.join("child-dir/child.txt")
.canonicalize()
.unwrap();
assert_eq!(
std::fs::read_to_string(&changed_file).unwrap(),
"made by child"
);
assert_eq!(
output.results[0].cwd,
temp.path().join("child-dir").canonicalize().unwrap()
);
assert_eq!(output.results[0].changed_files, vec![changed_file]);
}
#[test]
fn resolve_child_cwd_rejects_missing_non_directory_and_symlink_escape() {
let parent = tempfile::TempDir::new().unwrap();
let missing = resolve_child_cwd(parent.path(), Some(Path::new("missing")), true)
.unwrap_err()
.to_string();
assert!(
missing.contains("No such file") || missing.contains("not found"),
"{missing}"
);
std::fs::write(parent.path().join("file-cwd"), "file").unwrap();
let file_err = resolve_child_cwd(parent.path(), Some(Path::new("file-cwd")), true)
.unwrap_err()
.to_string();
assert!(file_err.contains("not a directory"), "{file_err}");
#[cfg(unix)]
{
use std::os::unix::fs::symlink;
let outside = tempfile::TempDir::new().unwrap();
symlink(outside.path(), parent.path().join("escape-link")).unwrap();
let link_err = resolve_child_cwd(parent.path(), Some(Path::new("escape-link")), true)
.unwrap_err()
.to_string();
assert!(link_err.contains("escapes parent cwd"), "{link_err}");
}
}
#[test]
fn multiple_child_sessions_are_unique() {
let temp = tempfile::TempDir::new().unwrap();
let provider = Arc::new(CountingProvider::new("never"));
let mut cfg = config(provider, temp.path());
cfg.sessions_root = Some(temp.path().join("sessions"));
let output = run_subagents(
SubagentsArgs {
concurrency: Some(2),
tasks: vec![
SubagentTask {
intent: "one".into(),
agent: None,
identity: None,
context: None,
cwd: None,
},
SubagentTask {
intent: "two".into(),
agent: None,
identity: None,
context: None,
cwd: None,
},
],
},
cfg,
)
.unwrap();
let first = &output.results[0];
let second = &output.results[1];
assert!(first.session_id.is_some() && second.session_id.is_some());
assert_ne!(first.session_id, second.session_id);
assert_ne!(first.session_path, second.session_path);
for path in [
first.session_path.as_ref().unwrap(),
second.session_path.as_ref().unwrap(),
] {
assert!(path.starts_with(temp.path().join("sessions/subagents")));
assert!(path.exists());
}
}