#[cfg(test)]
mod tests {
use super::super::server::BobbinMcpServer;
#[test]
fn every_tool_is_annotated_explicitly() {
let tools = BobbinMcpServer::advertised_tools();
assert!(
tools.len() >= 30,
"expected the full tool surface, got {} — a truncated or empty \
router makes every check below vacuous",
tools.len()
);
let mut unannotated = Vec::new();
let mut implicit = Vec::new();
for tool in &tools {
let Some(ann) = tool.annotations.as_ref() else {
unannotated.push(tool.name.to_string());
continue;
};
if ann.destructive_hint.is_none() {
implicit.push(format!("{}: destructive_hint", tool.name));
}
if ann.open_world_hint.is_none() {
implicit.push(format!("{}: open_world_hint", tool.name));
}
if ann.read_only_hint.is_none() {
implicit.push(format!("{}: read_only_hint", tool.name));
}
}
assert!(
unannotated.is_empty(),
"{} tool(s) ship with NO annotations: {:?}\n\
An unannotated tool is un-callable for every codex worker under \
`approval_policy = never`. Add `annotations(...)` to its \
`#[tool(...)]` — see the module docs for the classification.",
unannotated.len(),
unannotated
);
assert!(
implicit.is_empty(),
"{} hint(s) left implicit: {:?}\n\
Codex resolves a missing hint with `unwrap_or(true)`, so omitting \
one is the same as declaring the tool dangerous. Set it \
explicitly, even where the MCP spec calls it 'not meaningful'.",
implicit.len(),
implicit
);
}
#[test]
fn read_only_tools_are_not_destructive() {
let tools = BobbinMcpServer::advertised_tools();
assert!(!tools.is_empty(), "control: the router must not be empty");
let bad: Vec<String> = tools
.iter()
.filter_map(|t| {
let ann = t.annotations.as_ref()?;
(ann.read_only_hint == Some(true) && ann.destructive_hint == Some(true))
.then(|| t.name.to_string())
})
.collect();
assert!(
bad.is_empty(),
"tool(s) marked BOTH read-only and destructive: {:?}",
bad
);
}
#[test]
fn the_tool_surface_is_the_expected_size() {
let tools = BobbinMcpServer::advertised_tools();
let mut names: Vec<&str> = tools.iter().map(|t| t.name.as_ref()).collect();
names.sort_unstable();
assert_eq!(
tools.len(),
35,
"tool count changed — surface is now: {:?}",
names
);
}
}