use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
fn create_local_ati_dir() -> tempfile::TempDir {
let dir = tempfile::tempdir().expect("create tempdir");
let manifests_dir = dir.path().join("manifests");
std::fs::create_dir_all(&manifests_dir).expect("create manifests");
std::fs::write(
manifests_dir.join("test.toml"),
r#"
[provider]
name = "test_provider"
description = "Test provider"
base_url = "http://unused"
auth_type = "none"
[[tools]]
name = "web_search"
description = "Search"
endpoint = "/search"
method = "GET"
scope = "tool:web_search"
"#,
)
.expect("write manifest");
dir
}
#[tokio::test]
async fn test_call_without_proxy_uses_local_mode() {
std::env::remove_var("ATI_PROXY_URL");
let output = std::process::Command::new(env!("CARGO_BIN_EXE_ati"))
.args(["run", "web_search", "--query", "test"])
.env_remove("ATI_PROXY_URL")
.env("ATI_DIR", "/tmp/ati-test-nonexistent")
.output()
.expect("Failed to execute ati");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(!output.status.success());
assert!(
!stderr.contains("ATI_PROXY_URL"),
"Should not mention proxy URL in local mode. stderr: {stderr}"
);
}
#[tokio::test]
async fn test_call_with_proxy_url_routes_to_proxy() {
let mock_server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/call"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"result": {"data": "from proxy"},
"error": null
})))
.mount(&mock_server)
.await;
let output = std::process::Command::new(env!("CARGO_BIN_EXE_ati"))
.args(["run", "web_search", "--query", "test"])
.env("ATI_PROXY_URL", mock_server.uri())
.env("ATI_DIR", "/tmp/ati-test-nonexistent") .output()
.expect("Failed to execute ati");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"Proxy call should succeed. stderr: {stderr}"
);
assert!(
stdout.contains("from proxy"),
"Should contain proxy response. stdout: {stdout}"
);
}
#[tokio::test]
async fn test_verbose_shows_proxy_mode() {
let mock_server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/call"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"result": "ok",
"error": null
})))
.mount(&mock_server)
.await;
let output = std::process::Command::new(env!("CARGO_BIN_EXE_ati"))
.args(["--verbose", "run", "some_tool"])
.env("ATI_PROXY_URL", mock_server.uri())
.env("ATI_DIR", "/tmp/ati-test-nonexistent")
.output()
.expect("Failed to execute ati");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("mode: proxy"),
"Verbose output should show proxy mode. stderr: {stderr}"
);
}
#[tokio::test]
async fn test_proxy_error_propagated() {
let mock_server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/call"))
.respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
.mount(&mock_server)
.await;
let output = std::process::Command::new(env!("CARGO_BIN_EXE_ati"))
.args(["run", "web_search", "--query", "test"])
.env("ATI_PROXY_URL", mock_server.uri())
.env("ATI_DIR", "/tmp/ati-test-nonexistent")
.output()
.expect("Failed to execute ati");
assert!(!output.status.success(), "Should fail on proxy 500 error");
}
#[tokio::test]
async fn test_help_with_proxy_url_routes_to_proxy() {
let mock_server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/help"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"content": "Use ati run web_search --query \"your query\"",
"error": null
})))
.mount(&mock_server)
.await;
let output = std::process::Command::new(env!("CARGO_BIN_EXE_ati"))
.args(["assist", "how do I search the web?"])
.env("ATI_PROXY_URL", mock_server.uri())
.env("ATI_DIR", "/tmp/ati-test-nonexistent")
.output()
.expect("Failed to execute ati");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"Proxy help call should succeed. stderr: {stderr}"
);
assert!(
stdout.contains("web_search"),
"Should contain proxy help response. stdout: {stdout}"
);
}
#[tokio::test]
async fn test_local_tool_list_requires_token_when_jwt_configured() {
let ati_dir = create_local_ati_dir();
let output = std::process::Command::new(env!("CARGO_BIN_EXE_ati"))
.args(["tool", "list"])
.env("ATI_DIR", ati_dir.path())
.env(
"ATI_JWT_SECRET",
"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff",
)
.output()
.expect("Failed to execute ati");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(!output.status.success(), "Expected auth failure");
assert!(
stderr.contains("ATI_SESSION_TOKEN is required"),
"Unexpected stderr: {stderr}"
);
}
#[tokio::test]
async fn test_local_tool_list_rejects_invalid_token_when_jwt_configured() {
let ati_dir = create_local_ati_dir();
let output = std::process::Command::new(env!("CARGO_BIN_EXE_ati"))
.args(["tool", "list"])
.env("ATI_DIR", ati_dir.path())
.env(
"ATI_JWT_SECRET",
"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff",
)
.env("ATI_SESSION_TOKEN", "not-a-valid-jwt")
.output()
.expect("Failed to execute ati");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(!output.status.success(), "Expected auth failure");
assert!(
stderr.contains("Invalid ATI_SESSION_TOKEN"),
"Unexpected stderr: {stderr}"
);
}