Skip to main content

agentctl_auth/
test_credential.rs

1//! Test credentials against provider APIs.
2
3use crate::credential::{Credential, TestResult};
4use crate::pool::AuthPool;
5use anyhow::Result;
6use std::time::Instant;
7
8/// Test a single credential against its provider's API.
9pub async fn test_credential(name: &str, credential: &Credential) -> TestResult {
10    let start = Instant::now();
11
12    let result = match credential.provider.as_str() {
13        "anthropic" => test_anthropic(credential).await,
14        "openai" => test_openai(credential).await,
15        _ => Err(anyhow::anyhow!(
16            "Unknown provider: {}",
17            credential.provider
18        )),
19    };
20
21    let latency_ms = start.elapsed().as_millis() as u64;
22
23    match result {
24        Ok((success, status_code)) => TestResult {
25            credential_name: name.to_string(),
26            provider: credential.provider.clone(),
27            success,
28            status_code: Some(status_code),
29            error: if success {
30                None
31            } else {
32                Some(format!("HTTP {}", status_code))
33            },
34            latency_ms,
35        },
36        Err(e) => TestResult {
37            credential_name: name.to_string(),
38            provider: credential.provider.clone(),
39            success: false,
40            status_code: None,
41            error: Some(e.to_string()),
42            latency_ms,
43        },
44    }
45}
46
47/// Test all credentials in the pool.
48pub async fn test_all(pool: &AuthPool) -> Vec<TestResult> {
49    let mut results = Vec::new();
50    for (name, cred) in pool.all_credentials() {
51        let result = test_credential(name, cred).await;
52        results.push(result);
53    }
54    results
55}
56
57/// Test an Anthropic API key.
58async fn test_anthropic(credential: &Credential) -> Result<(bool, u16)> {
59    let token = credential
60        .resolved_token()
61        .ok_or_else(|| anyhow::anyhow!("Credential has no token"))?;
62
63    let client = reqwest::Client::new();
64    let response = client
65        .post("https://api.anthropic.com/v1/messages")
66        .header("x-api-key", token)
67        .header("anthropic-version", "2023-06-01")
68        .header("content-type", "application/json")
69        .body(r#"{"model":"claude-sonnet-4-20250514","max_tokens":1,"messages":[{"role":"user","content":"hi"}]}"#)
70        .send()
71        .await?;
72
73    let status = response.status().as_u16();
74    let success = status == 200 || status == 429;
75    Ok((success, status))
76}
77
78/// Test an OpenAI API key.
79async fn test_openai(credential: &Credential) -> Result<(bool, u16)> {
80    let token = credential
81        .resolved_token()
82        .ok_or_else(|| anyhow::anyhow!("Credential has no token"))?;
83
84    let client = reqwest::Client::new();
85    let response = client
86        .get("https://api.openai.com/v1/models")
87        .header("Authorization", format!("Bearer {}", token))
88        .send()
89        .await?;
90
91    let status = response.status().as_u16();
92    let success = status == 200;
93    Ok((success, status))
94}