use std::time::Duration;
use a2a_protocol_client::error::ClientError;
use a2a_protocol_client::token_provider::discover_token_endpoint;
use a2a_protocol_client::{OAuth2ClientCredentials, TokenProvider};
use a2a_protocol_types::error::A2aError;
use a2a_protocol_types::failure::{FailureClass, error_class};
async fn closed_port() -> u16 {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
.await
.expect("bind");
listener.local_addr().expect("addr").port()
}
fn assert_transient(err: ClientError) {
assert!(
matches!(err, ClientError::HttpClient(_)),
"a refused connection must be HttpClient, as the transports report it, got {err:?}"
);
assert!(
err.is_retryable(),
"a refused connection is transient: {err:?}"
);
let task_error = A2aError::from(err);
assert_eq!(
error_class(&task_error),
FailureClass::Transient,
"an executor's `?` must class it Transient: {task_error:?}"
);
}
#[tokio::test]
async fn a_refused_token_endpoint_is_retryable() {
let port = closed_port().await;
let provider =
OAuth2ClientCredentials::new(format!("http://127.0.0.1:{port}/token"), "cid", "csec");
let err = tokio::time::timeout(Duration::from_secs(10), provider.access_token())
.await
.expect("a refused connection answers at once")
.expect_err("nothing listens");
assert_transient(err);
}
#[tokio::test]
async fn a_refused_oidc_discovery_is_retryable() {
let port = closed_port().await;
let err = tokio::time::timeout(
Duration::from_secs(10),
discover_token_endpoint(&format!("http://127.0.0.1:{port}")),
)
.await
.expect("a refused connection answers at once")
.expect_err("nothing listens");
assert_transient(err);
}