use port_sdk::auth::{AuthStrategy, ClientCredentialsOptions};
use serde_json::json;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio::runtime::Runtime;
use url::Url;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
fn runtime() -> Runtime {
Runtime::new().expect("tokio runtime")
}
#[test]
fn static_token_provider_clones_token() {
let provider =
AuthStrategy::StaticToken("secret".to_string()).into_provider().expect("provider");
let token1 = runtime().block_on(provider.bearer_token()).unwrap();
let token2 = runtime().block_on(provider.bearer_token()).unwrap();
assert_eq!(token1, token2);
}
#[test]
fn client_credentials_provider_caches_tokens() {
let rt = runtime();
let server = rt.block_on(MockServer::start());
let attempts = Arc::new(AtomicUsize::new(0));
let attempts_for_mock = Arc::clone(&attempts);
rt.block_on(async {
Mock::given(method("POST"))
.and(path("/oauth/token"))
.respond_with(move |_: &wiremock::Request| {
attempts_for_mock.fetch_add(1, Ordering::SeqCst);
ResponseTemplate::new(200).set_body_json(json!({
"access_token": "token-1",
"expires_in": 3600
}))
})
.mount(&server)
.await;
});
let options = ClientCredentialsOptions {
client_id: "id".into(),
client_secret: "secret".into(),
token_url: Url::parse(&format!("{}/oauth/token", server.uri())).unwrap(),
minimum_ttl: Duration::from_secs(30),
};
let provider = AuthStrategy::ClientCredentials(options).into_provider().expect("provider");
let token1 = rt.block_on(provider.bearer_token()).unwrap();
let token2 = rt.block_on(provider.bearer_token()).unwrap();
assert_eq!(token1, "token-1");
assert_eq!(token2, "token-1");
assert_eq!(attempts.load(Ordering::SeqCst), 1);
}
#[test]
fn client_credentials_provider_refreshes_expired_tokens() {
let rt = runtime();
let server = rt.block_on(MockServer::start());
let attempts = Arc::new(AtomicUsize::new(0));
let attempts_for_mock = Arc::clone(&attempts);
rt.block_on(async {
Mock::given(method("POST"))
.and(path("/oauth/token"))
.respond_with(move |_: &wiremock::Request| {
let attempt = attempts_for_mock.fetch_add(1, Ordering::SeqCst);
if attempt == 0 {
ResponseTemplate::new(200).set_body_json(json!({
"access_token": "token-1",
"expires_in": 5
}))
} else {
ResponseTemplate::new(200).set_body_json(json!({
"access_token": "token-2",
"expires_in": 3600
}))
}
})
.mount(&server)
.await;
});
let options = ClientCredentialsOptions {
client_id: "id".into(),
client_secret: "secret".into(),
token_url: Url::parse(&format!("{}/oauth/token", server.uri())).unwrap(),
minimum_ttl: Duration::from_secs(10),
};
let provider = AuthStrategy::ClientCredentials(options).into_provider().expect("provider");
let token1 = rt.block_on(provider.bearer_token()).unwrap();
assert_eq!(token1, "token-1");
let token2 = rt.block_on(provider.bearer_token()).unwrap();
assert_eq!(token2, "token-2");
assert_eq!(attempts.load(Ordering::SeqCst), 2);
}