use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use super::{BearerAuthInterceptor, StaticTokenProvider, TokenProvider};
use crate::error::{ClientError, ClientResult};
use crate::interceptor::{CallInterceptor, ClientRequest};
#[derive(Default)]
struct Recorder(Mutex<Vec<String>>);
impl TokenProvider for Recorder {
fn access_token(&self) -> Pin<Box<dyn Future<Output = ClientResult<String>> + Send + '_>> {
Box::pin(async { Ok("tok".to_owned()) })
}
fn invalidate(&self, token: &str) {
self.0.lock().expect("log").push(token.to_owned());
}
}
fn status(status: u16) -> ClientError {
ClientError::UnexpectedStatus {
status,
body: String::new(),
retry_after: None,
}
}
async fn invalidated_after(header: Option<&str>, err: &ClientError) -> Vec<String> {
let recorder = Arc::new(Recorder::default());
let interceptor = BearerAuthInterceptor::new(Arc::clone(&recorder) as Arc<dyn TokenProvider>);
let mut req = ClientRequest::new("GetTask", serde_json::Value::Null);
if let Some(h) = header {
req.extra_headers
.insert("authorization".to_owned(), h.to_owned());
}
interceptor.on_error(&req, err).await;
recorder.0.lock().expect("log").clone()
}
#[tokio::test]
async fn a_401_invalidates_exactly_the_token_that_was_sent() {
assert_eq!(
invalidated_after(Some("Bearer tok-1"), &status(401)).await,
["tok-1"]
);
}
#[tokio::test]
async fn nothing_else_invalidates() {
for err in [
status(403),
status(500),
ClientError::Timeout("t".into()),
ClientError::Protocol(a2a_protocol_types::A2aError::internal("x")),
] {
assert!(
invalidated_after(Some("Bearer tok-1"), &err)
.await
.is_empty(),
"{err:?}"
);
}
}
#[tokio::test]
async fn a_non_bearer_or_missing_header_names_no_token() {
assert_eq!(
invalidated_after(Some("Basic abc"), &status(401)).await,
Vec::<String>::new()
);
assert_eq!(
invalidated_after(None, &status(401)).await,
Vec::<String>::new()
);
}
#[tokio::test]
async fn a_static_provider_keeps_its_token() {
let p = StaticTokenProvider::new("fixed");
p.invalidate("fixed");
assert_eq!(p.access_token().await.expect("token"), "fixed");
}