use std::time::Instant;
use crate::oauth2::jwk::cache::JwtKeyCache;
pub(super) async fn check_refresh_cooldown(jwt_key_cache: &JwtKeyCache) -> Option<u64> {
let config = &jwt_key_cache.config;
let last_refresh_failure = &jwt_key_cache.last_refresh_failure;
if let Some(last_failure) = *last_refresh_failure.read().await {
let elapsed_secs = last_failure.elapsed().as_secs();
let is_cooldown = elapsed_secs < config.refresh_cooldown.as_secs();
if is_cooldown {
debug!(
"Respecting background refresh cooldown: {}s elapsed of {}s required",
elapsed_secs,
config.refresh_cooldown.as_secs()
);
let remaining_cooldown = config.refresh_cooldown.as_secs() - elapsed_secs;
return Some(remaining_cooldown);
} else {
trace!(
"Background cooldown period elapsed: {}s passed (required {}s)",
elapsed_secs,
config.refresh_cooldown.as_secs()
);
return None;
}
}
trace!("No previous JWT key refresh failures recorded, no backoff needed");
None
}
pub(super) fn is_cache_approaching_expiry(jwt_key_cache: &JwtKeyCache, timestamp: Instant) -> bool {
let config = &jwt_key_cache.config;
let elapsed_millis = timestamp.elapsed().as_millis();
let threshold_percentage = config.background_refresh_threshold as f64 / 100.0;
let threshold_millis = (config.cache_ttl.as_millis() as f64 * threshold_percentage) as u128;
let is_approaching_expiry = elapsed_millis > threshold_millis;
let elapsed_seconds = timestamp.elapsed().as_secs();
let threshold_seconds = (config.cache_ttl.as_secs() as f64 * threshold_percentage) as u64;
if is_approaching_expiry {
debug!(
"JWT keys cache approaching expiry: elapsed={}s, threshold={}s ({}% of ttl={}s)",
elapsed_seconds,
threshold_seconds,
config.background_refresh_threshold,
config.cache_ttl.as_secs()
);
true
} else {
trace!( "JWT keys cache not yet approaching expiry: elapsed={}s, threshold={}s ({}% of ttl={}s)",
elapsed_seconds,
threshold_seconds,
config.background_refresh_threshold,
config.cache_ttl.as_secs());
false
}
}
pub(super) fn is_cache_expired(jwt_key_cache: &JwtKeyCache, timestamp: Instant) -> bool {
let cache_ttl = jwt_key_cache.config.cache_ttl;
let is_expired = timestamp.elapsed().as_millis() >= cache_ttl.as_millis();
if is_expired {
debug!(
"JWT keys cache expired: elapsed={}s, ttl={}s",
timestamp.elapsed().as_secs(),
cache_ttl.as_secs()
);
true
} else {
trace!(
"JWT keys cache valid: elapsed={}s, ttl={}s",
timestamp.elapsed().as_secs(),
cache_ttl.as_secs()
);
false
}
}
#[cfg(test)]
mod is_refresh_cooldown_tests {
use crate::Client;
use super::check_refresh_cooldown;
#[tokio::test]
async fn test_check_refresh_cooldown_within_cooldown() {
let esi_client = Client::builder()
.user_agent("MyApp/1.0 (contact@email.com")
.build()
.expect("Failed to build Client");
let jwt_key_cache = &esi_client.inner.jwt_key_cache;
{
let mut failure_time = jwt_key_cache.last_refresh_failure.write().await;
*failure_time = Some(std::time::Instant::now() - std::time::Duration::from_secs(30));
}
let cooldown = check_refresh_cooldown(&jwt_key_cache).await;
assert!(cooldown.is_some());
let remaining_cooldown = cooldown.unwrap();
assert_eq!(remaining_cooldown, 30);
}
#[tokio::test]
async fn test_check_refresh_cooldown_recent_failure() {
let esi_client = Client::builder()
.user_agent("MyApp/1.0 (contact@email.com")
.build()
.expect("Failed to build Client");
let jwt_key_cache = &esi_client.inner.jwt_key_cache;
{
let mut failure_time = jwt_key_cache.last_refresh_failure.write().await;
*failure_time = Some(std::time::Instant::now() - std::time::Duration::from_secs(61));
}
let cooldown = check_refresh_cooldown(&jwt_key_cache).await;
assert!(cooldown.is_none());
}
#[tokio::test]
async fn test_check_refresh_cooldown_no_failure() {
let esi_client = Client::builder()
.user_agent("MyApp/1.0 (contact@email.com")
.build()
.expect("Failed to build Client");
let jwt_key_cache = &esi_client.inner.jwt_key_cache;
let cooldown = check_refresh_cooldown(&jwt_key_cache).await;
assert!(cooldown.is_none());
}
}
#[cfg(test)]
mod is_cache_approaching_expiry_tests {
use crate::Client;
use super::is_cache_approaching_expiry;
#[test]
fn test_is_cache_approaching_expiry_true() {
let esi_client = Client::builder()
.user_agent("MyApp/1.0 (contact@email.com")
.build()
.expect("Failed to build Client");
let timestamp = std::time::Instant::now() - std::time::Duration::from_secs(2881);
let result = is_cache_approaching_expiry(&esi_client.inner.jwt_key_cache, timestamp);
assert_eq!(result, true)
}
#[test]
fn test_is_cache_approaching_expiry_false() {
let esi_client = Client::builder()
.user_agent("MyApp/1.0 (contact@email.com")
.build()
.expect("Failed to build Client");
let timestamp = std::time::Instant::now();
let result = is_cache_approaching_expiry(&esi_client.inner.jwt_key_cache, timestamp);
assert_eq!(result, false)
}
}
#[cfg(test)]
mod is_cache_expired_tests {
use crate::Client;
use super::is_cache_expired;
#[test]
fn test_is_cache_expired_true() {
let esi_client = Client::builder()
.user_agent("MyApp/1.0 (contact@example.com)")
.build()
.expect("Failed to build Client");
let timestamp = std::time::Instant::now() - std::time::Duration::from_secs(3601);
let result = is_cache_expired(&esi_client.inner.jwt_key_cache, timestamp);
assert_eq!(result, true)
}
#[test]
fn test_is_cache_expired_false() {
let esi_client = Client::builder()
.user_agent("MyApp/1.0 (contact@example.com)")
.build()
.expect("Failed to build Client");
let timestamp = std::time::Instant::now();
let result = is_cache_expired(&esi_client.inner.jwt_key_cache, timestamp);
assert_eq!(result, false)
}
}