use super::*;
use crate::prompt::Error;
const WORKER: &str = crate::prompt::WORKER_ROLE;
use serde_json::json;
use std::fs;
use tempfile::TempDir;
fn write_schema(worktree: &Path, name: &str, body: &str) {
let dir = worktree.join(TOOLS_DESC_DIR);
fs::create_dir_all(&dir).unwrap();
fs::write(dir.join(format!("{name}.json")), body).unwrap();
}
fn write_skill(worktree: &Path, name: &str, frontmatter_body: &str) {
let dir = worktree.join(SKILLS_DESC_DIR);
fs::create_dir_all(&dir).unwrap();
fs::write(dir.join(format!("{name}.md")), frontmatter_body).unwrap();
}
fn custom(t: &Tool) -> (&str, Option<&str>, &Value) {
match t {
Tool::Custom {
name,
description,
input_schema,
..
} => (name.as_str(), description.as_deref(), input_schema),
_ => panic!("composed tools are Custom"),
}
}
const BASH_SCHEMA: &str = r#"{
"type": "object",
"properties": { "command": { "type": "string" } },
"required": ["command"]
}"#;
#[test]
fn declared_tool_with_schema_carries_it_verbatim() {
let wt = TempDir::new().unwrap();
write_schema(wt.path(), "bash", BASH_SCHEMA);
let tools = compose(wt.path(), WORKER, &["bash".to_string()], &[]).unwrap();
assert_eq!(tools.len(), 1);
let (name, description, input_schema) = custom(&tools[0]);
assert_eq!(name, "bash");
assert_eq!(
*input_schema,
serde_json::from_str::<Value>(BASH_SCHEMA).unwrap()
);
assert_eq!(description, None);
}
#[test]
fn skill_frontmatter_populates_the_tool_description() {
let wt = TempDir::new().unwrap();
write_schema(wt.path(), "bash", BASH_SCHEMA);
write_skill(
wt.path(),
"bash",
"name: bash\ndescription: Run a shell command.\n",
);
let tools = compose(wt.path(), WORKER, &["bash".to_string()], &[]).unwrap();
let (_, description, input_schema) = custom(&tools[0]);
assert_eq!(description, Some("Run a shell command."));
assert_eq!(
*input_schema,
serde_json::from_str::<Value>(BASH_SCHEMA).unwrap()
);
}
#[test]
fn malformed_skill_frontmatter_is_a_hard_error() {
let wt = TempDir::new().unwrap();
write_schema(wt.path(), "bash", BASH_SCHEMA);
write_skill(wt.path(), "bash", "not: a valid frontmatter\n");
let err = compose(wt.path(), WORKER, &["bash".to_string()], &[]).unwrap_err();
match err {
Error::SkillFrontmatter { name, .. } => assert_eq!(name, "bash"),
other => panic!("expected SkillFrontmatter, got {other:?}"),
}
}
#[test]
fn unreadable_skill_frontmatter_surfaces_io_error() {
let wt = TempDir::new().unwrap();
write_schema(wt.path(), "bash", BASH_SCHEMA);
fs::create_dir_all(wt.path().join(SKILLS_DESC_DIR).join("bash.md")).unwrap();
let err = compose(wt.path(), WORKER, &["bash".to_string()], &[]).unwrap_err();
match err {
Error::SkillFrontmatterIo { name, .. } => assert_eq!(name, "bash"),
other => panic!("expected SkillFrontmatterIo, got {other:?}"),
}
}
#[test]
fn empty_declaration_yields_empty_tools() {
let wt = TempDir::new().unwrap();
write_schema(wt.path(), "bash", BASH_SCHEMA);
assert!(compose(wt.path(), WORKER, &[], &[]).unwrap().is_empty());
}
#[test]
fn declared_tool_without_schema_is_dropped() {
let wt = TempDir::new().unwrap();
assert!(
compose(wt.path(), WORKER, &["bash".to_string()], &[])
.unwrap()
.is_empty()
);
}
#[test]
fn intersection_keeps_only_available_schemas_in_declared_order() {
let wt = TempDir::new().unwrap();
write_schema(wt.path(), "read_file", r#"{"type":"object"}"#);
write_schema(wt.path(), "bash", BASH_SCHEMA);
let declared = [
"bash".to_string(),
"missing".to_string(),
"read_file".to_string(),
];
let tools = compose(wt.path(), WORKER, &declared, &[]).unwrap();
let names: Vec<&str> = tools.iter().map(|t| custom(t).0).collect();
assert_eq!(names, vec!["bash", "read_file"]);
}
#[test]
fn present_but_malformed_schema_is_a_hard_error() {
let wt = TempDir::new().unwrap();
write_schema(wt.path(), "bash", "{ not json");
let err = compose(wt.path(), WORKER, &["bash".to_string()], &[]).unwrap_err();
match err {
Error::ToolSchemaJson { name, .. } => assert_eq!(name, "bash"),
other => panic!("expected ToolSchemaJson, got {other:?}"),
}
}
#[test]
fn unreadable_schema_file_surfaces_io_error() {
let wt = TempDir::new().unwrap();
fs::create_dir_all(wt.path().join(TOOLS_DESC_DIR).join("bash.json")).unwrap();
let err = compose(wt.path(), WORKER, &["bash".to_string()], &[]).unwrap_err();
match err {
Error::ToolSchemaIo { name, .. } => assert_eq!(name, "bash"),
other => panic!("expected ToolSchemaIo, got {other:?}"),
}
}
fn history_calling(names: &[&str]) -> Vec<Message> {
vec![Message {
role: brazen::Role::Assistant,
content: names
.iter()
.enumerate()
.map(|(i, name)| Content::ToolUse {
id: format!("toolu_{i}"),
name: (*name).to_string(),
input: json!({}),
signature: None,
})
.collect(),
}]
}
#[test]
fn a_tool_the_history_names_is_declared_with_its_committed_schema() {
let wt = TempDir::new().unwrap();
write_schema(wt.path(), "bash", BASH_SCHEMA);
write_skill(
wt.path(),
"bash",
"name: bash\ndescription: Run a shell command.\n",
);
let tools = compose(wt.path(), WORKER, &[], &history_calling(&["bash"])).unwrap();
assert_eq!(tools.len(), 1);
let (name, description, input_schema) = custom(&tools[0]);
assert_eq!(name, "bash");
assert_eq!(description, Some("Run a shell command."));
assert_eq!(
*input_schema,
serde_json::from_str::<Value>(BASH_SCHEMA).unwrap()
);
}
#[test]
fn a_tool_the_history_names_with_no_schema_still_gets_a_declaration() {
let wt = TempDir::new().unwrap();
let tools = compose(wt.path(), WORKER, &[], &history_calling(&["frobnicate"])).unwrap();
let (name, description, input_schema) = custom(&tools[0]);
assert_eq!(name, "frobnicate");
assert_eq!(description, None);
assert_eq!(*input_schema, json!({"type":"object"}));
}
#[test]
fn an_already_declared_tool_is_never_declared_twice() {
let wt = TempDir::new().unwrap();
write_schema(wt.path(), "bash", BASH_SCHEMA);
let declared = ["bash".to_string()];
let tools = compose(
wt.path(),
WORKER,
&declared,
&history_calling(&["bash", "bash"]),
)
.unwrap();
let names: Vec<&str> = tools.iter().map(|t| custom(t).0).collect();
assert_eq!(names, vec!["bash"]);
}
#[test]
fn a_compactor_gets_its_injected_pair_and_never_a_duplicate_of_it() {
let wt = TempDir::new().unwrap();
let history = history_calling(&["write_summary", "bash"]);
let tools = compose(wt.path(), "compactor", &[], &history).unwrap();
let names: Vec<&str> = tools.iter().map(|t| custom(t).0).collect();
assert_eq!(names, vec!["write_summary", "mark_for_deletion", "bash"]);
}
#[test]
fn a_history_with_no_tool_use_leaves_the_declaration_alone() {
let wt = TempDir::new().unwrap();
write_schema(wt.path(), "bash", BASH_SCHEMA);
let history = vec![Message {
role: brazen::Role::User,
content: vec![Content::Text("just talking".into())],
}];
let declared = ["bash".to_string()];
let tools = compose(wt.path(), WORKER, &declared, &history).unwrap();
let names: Vec<&str> = tools.iter().map(|t| custom(t).0).collect();
assert_eq!(names, vec!["bash"]);
}
#[test]
fn a_malformed_schema_for_a_history_named_tool_is_a_hard_error() {
let wt = TempDir::new().unwrap();
write_schema(wt.path(), "bash", "{ not json");
let err = compose(wt.path(), WORKER, &[], &history_calling(&["bash"])).unwrap_err();
match err {
Error::ToolSchemaJson { name, .. } => assert_eq!(name, "bash"),
other => panic!("expected ToolSchemaJson, got {other:?}"),
}
}
#[test]
fn schema_value_shape_is_preserved() {
let wt = TempDir::new().unwrap();
write_schema(
wt.path(),
"x",
r#"{"type":"object","properties":{"a":{"type":"number"}}}"#,
);
let tools = compose(wt.path(), WORKER, &["x".to_string()], &[]).unwrap();
assert_eq!(
*custom(&tools[0]).2,
json!({"type":"object","properties":{"a":{"type":"number"}}})
);
}