use std::fs;
use std::path::{Path, PathBuf};
use a3s_code_core::config::{AgentDir, ToolSpec};
fn write(dir: &Path, rel: &str, content: &str) {
let path = dir.join(rel);
fs::create_dir_all(path.parent().unwrap()).unwrap();
fs::write(path, content).unwrap();
}
fn build_full_agent_dir() -> tempfile::TempDir {
let dir = tempfile::tempdir().unwrap();
let p = dir.path();
write(p, "instructions.md", "You are a release agent. Be terse.\n");
write(
p,
"agent.acl",
r#"
default_model = "anthropic/claude-sonnet-4-20250514"
providers "anthropic" {
api_key = "test-key"
models "claude-sonnet-4-20250514" { name = "Claude Sonnet 4" }
}
"#,
);
write(
p,
"skills/summarize.md",
"---\nname: summarize\ndescription: summarize text\n---\n# Summarize\n",
);
write(
p,
"schedules/daily.md",
"---\ncron: \"0 9 * * *\"\nname: daily-report\n---\nGenerate the daily report.\n",
);
write(
p,
"schedules/paused.md",
"---\ncron: \"*/5 * * * *\"\nenabled: false\n---\nThis one is paused.\n",
);
write(
p,
"tools/github.md",
"---\nkind: mcp\nname: github\ntransport: stdio\ncommand: echo\nargs: [\"hi\"]\n---\nGitHub MCP tools.\n",
);
write(
p,
"tools/search.md",
"---\nkind: script\nname: search-auth\npath: scripts/search.js\nallowed_tools: [grep, read]\nlimits:\n timeoutMs: 20000\n maxToolCalls: 8\n---\nFind auth-related files.\n",
);
dir
}
#[test]
fn full_agent_dir_loads_every_convention() {
let dir = build_full_agent_dir();
let agent = AgentDir::load(dir.path()).expect("agent dir loads");
assert_eq!(
agent.prompt_slots.role.as_deref(),
Some("You are a release agent. Be terse.")
);
assert_eq!(
agent.config.default_model.as_deref(),
Some("anthropic/claude-sonnet-4-20250514"),
"agent.acl default_model must override the default config"
);
assert!(
agent
.config
.skill_dirs
.iter()
.any(|d| d.ends_with("skills")),
"skills/ dir must be appended to skill_dirs"
);
assert_eq!(agent.schedules.len(), 2);
let daily = agent
.schedules
.iter()
.find(|s| s.name == "daily-report")
.expect("named schedule");
assert_eq!(daily.cron, "0 9 * * *");
assert_eq!(daily.prompt, "Generate the daily report.");
assert!(daily.enabled);
let paused = agent
.schedules
.iter()
.find(|s| s.name == "paused")
.expect("file-stem-named schedule");
assert!(!paused.enabled, "enabled: false must be honored");
assert_eq!(agent.tools.len(), 2);
let gh = agent.tools.iter().find(|t| t.name() == "github").unwrap();
assert_eq!(gh.kind(), "mcp");
let search = agent
.tools
.iter()
.find(|t| t.name() == "search-auth")
.expect("script tool");
assert_eq!(search.kind(), "script");
let ToolSpec::Script(spec) = search else {
panic!("expected a script tool spec");
};
assert_eq!(spec.path, PathBuf::from("scripts/search.js"));
assert_eq!(spec.description, "Find auth-related files.");
assert_eq!(
spec.allowed_tools.as_deref(),
Some(["grep".to_string(), "read".to_string()].as_slice())
);
assert_eq!(spec.limits.timeout_ms, Some(20000));
assert_eq!(spec.limits.max_tool_calls, Some(8));
}
#[test]
fn minimal_agent_dir_loads_with_empty_specs() {
let dir = tempfile::tempdir().unwrap();
write(dir.path(), "instructions.md", "Minimal agent.\n");
let agent = AgentDir::load(dir.path()).expect("minimal dir loads");
assert_eq!(agent.prompt_slots.role.as_deref(), Some("Minimal agent."));
assert!(agent.schedules.is_empty());
assert!(agent.tools.is_empty());
assert!(agent.config.default_model.is_none());
}
#[test]
fn missing_dir_or_instructions_is_an_error() {
assert!(AgentDir::load("/nonexistent/a3s/agent/dir").is_err());
let dir = tempfile::tempdir().unwrap();
fs::create_dir_all(dir.path().join("skills")).unwrap();
assert!(
AgentDir::load(dir.path()).is_err(),
"instructions.md is required"
);
}