1use crate::error::ApiError;
7
8pub const USAGE_API_URL: &str = "https://api.anthropic.com/api/oauth/usage";
10
11pub const BETA_HEADER: &str = "oauth-2025-04-20";
13
14#[cfg(feature = "blocking")]
36pub fn fetch_usage_raw(token: &str) -> Result<String, ApiError> {
37 let client = reqwest::blocking::Client::new();
38
39 let response = client
40 .get(USAGE_API_URL)
41 .header("Authorization", format!("Bearer {}", token))
42 .header("anthropic-beta", BETA_HEADER)
43 .send()
44 .map_err(|_| ApiError::Network("Failed to connect to Anthropic API".to_string()))?;
46
47 map_response(response)
48}
49
50#[cfg(feature = "blocking")]
52fn map_response(response: reqwest::blocking::Response) -> Result<String, ApiError> {
53 let status = response.status().as_u16();
54
55 match status {
56 200 => response
57 .text()
58 .map_err(|_| ApiError::Network("Failed to read response body".to_string())),
59 401 => Err(ApiError::Unauthorized),
60 429 => {
61 let retry_after = response
62 .headers()
63 .get("retry-after")
64 .and_then(|v| v.to_str().ok())
65 .map(String::from);
66 Err(ApiError::RateLimited { retry_after })
67 }
68 500..=599 => Err(ApiError::Server(status)),
69 _ => Err(ApiError::Unexpected(status)),
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[test]
78 fn test_api_url_is_correct() {
79 assert_eq!(USAGE_API_URL, "https://api.anthropic.com/api/oauth/usage");
80 }
81
82 #[test]
83 fn test_beta_header_is_correct() {
84 assert_eq!(BETA_HEADER, "oauth-2025-04-20");
85 }
86
87 #[test]
89 #[ignore = "requires real API credentials"]
90 #[cfg(feature = "blocking")]
91 fn test_fetch_usage_raw_integration() {
92 let token = std::env::var("CLAUDE_CODE_OAUTH_TOKEN")
94 .expect("CLAUDE_CODE_OAUTH_TOKEN must be set for integration test");
95
96 let result = fetch_usage_raw(&token);
97 match result {
98 Ok(body) => {
99 assert!(body.contains("five_hour"));
100 assert!(body.contains("seven_day"));
101 println!("API response received successfully");
102 }
103 Err(ApiError::Unauthorized) => {
104 println!("Token is invalid or expired");
105 }
106 Err(e) => {
107 panic!("Unexpected error: {}", e);
108 }
109 }
110 }
111
112 #[test]
113 #[cfg(feature = "blocking")]
114 fn test_fetch_with_invalid_token() {
115 let result = fetch_usage_raw("invalid-token");
117 assert!(matches!(result, Err(ApiError::Unauthorized)));
118 }
119}