use super::*;
use std::collections::HashMap;
use std::fs;
use std::path::Path;
use tempfile::TempDir;
fn write_agent(dir: &Path, file: &str, contents: &str) {
fs::write(dir.join(file), contents).unwrap();
}
#[test]
fn a_custom_agent_naming_a_builtin_binary_is_refused() {
let dir = TempDir::new().unwrap();
write_agent(
dir.path(),
"fork.toml",
r#"[agent]
id = "l2-researcher"
display_name = "L2 Researcher"
command = "droid"
prompt_mode = "arg"
"#,
);
write_agent(
dir.path(),
"wrapper.toml",
r#"[agent]
id = "goose"
display_name = "Goose"
command = "goose"
prompt_mode = "arg"
"#,
);
let agents = load_from_dir(dir.path());
assert!(
!agents.contains_key("l2-researcher"),
"a droid fork must not load"
);
assert!(
agents.contains_key("goose"),
"a genuinely different CLI must still load"
);
}
#[test]
fn a_path_qualified_builtin_binary_is_refused_too() {
let config = parse_config(
r#"[agent]
id = "sneaky"
display_name = "Sneaky"
command = "/usr/local/bin/codex"
prompt_mode = "arg"
"#,
)
.unwrap();
assert!(fork_of_builtin_reason(&config).is_some());
}
#[test]
fn a_delegating_agent_is_still_allowed() {
let config = parse_config(
r#"[agent]
id = "mimo"
display_name = "MiMo"
command = "opencode"
prompt_mode = "arg"
delegate_to = "opencode"
forced_model = "mimo/mimo-v2.5-pro"
"#,
)
.unwrap();
assert!(fork_of_builtin_reason(&config).is_none());
}
fn sample_agent_toml(id: &str) -> String {
format!(
r#"[agent]
id = "{id}"
display_name = "{id} agent"
command = "{id}"
"#,
id = id
)
}
#[test]
fn empty_dir_returns_empty_registry() {
let dir = TempDir::new().unwrap();
assert!(load_from_dir(dir.path()).is_empty());
}
#[test]
fn loads_valid_toml() {
let dir = TempDir::new().unwrap();
write_agent(dir.path(), "foo.toml", &sample_agent_toml("foo"));
let map = load_from_dir(dir.path());
assert!(map.contains_key("foo"));
}
#[test]
fn skips_invalid_toml() {
let dir = TempDir::new().unwrap();
write_agent(dir.path(), "bad.toml", "not = valid = toml");
assert!(load_from_dir(dir.path()).is_empty());
}
#[test]
fn resolve_returns_none_for_unknown() {
let map = HashMap::new();
assert!(resolve_from_registry(&map, "missing").is_none());
}
#[test]
fn list_returns_sorted() {
let dir = TempDir::new().unwrap();
write_agent(dir.path(), "b.toml", &sample_agent_toml("b"));
write_agent(dir.path(), "a.toml", &sample_agent_toml("a"));
let map = load_from_dir(dir.path());
let list = list_from_registry(&map);
let ids: Vec<_> = list.iter().map(|c| c.id.as_str()).collect();
assert_eq!(ids, vec!["a", "b"]);
}
#[test]
fn delegate_to_opencode_returns_overlay_agent() {
let toml_data = r#"[agent]
id = "mimo"
display_name = "MiMo"
command = "bash"
delegate_to = "opencode"
forced_model = "mimo/mimo-v2.5-pro"
"#;
let config = parse_config(toml_data).unwrap();
let agent = build_agent(&config);
let opts = super::super::RunOpts {
dir: None,
output: None,
result_file: None,
model: None,
budget: false,
read_only: false,
sandbox: false,
context_files: Vec::new(),
session_id: None,
env: None,
env_forward: None,
};
let cmd = agent.build_command("hello", &opts).unwrap();
let program = cmd.get_program().to_string_lossy().into_owned();
assert_eq!(program, "opencode");
let args: Vec<String> = cmd.get_args().map(|a| a.to_string_lossy().into_owned()).collect();
assert!(args.iter().any(|a| a == "-m"));
assert!(args.iter().any(|a| a == "mimo/mimo-v2.5-pro"));
}
#[test]
fn delegate_to_opencode_uses_binary_extra_args_and_rate_limit_kind() {
let toml_data = r#"[agent]
id = "mimocode"
display_name = "MiMo Code"
command = "bash"
delegate_to = "opencode"
forced_model = "mimo/mimo-auto"
binary = "mimo"
extra_args = ["--dangerously-skip-permissions"]
rate_limit_kind = "mimocode"
"#;
let config = parse_config(toml_data).unwrap();
let agent = build_agent(&config);
let opts = super::super::RunOpts {
dir: None,
output: None,
result_file: None,
model: None,
budget: false,
read_only: false,
sandbox: false,
context_files: Vec::new(),
session_id: None,
env: None,
env_forward: None,
};
let cmd = agent.build_command("hello", &opts).unwrap();
assert_eq!(cmd.get_program().to_string_lossy(), "mimo");
let args: Vec<String> = cmd
.get_args()
.map(|arg| arg.to_string_lossy().into_owned())
.collect();
assert!(args.contains(&"--dangerously-skip-permissions".to_string()));
assert!(args.windows(2).any(|pair| pair == ["-m", "mimo/mimo-auto"]));
}
#[test]
fn missing_forced_model_falls_back_to_bash_wrapper() {
let toml_data = r#"[agent]
id = "broken"
display_name = "Broken"
command = "bash"
delegate_to = "opencode"
"#;
let config = parse_config(toml_data).unwrap();
let agent = build_agent(&config);
let opts = super::super::RunOpts {
dir: None,
output: None,
result_file: None,
model: None,
budget: false,
read_only: false,
sandbox: false,
context_files: Vec::new(),
session_id: None,
env: None,
env_forward: None,
};
let cmd = agent.build_command("hi", &opts).unwrap();
assert_eq!(cmd.get_program().to_string_lossy(), "bash");
}
#[test]
fn ordinary_claude_delegate_custom_agent_still_resolves() {
let home = TempDir::new().unwrap();
let _guard = crate::support::paths::AidHomeGuard::set(home.path());
let dir = agents_dir();
fs::create_dir_all(&dir).unwrap();
write_agent(
&dir,
"delegate.toml",
concat!(
"[agent]\nid = 'delegate'\ndisplay_name = 'Delegate'\ncommand = 'wrapper'\n",
"delegate_to = 'claude'\nforced_model = 'opus'\n"
),
);
let agent = resolve_custom_agent("delegate").unwrap();
assert!(agent.api_route().is_none());
assert_eq!(agent.rate_limit_name(), Some("delegate"));
}