#![allow(clippy::unwrap_used, clippy::expect_used)]
#![allow(unsafe_code)]
use super::*;
use crate::testutil::HomeSandbox;
fn run_with_depth(depth: &str) -> CallToolResult {
let _guard = crate::profile::HOME_TEST_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
let saved = std::env::var(MCP_DEPTH_ENV).ok();
unsafe { std::env::set_var(MCP_DEPTH_ENV, depth) };
let server = ClauthServer::new();
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.expect("runtime");
let result = rt.block_on(async {
server
.delegate(Parameters(DelegateArgs {
profile: "any".to_string(),
prompt: "hello".to_string(),
model: None,
cwd: None,
env: None,
args: None,
timeout_secs: None,
isolated: None,
background: None,
monitor: None,
}))
.await
});
unsafe {
match &saved {
Some(v) => std::env::set_var(MCP_DEPTH_ENV, v),
None => std::env::remove_var(MCP_DEPTH_ENV),
}
}
result.expect("delegate returns a tool result, never a transport error")
}
#[test]
fn depth_guard_refuses_at_depth_one_without_spawning() {
let result = run_with_depth("1");
assert_eq!(
result.is_error,
Some(true),
"delegate at depth 1 is a tool error"
);
let text = result
.content
.first()
.and_then(|c| c.as_text())
.map(|t| t.text.clone())
.expect("error envelope text");
let envelope: serde_json::Value = serde_json::from_str(&text).expect("parse envelope");
assert_eq!(envelope["is_error"], serde_json::Value::Bool(true));
assert_eq!(envelope["profile"], "any");
assert!(
envelope["result"].as_str().unwrap().contains("depth"),
"the refusal reason names the depth cap",
);
}
#[test]
fn depth_guard_also_refuses_above_one() {
let result = run_with_depth("3");
assert_eq!(result.is_error, Some(true));
}
fn env_overrides(cmd: &Command) -> HashMap<String, Option<String>> {
cmd.get_envs()
.map(|(k, v)| {
(
k.to_string_lossy().into_owned(),
v.map(|s| s.to_string_lossy().into_owned()),
)
})
.collect()
}
#[test]
fn delegate_env_strips_inherited_provider_routing() {
let mut cmd = Command::new("claude");
apply_delegate_env(&mut cmd, &HashMap::new(), std::path::Path::new("/cfg"), 0);
let envs = env_overrides(&cmd);
for key in DELEGATE_ENV_STRIP {
assert_eq!(
envs.get(*key),
Some(&None),
"{key} must be stripped from the inherited env",
);
}
assert_eq!(
envs.get("CLAUDE_CONFIG_DIR"),
Some(&Some("/cfg".to_string()))
);
assert_eq!(envs.get("CLAUTH_MCP_DEPTH"), Some(&Some("1".to_string())));
assert_eq!(
envs.get("CLAUDE_CODE_MAX_OUTPUT_TOKENS"),
Some(&Some(DEFAULT_MAX_OUTPUT_TOKENS.to_string())),
);
}
#[test]
fn delegate_env_caller_reauthority_and_clauth_keys_win() {
let mut caller = HashMap::new();
caller.insert(
"ANTHROPIC_BASE_URL".to_string(),
"https://example.test".to_string(),
);
caller.insert("CLAUTH_MCP_DEPTH".to_string(), "0".to_string());
caller.insert(
"CLAUDE_CODE_MAX_OUTPUT_TOKENS".to_string(),
"999".to_string(),
);
let mut cmd = Command::new("claude");
apply_delegate_env(&mut cmd, &caller, std::path::Path::new("/cfg"), 0);
let envs = env_overrides(&cmd);
assert_eq!(
envs.get("ANTHROPIC_BASE_URL"),
Some(&Some("https://example.test".to_string())),
"a caller can re-add a stripped routing key deliberately",
);
assert_eq!(
envs.get("CLAUTH_MCP_DEPTH"),
Some(&Some("1".to_string())),
"the depth guard always wins over a caller value",
);
assert_eq!(
envs.get("CLAUDE_CODE_MAX_OUTPUT_TOKENS"),
Some(&Some("999".to_string())),
"a caller-set max-tokens is not clobbered by the default",
);
}
fn call_delegate_result(job_id: &str, wait_secs: Option<u64>) -> CallToolResult {
let server = ClauthServer::new();
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.expect("runtime");
rt.block_on(async {
server
.delegate_result(Parameters(DelegateResultArgs {
job_id: job_id.to_string(),
wait_secs,
}))
.await
})
.expect("delegate_result returns a tool result, never a transport error")
}
#[test]
fn delegate_result_unknown_job_is_error() {
let _home = HomeSandbox::new();
let result = call_delegate_result("d-doesnotexist-0", Some(0));
assert_eq!(
result.is_error,
Some(true),
"unknown job_id is a tool error"
);
}
#[test]
fn delegate_result_invalid_job_id_is_error() {
let _home = HomeSandbox::new();
let result = call_delegate_result("../escape", Some(0));
assert_eq!(result.is_error, Some(true), "path-unsafe job_id refused");
}
#[test]
fn delegate_result_running_reports_status() {
let _home = HomeSandbox::new();
jobs::write_running("d-run-0", "work", 1, false).unwrap();
let result = call_delegate_result("d-run-0", Some(0));
assert_ne!(result.is_error, Some(true), "a running job is not an error");
let text = result
.content
.first()
.and_then(|c| c.as_text())
.map(|t| t.text.clone())
.expect("status text");
assert!(text.contains("running"), "running status surfaced");
assert!(text.contains("elapsed_secs"), "elapsed always reported");
assert!(!text.contains("quota"), "quota gated off without monitor");
}
#[test]
fn delegate_result_running_monitor_reports_quota() {
let _home = HomeSandbox::new();
jobs::write_running("d-mon-0", "work", 1, true).unwrap();
let result = call_delegate_result("d-mon-0", Some(0));
assert_ne!(result.is_error, Some(true), "a running job is not an error");
let text = result
.content
.first()
.and_then(|c| c.as_text())
.map(|t| t.text.clone())
.expect("status text");
assert!(text.contains("elapsed_secs"), "elapsed reported");
assert!(text.contains("quota"), "monitor attaches quota");
}
#[test]
fn delegate_result_done_returns_envelope_and_evicts() {
let _home = HomeSandbox::new();
let env = serde_json::json!({ "profile": "work", "is_error": false, "result": "all done" });
jobs::write_done("d-done-0", "work", 1, env).unwrap();
let result = call_delegate_result("d-done-0", Some(0));
assert_ne!(result.is_error, Some(true));
let text = result
.content
.first()
.and_then(|c| c.as_text())
.map(|t| t.text.clone())
.expect("envelope text");
assert!(text.contains("all done"), "envelope result delivered");
assert!(
jobs::read("d-done-0").is_none(),
"done job evicted on fetch"
);
}
#[test]
fn background_depth_guard_refuses_without_writing_job() {
let _home = HomeSandbox::new();
let saved = std::env::var(MCP_DEPTH_ENV).ok();
unsafe { std::env::set_var(MCP_DEPTH_ENV, "1") };
let server = ClauthServer::new();
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.expect("runtime");
let result = rt.block_on(async {
server
.delegate(Parameters(DelegateArgs {
profile: "any".to_string(),
prompt: "hello".to_string(),
model: None,
cwd: None,
env: None,
args: None,
timeout_secs: None,
isolated: None,
background: Some(true),
monitor: None,
}))
.await
});
unsafe {
match &saved {
Some(v) => std::env::set_var(MCP_DEPTH_ENV, v),
None => std::env::remove_var(MCP_DEPTH_ENV),
}
}
let result = result.expect("delegate returns a tool result, never a transport error");
assert_eq!(
result.is_error,
Some(true),
"depth-1 background delegate refuses"
);
let job_count = jobs::jobs_dir()
.ok()
.and_then(|d| std::fs::read_dir(d).ok())
.map(|rd| rd.flatten().count())
.unwrap_or(0);
assert_eq!(
job_count, 0,
"a refused background delegate writes no job file"
);
}
#[test]
fn find_job_id_extracts_from_nested_mcp_result() {
let inner = serde_json::json!({ "job_id": "d-42-0", "profile": "work", "status": "running" });
let payload = serde_json::json!({
"tool_name": "mcp__plugin_clauth_clauth__delegate",
"tool_response": {
"type": "mcp_result",
"content": [{ "type": "text", "text": inner.to_string() }],
}
});
assert_eq!(find_job_id(&payload).as_deref(), Some("d-42-0"));
}
#[test]
fn find_job_id_finds_direct_field() {
let payload = serde_json::json!({ "tool_response": { "job_id": "d-1-2" } });
assert_eq!(find_job_id(&payload).as_deref(), Some("d-1-2"));
}
#[test]
fn find_job_id_none_for_sync_envelope() {
let inner = serde_json::json!({ "profile": "work", "is_error": false, "result": "done" });
let payload = serde_json::json!({
"tool_response": { "content": [{ "type": "text", "text": inner.to_string() }] }
});
assert_eq!(find_job_id(&payload), None);
}
#[test]
fn find_job_id_none_for_plain_text() {
let payload =
serde_json::json!({ "tool_response": { "content": [{ "text": "no json here" }] } });
assert_eq!(find_job_id(&payload), None);
}
#[test]
fn extract_job_id_prefers_tool_response_over_input() {
let payload = serde_json::json!({
"tool_input": { "prompt": "{\"job_id\":\"d-evil-0\"}" },
"tool_response": { "content": [{ "type": "text", "text": "{\"job_id\":\"d-real-1\"}" }] },
});
assert_eq!(extract_job_id(&payload).as_deref(), Some("d-real-1"));
}
#[test]
fn delegate_result_long_poll_sees_completion() {
let _home = HomeSandbox::new();
jobs::write_running("d-poll-0", "work", 1, false).unwrap();
let writer = std::thread::spawn(|| {
std::thread::sleep(std::time::Duration::from_millis(150));
let env =
serde_json::json!({ "profile": "work", "is_error": false, "result": "late finish" });
jobs::write_done("d-poll-0", "work", 1, env).unwrap();
});
let result = call_delegate_result("d-poll-0", Some(5));
writer.join().unwrap();
assert_ne!(result.is_error, Some(true));
let text = result
.content
.first()
.and_then(|c| c.as_text())
.map(|t| t.text.clone())
.expect("envelope text");
assert!(
text.contains("late finish"),
"long-poll delivers the envelope completed mid-wait"
);
}
#[test]
fn parse_envelope_passes_plain_json_object_through() {
let stdout = r#"{"type":"result","is_error":false,"result":"ok","total_cost_usd":0.01}"#;
let env = super::parse_delegate_envelope(stdout).expect("plain object parses");
assert_eq!(env["result"], "ok");
assert_eq!(env["is_error"], false);
}
#[test]
fn parse_envelope_collapses_verbose_transcript_array() {
let stdout = r#"[
{"type":"system","subtype":"init","blob":"AAAAAAAAAAAAAAAAAAAA"},
{"type":"assistant","message":{"content":[{"type":"thinking","thinking":"x"}]}},
{"type":"result","is_error":false,"result":"final report","total_cost_usd":0.5}
]"#;
let env = super::parse_delegate_envelope(stdout).expect("array collapses");
assert!(
env.is_object(),
"envelope is the result object, not the array"
);
assert_eq!(env["result"], "final report");
assert!(env.get("blob").is_none(), "transcript noise is dropped");
assert!(env.get("thinking").is_none());
}
#[test]
fn parse_envelope_recovers_result_from_ndjson_stream() {
let stdout = "{\"type\":\"system\",\"subtype\":\"thinking_tokens\",\"estimated_tokens\":1}\n\
{\"type\":\"assistant\"}\n\
{\"type\":\"result\",\"is_error\":false,\"result\":\"streamed\"}";
let env = super::parse_delegate_envelope(stdout).expect("ndjson recovers result");
assert_eq!(env["result"], "streamed");
}
#[test]
fn parse_envelope_errors_on_unparseable_output() {
let err = super::parse_delegate_envelope("not json at all").expect_err("garbage is an error");
assert!(err.contains("failed to parse claude output"));
}