ohttp-gateway/tests/config_handler_tests.rs

211 lines
6.8 KiB
Rust
Raw Permalink Normal View History

2025-11-23 20:27:24 +00:00
use axum::http::StatusCode;
use rand::Rng;
use ohttp_gateway::{key_manager::KeyManager, key_manager::KeyManagerConfig};
mod common;
use common::{LEGACY_KEY_ID, validate_cache_control_header};
// Mock HTTP response structure for testing
struct MockResponse {
status: StatusCode,
headers: std::collections::HashMap<String, String>,
body: Vec<u8>,
}
impl MockResponse {
fn new(status: StatusCode, body: Vec<u8>) -> Self {
Self {
status,
headers: std::collections::HashMap::new(),
body,
}
}
fn add_header(&mut self, name: &str, value: &str) {
self.headers.insert(name.to_string(), value.to_string());
}
fn get_header(&self, name: &str) -> Option<&String> {
self.headers.get(name)
}
}
// Mock config handler - adapt this to match your actual HTTP handler structure
async fn mock_config_handler(
manager: &KeyManager,
) -> Result<MockResponse, Box<dyn std::error::Error>> {
// Generate random cache age between 12-36 hours (mirroring Go implementation)
use rand::Rng;
let mut rng = rand::thread_rng();
let twelve_hours = 12 * 3600;
let twenty_four_hours = 24 * 3600;
let max_age = twelve_hours + rng.gen_range(0..twenty_four_hours);
let encoded_config = manager.get_encoded_config().await?;
let mut response = MockResponse::new(StatusCode::OK, encoded_config);
response.add_header("Cache-Control", &format!("max-age={}, private", max_age));
response.add_header("Content-Type", "application/ohttp-keys");
Ok(response)
}
async fn mock_legacy_config_handler(
manager: &KeyManager,
_key_id: u8,
) -> Result<MockResponse, Box<dyn std::error::Error>> {
// This would need to be implemented based on your legacy config support
// For now, return a simple implementation
let encoded_config = manager.get_encoded_config().await?;
let mut rng = rand::thread_rng();
let twelve_hours = 12 * 3600;
let twenty_four_hours = 24 * 3600;
let max_age = twelve_hours + rng.gen_range(0..twenty_four_hours);
let mut response = MockResponse::new(StatusCode::OK, encoded_config);
response.add_header("Cache-Control", &format!("max-age={}, private", max_age));
Ok(response)
}
#[tokio::test]
async fn test_config_handler() {
let config = KeyManagerConfig::default();
let manager = KeyManager::new(config).await.unwrap();
let response = mock_config_handler(&manager).await.unwrap();
// Check status
assert_eq!(response.status, StatusCode::OK);
// Check headers
assert_eq!(
response.get_header("Content-Type").unwrap(),
"application/ohttp-keys"
);
let cache_control = response.get_header("Cache-Control").unwrap();
validate_cache_control_header(cache_control).unwrap();
2025-11-23 20:27:24 +00:00
// Check body is not empty and has expected structure with 2-byte length prefix
assert!(!response.body.is_empty());
2025-11-23 20:27:24 +00:00
assert!(response.body.len() >= 4); // At least 2-byte length prefix + some config data
// Verify the length prefix is correct per RFC 9458
let length_prefix = u16::from_be_bytes([response.body[0], response.body[1]]);
assert_eq!(length_prefix as usize, response.body.len() - 2);
}
#[tokio::test]
async fn test_legacy_config_handler() {
let config = KeyManagerConfig::default();
let manager = KeyManager::new(config).await.unwrap();
let response = mock_legacy_config_handler(&manager, LEGACY_KEY_ID)
.await
.unwrap();
// Check status
assert_eq!(response.status, StatusCode::OK);
// Check cache control header exists and is valid
let cache_control = response.get_header("Cache-Control").unwrap();
validate_cache_control_header(cache_control).unwrap();
// Check body
assert!(!response.body.is_empty());
}
#[tokio::test]
async fn test_config_handler_multiple_keys() {
let config = KeyManagerConfig::default();
let manager = KeyManager::new(config).await.unwrap();
// Add another key through rotation
manager.rotate_keys().await.unwrap();
let response = mock_config_handler(&manager).await.unwrap();
assert_eq!(response.status, StatusCode::OK);
// Body should be larger with multiple keys
assert!(response.body.len() >= 8); // At least 2 key configs
}
#[tokio::test]
async fn test_config_consistency() {
let config = KeyManagerConfig::default();
let manager = KeyManager::new(config).await.unwrap();
// Get config multiple times
let response1 = mock_config_handler(&manager).await.unwrap();
let response2 = mock_config_handler(&manager).await.unwrap();
// Both responses should be successful
assert_eq!(response1.status, StatusCode::OK);
assert_eq!(response2.status, StatusCode::OK);
// Key content should be the same (though cache headers may differ)
// Note: In a real implementation, you might want to test deterministic key generation
assert_eq!(response1.body.len(), response2.body.len());
}
#[tokio::test]
async fn test_config_with_deterministic_seed() {
let config = KeyManagerConfig::default();
let seed = vec![0x42u8; 32]; // Fixed seed for deterministic keys
let manager1 = KeyManager::new_with_seed(config.clone(), seed.clone())
.await
.unwrap();
let manager2 = KeyManager::new_with_seed(config, seed).await.unwrap();
let response1 = mock_config_handler(&manager1).await.unwrap();
let response2 = mock_config_handler(&manager2).await.unwrap();
// Both should succeed
assert_eq!(response1.status, StatusCode::OK);
assert_eq!(response2.status, StatusCode::OK);
// With the same seed, the key configurations should be identical
// This now works because we're using KeyConfig::derive() for deterministic generation
assert_eq!(response1.body, response2.body);
// Also verify the bodies are not empty and have valid structure
assert!(!response1.body.is_empty());
assert!(response1.body.len() >= 4);
}
#[tokio::test]
async fn test_cache_control_randomization() {
let config = KeyManagerConfig::default();
let manager = KeyManager::new(config).await.unwrap();
let mut max_ages = std::collections::HashSet::new();
// Generate multiple responses and collect max-age values
for _ in 0..10 {
let response = mock_config_handler(&manager).await.unwrap();
let cache_control = response.get_header("Cache-Control").unwrap();
// Extract max-age value
let max_age_str = cache_control
.strip_prefix("max-age=")
.and_then(|s| s.strip_suffix(", private"))
.unwrap();
let max_age: u32 = max_age_str.parse().unwrap();
max_ages.insert(max_age);
}
// Should have some variation in max-age values (randomization)
// Note: This test might occasionally fail due to randomness, but should usually pass
assert!(
max_ages.len() > 1,
"Cache-Control max-age should be randomized"
);
}