use crate::brain::tools::http::HttpClientTool;
use crate::brain::tools::{Tool, ToolExecutionContext};
use serde_json::json;
use uuid::Uuid;
fn ctx() -> ToolExecutionContext {
ToolExecutionContext::new(Uuid::new_v4()).with_auto_approve(true)
}
#[tokio::test]
async fn default_user_agent_is_opencrabs_with_version() {
let mut server = mockito::Server::new_async().await;
let url = server.url();
let expected_ua = concat!("opencrabs/", env!("CARGO_PKG_VERSION"));
let _mock = server
.mock("GET", "/anything")
.match_header("user-agent", expected_ua)
.with_status(200)
.with_body("ok")
.create_async()
.await;
let tool = HttpClientTool;
let input = json!({
"method": "GET",
"url": format!("{}/anything", url),
});
let result = tool.execute(input, &ctx()).await.expect("tool execute");
assert!(
result.success,
"request should succeed when UA matches expected default, got: {:?}",
result.output
);
}
#[tokio::test]
async fn caller_supplied_user_agent_overrides_default() {
let mut server = mockito::Server::new_async().await;
let url = server.url();
let _mock = server
.mock("GET", "/anything")
.match_header("user-agent", "my-custom-agent/1.0")
.with_status(200)
.with_body("ok")
.create_async()
.await;
let tool = HttpClientTool;
let input = json!({
"method": "GET",
"url": format!("{}/anything", url),
"headers": { "User-Agent": "my-custom-agent/1.0" },
});
let result = tool.execute(input, &ctx()).await.expect("tool execute");
assert!(
result.success,
"caller-supplied UA should override default, got: {:?}",
result.output
);
}
#[tokio::test]
async fn forbidden_response_surfaces_body_to_caller() {
let mut server = mockito::Server::new_async().await;
let url = server.url();
let _mock = server
.mock("GET", "/forbidden")
.with_status(403)
.with_body("Request forbidden by administrative rules. Please make sure your request has a User-Agent header")
.create_async()
.await;
let tool = HttpClientTool;
let input = json!({
"method": "GET",
"url": format!("{}/forbidden", url),
});
let result = tool.execute(input, &ctx()).await.expect("tool execute");
assert!(
result.success,
"a completed HTTP response is a successful tool invocation"
);
let body = result.output;
assert!(
body.contains("403"),
"output should mention the status code: {}",
body
);
assert!(
body.contains("User-Agent header"),
"output should surface the server's explanation: {}",
body
);
assert!(
body.contains("non-2xx"),
"output should flag the non-2xx status so the model notices: {}",
body
);
assert_eq!(
result.metadata.get("status_code").map(String::as_str),
Some("403"),
"status code stays available in metadata"
);
}
#[tokio::test]
async fn not_found_is_a_successful_invocation_with_status_surfaced() {
let mut server = mockito::Server::new_async().await;
let url = server.url();
let _mock = server
.mock("GET", "/missing")
.with_status(404)
.with_body("not here")
.create_async()
.await;
let tool = HttpClientTool;
let input = json!({
"method": "GET",
"url": format!("{}/missing", url),
});
let result = tool.execute(input, &ctx()).await.expect("tool execute");
assert!(
result.success,
"a completed 404 response is a successful tool invocation"
);
assert!(
result.output.contains("404") && result.output.contains("non-2xx"),
"status and non-2xx note must reach the model: {}",
result.output
);
assert_eq!(
result.metadata.get("status_code").map(String::as_str),
Some("404")
);
}
#[tokio::test]
async fn server_error_is_still_a_successful_invocation() {
let mut server = mockito::Server::new_async().await;
let url = server.url();
let _mock = server
.mock("GET", "/boom")
.with_status(500)
.with_body("kaboom")
.create_async()
.await;
let tool = HttpClientTool;
let input = json!({ "method": "GET", "url": format!("{}/boom", url) });
let result = tool.execute(input, &ctx()).await.expect("tool execute");
assert!(
result.success,
"a completed 5xx response is not a tool failure"
);
assert!(result.output.contains("kaboom"), "body reaches the model");
}