use super::*;
struct StubCrew;
#[async_trait::async_trait]
impl crate::agentic::CrewRunner for StubCrew {
async fn dispatch(
&self,
op: &str,
_args: &serde_json::Value,
_caveats: &Caveats,
) -> Result<String, String> {
match op {
"compose_roster" => Ok("proposed roster: planner <- qwen3-coder:30b".to_string()),
"crew" => Ok("crew ran: diff +1/-0, status PASS".to_string()),
other => Err(format!("unknown op: {other}")),
}
}
}
async fn run_crew_tool(
name: &str,
args: serde_json::Value,
crew: Option<&dyn crate::agentic::CrewRunner>,
) -> String {
let ws = tempfile::TempDir::new().unwrap();
execute_tool(
name,
&args,
&ws.path().to_string_lossy(),
false,
20,
&caveats_rw(ws.path()),
&mut NoMcp,
None, None, None, None, None, None, None, crew,
None, None, None, None, None, )
.await
}
#[tokio::test]
async fn crew_arm_dispatches_when_injected() {
let out = run_crew_tool(
"crew",
serde_json::json!({ "task": "do X" }),
Some(&StubCrew),
)
.await;
assert!(
out.contains("crew ran") && out.contains("PASS"),
"got: {out}"
);
let out = run_crew_tool(
"compose_roster",
serde_json::json!({ "mode": "crew" }),
Some(&StubCrew),
)
.await;
assert!(out.contains("proposed roster"), "got: {out}");
}
#[tokio::test]
async fn crew_arm_without_injection_coaches_recovery() {
for name in ["crew", "compose_roster"] {
let out = run_crew_tool(name, serde_json::json!({ "task": "x" }), None).await;
assert!(out.contains("NEWT_TEAM"), "{name}: {out}");
assert!(out.contains("read_file"), "{name}: {out}");
assert!(!out.contains("unknown tool"), "{name}: {out}");
}
}
#[test]
fn crew_off_recovery_result_names_gate_and_alternative() {
let out = crew_off_recovery_result("crew");
assert!(out.contains("'crew'"), "{out}");
assert!(out.contains("NEWT_TEAM"), "{out}");
assert!(out.contains("write_file"), "{out}");
assert!(!out.contains("unknown tool"), "{out}");
}
#[test]
fn classify_gated_off_reach_only_fires_for_off_crew_names() {
for name in ["crew", "compose_roster"] {
assert_eq!(
classify_gated_off_reach(name, false),
Some(crate::PhantomResolution::GatedOff(
"crew/team surface off (NEWT_TEAM)".into()
)),
"{name} OFF should record GatedOff"
);
assert_eq!(
classify_gated_off_reach(name, true),
None,
"{name} ON dispatches normally — no phantom"
);
}
assert_eq!(classify_gated_off_reach("read_file", false), None);
assert_eq!(classify_gated_off_reach("read_file", true), None);
}
#[test]
fn crew_names_stay_real_and_unflagged_by_existing_seams() {
for name in ["crew", "compose_roster"] {
assert!(
!is_hallucination(name, &serde_json::json!({ "task": "x" })),
"{name} must stay a real tool name"
);
assert_eq!(
classify_phantom_reach(name, &serde_json::json!({ "task": "x" }), "ok", true),
None,
"{name} must not be flagged by classify_phantom_reach"
);
}
}