use super::test_helpers::*;
use crate::{jwks, parser};
use std::sync::Arc;
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_jwks_cache_creation() {
let cache = jwks::JwksCache::new(TEST_JWKS_URL);
drop(cache); }
#[tokio::test]
async fn test_jwks_cache_operations() {
let mock_server = create_mock_jwks_server().await;
let jwks_url = format!("{}/jwks", mock_server.uri());
let cache = jwks::JwksCache::new(&jwks_url);
let result = cache.get_jwks().await;
assert!(result.is_ok());
let jwks = result.unwrap();
assert_eq!(jwks.keys.len(), 1);
assert_eq!(jwks.keys[0].kid, TEST_KID);
}
#[tokio::test]
async fn test_jwks_find_key() {
let mock_server = create_mock_jwks_server().await;
let jwks_url = format!("{}/jwks", mock_server.uri());
let cache = jwks::JwksCache::new(&jwks_url);
let result = cache.find_key(TEST_KID).await;
assert!(result.is_ok());
let key = result.unwrap();
assert_eq!(key.kid, TEST_KID);
assert_eq!(key.kty, "EC");
assert_eq!(key.alg, Some("ES256".to_string()));
let result = cache.find_key("non-existent-key").await;
assert!(result.is_err());
}
#[test]
fn test_jwk_structure() {
let jwk = create_test_jwk();
assert_eq!(jwk.kid, TEST_KID);
assert_eq!(jwk.kty, "EC");
assert_eq!(jwk.alg, Some("ES256".to_string()));
assert_eq!(jwk.crv, Some("P-256".to_string()));
assert!(jwk.x.is_some());
assert!(jwk.y.is_some());
}
#[test]
fn test_jwks_response_structure() {
let response = create_test_jwks_response();
assert_eq!(response.keys.len(), 1);
assert_eq!(response.keys[0].kid, TEST_KID);
}
#[test]
fn test_jwk_serialization() {
let jwk = create_test_jwk();
let serialized = serde_json::to_string(&jwk).unwrap();
assert!(!serialized.is_empty());
let deserialized: jwks::Jwk = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized.kid, jwk.kid);
assert_eq!(deserialized.kty, jwk.kty);
}
#[tokio::test]
async fn test_jwks_server_errors() {
let mock_server = MockServer::start().await;
Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/jwks"))
.respond_with(ResponseTemplate::new(500))
.mount(&mock_server)
.await;
let jwks_url = format!("{}/jwks", mock_server.uri());
let cache = jwks::JwksCache::new(&jwks_url);
let result = cache.get_jwks().await;
assert!(result.is_err());
}
#[test]
fn test_jwk_with_missing_optional_fields() {
let mut jwk = create_test_jwk();
jwk.key_use = None;
jwk.key_ops = None;
jwk.ext = None;
assert_eq!(jwk.kid, TEST_KID);
assert_eq!(jwk.kty, "EC");
}
#[tokio::test]
async fn test_jwks_cache_basic_functionality() {
let mock_server = create_mock_jwks_server().await;
let jwks_url = format!("{}/jwks", mock_server.uri());
let cache = jwks::JwksCache::new(&jwks_url);
let result = cache.find_key(TEST_KID).await;
assert!(result.is_ok());
let jwk = result.unwrap();
assert_eq!(jwk.kid, TEST_KID);
assert_eq!(jwk.kty, "EC");
assert_eq!(jwk.alg, Some("ES256".to_string()));
let result2 = cache.find_key(TEST_KID).await;
assert!(result2.is_ok());
assert_eq!(result2.unwrap().kid, TEST_KID);
}
#[tokio::test]
async fn test_jwks_find_key_not_found() {
let mock_server = create_mock_jwks_server().await;
let jwks_url = format!("{}/jwks", mock_server.uri());
let cache = jwks::JwksCache::new(&jwks_url);
let result = cache.find_key("non-existent-key-id").await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_jwks_empty_response() {
let mock_server = MockServer::start().await;
let empty_jwks = serde_json::json!({
"keys": []
});
Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/jwks"))
.respond_with(ResponseTemplate::new(200).set_body_json(&empty_jwks))
.mount(&mock_server)
.await;
let jwks_url = format!("{}/jwks", mock_server.uri());
let cache = jwks::JwksCache::new(&jwks_url);
let result = cache.find_key(TEST_KID).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_jwks_invalid_json_response() {
let mock_server = MockServer::start().await;
Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/jwks"))
.respond_with(ResponseTemplate::new(200).set_body_string("invalid json"))
.mount(&mock_server)
.await;
let jwks_url = format!("{}/jwks", mock_server.uri());
let cache = jwks::JwksCache::new(&jwks_url);
let result = cache.get_jwks().await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_jwks_network_timeout() {
let cache = jwks::JwksCache::new("http://127.0.0.1:1/jwks");
let result = cache.get_jwks().await;
assert!(result.is_err());
}
#[test]
fn test_jwk_missing_required_fields() {
let mut jwk_empty_kid = create_test_jwk();
jwk_empty_kid.kid = "".to_string();
let result_kid = parser::JwtParser::create_decoding_key(&jwk_empty_kid);
assert!(
result_kid.is_ok(),
"Empty kid should not prevent decoding key creation"
);
let mut jwk_empty_kty = create_test_jwk();
jwk_empty_kty.kty = "".to_string();
let result_kty = parser::JwtParser::create_decoding_key(&jwk_empty_kty);
assert!(
matches!(result_kty, Err(crate::AuthError::UnsupportedKeyType(_))),
"Empty kty should cause decoding key creation to fail"
);
let mut jwk_invalid_kty = create_test_jwk();
jwk_invalid_kty.kty = "RSA".to_string();
let result_invalid = parser::JwtParser::create_decoding_key(&jwk_invalid_kty);
assert!(
matches!(result_invalid, Err(crate::AuthError::UnsupportedKeyType(_))),
"Unsupported kty should cause decoding key creation to fail"
);
}
#[test]
fn test_jwk_coordinate_edge_cases() {
let mut jwk = create_test_jwk();
jwk.x = Some("".to_string());
jwk.y = Some("".to_string());
let result = parser::JwtParser::create_decoding_key(&jwk);
assert!(
matches!(result, Err(crate::AuthError::InvalidKeyComponent(_))),
"Empty coordinates should fail at the length check, not base64 decoding"
);
let mut jwk_none = create_test_jwk();
jwk_none.x = None;
jwk_none.y = None;
let result_none = parser::JwtParser::create_decoding_key(&jwk_none);
assert!(
matches!(result_none, Err(crate::AuthError::InvalidKeyComponent(_))),
"Missing coordinates should cause decoding key creation to fail"
);
let mut jwk_partial = create_test_jwk();
jwk_partial.x = None;
let result_partial = parser::JwtParser::create_decoding_key(&jwk_partial);
assert!(
matches!(
result_partial,
Err(crate::AuthError::InvalidKeyComponent(_))
),
"Partial coordinates should cause decoding key creation to fail"
);
}
#[tokio::test]
async fn test_jwks_cache_error_recovery() {
let error_server = create_error_mock_jwks_server().await;
let jwks_url = format!("{}/jwks", error_server.uri());
let cache = jwks::JwksCache::new(&jwks_url);
let result = cache.get_jwks().await;
assert!(result.is_err());
let key_result = cache.find_key(TEST_KID).await;
assert!(key_result.is_err());
}
#[tokio::test]
async fn test_jwks_multiple_keys() {
let mock_server = MockServer::start().await;
let jwks_response = jwks::JwksResponse {
keys: vec![
create_test_jwk(),
jwks::Jwk {
kid: "key-2".to_string(),
kty: "EC".to_string(),
alg: Some("ES256".to_string()),
key_use: Some("sig".to_string()),
key_ops: Some(vec!["verify".to_string()]),
crv: Some("P-256".to_string()),
x: Some("different_x_coordinate".to_string()),
y: Some("different_y_coordinate".to_string()),
n: None,
e: None,
ext: Some(true),
},
],
};
Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/jwks"))
.respond_with(ResponseTemplate::new(200).set_body_json(&jwks_response))
.mount(&mock_server)
.await;
let jwks_url = format!("{}/jwks", mock_server.uri());
let cache = jwks::JwksCache::new(&jwks_url);
let key1 = cache.find_key(TEST_KID).await;
assert!(key1.is_ok());
assert_eq!(key1.unwrap().kid, TEST_KID);
let key2 = cache.find_key("key-2").await;
assert!(key2.is_ok());
assert_eq!(key2.unwrap().kid, "key-2");
let key3 = cache.find_key("non-existent").await;
assert!(key3.is_err());
}
#[test]
fn test_jwks_response_validation() {
let empty_response = create_empty_jwks_response();
assert!(empty_response.keys.is_empty());
let normal_response = create_test_jwks_response();
assert_eq!(normal_response.keys.len(), 1);
assert_eq!(normal_response.keys[0].kid, TEST_KID);
}
#[test]
fn test_jwk_field_validation() {
let jwk = create_test_jwk();
assert!(!jwk.kid.is_empty());
assert_eq!(jwk.kty, "EC");
assert!(jwk.alg.is_some());
assert!(jwk.key_use.is_some());
assert!(jwk.crv.is_some());
assert!(jwk.x.is_some());
assert!(jwk.y.is_some());
assert!(jwk.n.is_none());
assert!(jwk.e.is_none());
}
#[tokio::test]
async fn test_jwks_concurrent_access() {
let mock_server = create_mock_jwks_server().await;
let jwks_url = format!("{}/jwks", mock_server.uri());
let cache = Arc::new(jwks::JwksCache::new(&jwks_url));
let mut handles = vec![];
for _ in 0..10 {
let cache_clone = cache.clone();
let handle = tokio::spawn(async move { cache_clone.find_key(TEST_KID).await });
handles.push(handle);
}
for handle in handles {
let result = handle.await.unwrap();
assert!(result.is_ok());
assert_eq!(result.unwrap().kid, TEST_KID);
}
}
#[tokio::test]
async fn test_jwks_cache_consistency_and_stability() {
let mock_server = create_mock_jwks_server().await;
let jwks_url = format!("{}/jwks", mock_server.uri());
let cache = jwks::JwksCache::new(&jwks_url);
let result1 = cache.get_jwks().await;
assert!(result1.is_ok());
let jwks1 = result1.unwrap();
assert_eq!(jwks1.keys.len(), 1);
assert_eq!(jwks1.keys[0].kid, TEST_KID);
let result2 = cache.get_jwks().await;
assert!(result2.is_ok());
let jwks2 = result2.unwrap();
assert_eq!(jwks2.keys[0].kid, TEST_KID);
for i in 0..5 {
let result = cache.get_jwks().await;
assert!(
result.is_ok(),
"Cache access should be stable on iteration {i}"
);
let jwks = result.unwrap();
assert_eq!(jwks.keys.len(), 1, "Key count should remain consistent");
assert_eq!(
jwks.keys[0].kid, TEST_KID,
"Key ID should remain consistent"
);
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
}
}
#[tokio::test]
async fn test_jwks_network_retry_mechanism() {
let error_server = MockServer::start().await;
Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/jwks"))
.respond_with(ResponseTemplate::new(503)) .mount(&error_server)
.await;
let error_jwks_url = format!("{}/jwks", error_server.uri());
let error_cache = jwks::JwksCache::new(&error_jwks_url);
let error_result = error_cache.get_jwks().await;
assert!(error_result.is_err());
let key_error_result = error_cache.find_key(TEST_KID).await;
assert!(key_error_result.is_err());
let normal_server = create_mock_jwks_server().await;
let normal_jwks_url = format!("{}/jwks", normal_server.uri());
let normal_cache = jwks::JwksCache::new(&normal_jwks_url);
let normal_result = normal_cache.get_jwks().await;
assert!(normal_result.is_ok());
let jwks = normal_result.unwrap();
assert_eq!(jwks.keys.len(), 1);
assert_eq!(jwks.keys[0].kid, TEST_KID);
let key_result = normal_cache.find_key(TEST_KID).await;
assert!(key_result.is_ok());
assert_eq!(key_result.unwrap().kid, TEST_KID);
let timeout_cache = jwks::JwksCache::new("http://127.0.0.1:1/jwks");
let timeout_result = timeout_cache.get_jwks().await;
assert!(timeout_result.is_err());
let timeout_key_result = timeout_cache.find_key(TEST_KID).await;
assert!(timeout_key_result.is_err());
}
#[tokio::test]
async fn test_jwks_long_term_memory_stability() {
let mock_server = create_mock_jwks_server().await;
let jwks_url = format!("{}/jwks", mock_server.uri());
let cache = Arc::new(jwks::JwksCache::new(&jwks_url));
for round in 0..10 {
let mut handles = vec![];
for task_id in 0..20 {
let cache_clone = cache.clone();
let handle = tokio::spawn(async move {
let jwks_result = cache_clone.get_jwks().await;
let key_result = cache_clone.find_key(TEST_KID).await;
(jwks_result.is_ok(), key_result.is_ok(), task_id)
});
handles.push(handle);
}
let mut success_count = 0;
for handle in handles {
let (jwks_ok, key_ok, _task_id) = handle.await.unwrap();
if jwks_ok && key_ok {
success_count += 1;
}
}
assert!(
success_count >= 18,
"Round {round}: Success rate too low: {success_count}/20"
);
tokio::time::sleep(tokio::time::Duration::from_millis(5)).await;
}
let final_result = cache.get_jwks().await;
assert!(final_result.is_ok());
let final_jwks = final_result.unwrap();
assert_eq!(final_jwks.keys.len(), 1);
assert_eq!(final_jwks.keys[0].kid, TEST_KID);
}
#[tokio::test]
async fn test_jwks_cache_consistency_under_pressure() {
let mock_server = create_mock_jwks_server().await;
let jwks_url = format!("{}/jwks", mock_server.uri());
let cache = Arc::new(jwks::JwksCache::new(&jwks_url));
let mut all_handles = vec![];
for batch in 0..5 {
for task in 0..50 {
let cache_clone = cache.clone();
let handle = tokio::spawn(async move {
let start_time = std::time::Instant::now();
let jwks_result = cache_clone.get_jwks().await;
let key_result = cache_clone.find_key(TEST_KID).await;
let duration = start_time.elapsed();
(
jwks_result.is_ok(),
key_result.is_ok(),
duration,
batch,
task,
)
});
all_handles.push(handle);
}
tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
}
let mut total_success = 0;
let mut max_duration = std::time::Duration::from_millis(0);
let mut min_duration = std::time::Duration::from_secs(1);
for handle in all_handles {
let (jwks_ok, key_ok, duration, _batch, _task) = handle.await.unwrap();
if jwks_ok && key_ok {
total_success += 1;
}
max_duration = max_duration.max(duration);
min_duration = min_duration.min(duration);
}
let total_tasks = 5 * 50; let success_rate = (total_success as f64) / (total_tasks as f64) * 100.0;
assert!(
success_rate >= 95.0,
"Success rate too low: {success_rate:.2}%"
);
assert!(
max_duration.as_millis() < 1000,
"Max duration too high: {max_duration:?}"
);
println!(
"Pressure test completed: {total_success}/{total_tasks} tasks succeeded ({success_rate:.2}%)"
);
println!("Duration range: {min_duration:?} - {max_duration:?}");
}
}