ohttp-gateway 1.0.1

A OHTTP Gateway server, meant to run between a OHTTP Relay and a target web service.
Documentation
use std::time::Duration;

use ohttp_gateway::{key_manager::KeyManager, key_manager::KeyManagerConfig};

mod common;
use common::*;

#[tokio::test]
async fn test_end_to_end_encryption_decryption() {
    let config = KeyManagerConfig::default();
    let manager = KeyManager::new(config).await.unwrap();

    // Get the server for decryption
    let _ = manager.get_current_server().await.unwrap();

    // Get the key config for client encryption
    let encoded_config = manager.get_encoded_config().await.unwrap();

    // Parse the config (this would normally be done by a real OHTTP client)
    // For now, create a client with the current key config
    let stats = manager.get_stats().await;
    let _ = manager.get_server_by_id(stats.active_key_id).await.unwrap();

    // Test message
    let test_message = create_test_binary_http_message();

    // This test verifies that encryption/decryption round trip works
    // In a real implementation, you'd use the ohttp client/server APIs

    // For now, just verify we can get the components we need
    assert!(!encoded_config.is_empty());
    assert!(!test_message.is_empty());
}

#[tokio::test]
async fn test_key_rotation_during_requests() {
    let config = KeyManagerConfig {
        rotation_interval: Duration::from_millis(100),
        key_retention_period: Duration::from_millis(200),
        auto_rotation_enabled: false, // Manual control
        ..Default::default()
    };

    let manager = KeyManager::new(config).await.unwrap();
    let initial_stats = manager.get_stats().await;

    // Get server for old key
    let old_server = manager.get_server_by_id(initial_stats.active_key_id).await;
    assert!(old_server.is_some());

    // Rotate keys
    manager.rotate_keys().await.unwrap();
    let new_stats = manager.get_stats().await;

    // Old key should still be available for decryption
    let old_server_after_rotation = manager.get_server_by_id(initial_stats.active_key_id).await;
    assert!(old_server_after_rotation.is_some());

    // New key should also be available
    let new_server = manager.get_server_by_id(new_stats.active_key_id).await;
    assert!(new_server.is_some());

    // Active key should have changed
    assert_ne!(initial_stats.active_key_id, new_stats.active_key_id);
    assert_eq!(new_stats.total_keys, 2);
}

#[tokio::test]
async fn test_invalid_key_id_handling() {
    let config = KeyManagerConfig::default();
    let manager = KeyManager::new(config).await.unwrap();

    let stats = manager.get_stats().await;
    let invalid_key_id = stats.active_key_id.wrapping_add(100);

    // Should return None for invalid key ID
    let server = manager.get_server_by_id(invalid_key_id).await;
    assert!(server.is_none());
}

#[tokio::test]
async fn test_concurrent_key_operations() {
    let config = KeyManagerConfig::default();
    let manager = std::sync::Arc::new(KeyManager::new(config).await.unwrap());

    let mut handles = vec![];

    // Spawn multiple tasks that access keys concurrently
    for i in 0..10 {
        let manager_clone = manager.clone();
        let handle = tokio::spawn(async move {
            if i % 2 == 0 {
                // Half the tasks get the current server
                let _server = manager_clone.get_current_server().await.unwrap();
            } else {
                // Half get stats
                let _stats = manager_clone.get_stats().await;
            }
        });
        handles.push(handle);
    }

    // Wait for all tasks to complete
    for handle in handles {
        handle.await.unwrap();
    }

    // Manager should still be functional
    let final_stats = manager.get_stats().await;
    assert_eq!(final_stats.total_keys, 1);
}

#[tokio::test]
async fn test_automatic_rotation_scheduler() {
    let config = KeyManagerConfig {
        rotation_interval: Duration::from_millis(100),
        key_retention_period: Duration::from_millis(200),
        auto_rotation_enabled: true,
        ..Default::default()
    };

    let manager = std::sync::Arc::new(KeyManager::new(config).await.unwrap());
    let initial_stats = manager.get_stats().await;

    // Start the rotation scheduler
    let manager_clone = manager.clone();
    manager_clone.start_rotation_scheduler().await;

    // Wait for automatic rotation to occur
    tokio::time::sleep(Duration::from_millis(300)).await;

    let final_stats = manager.get_stats().await;

    // Key should have rotated automatically
    // Note: This test might be flaky depending on timing
    assert!(final_stats.active_key_id != initial_stats.active_key_id || final_stats.total_keys > 1);
}

#[tokio::test]
async fn test_metrics_tracking() {
    let factory = MockMetricsFactory::new();

    // Simulate various operations and metric collection
    let metrics = factory.create("test_event".to_string()).await;

    metrics.fire("operation_success").await;
    metrics.response_status("test", 200).await;

    assert!(metrics.contains_result("operation_success").await);
    assert!(metrics.contains_result("test_response_status_200").await);

    // Test the helper function
    assert_metrics_contains_result(&factory, "test_event", "operation_success")
        .await
        .unwrap();
}

#[tokio::test]
async fn test_config_serialization_format() {
    let config = KeyManagerConfig::default();
    let manager = KeyManager::new(config).await.unwrap();

    let encoded_config = manager.get_encoded_config().await.unwrap();

    // Verify we have config data with 2-byte length prefix per RFC 9458
    assert!(!encoded_config.is_empty());
    assert!(encoded_config.len() > 12); // At least 2 bytes for length + some config data

    // Verify the length prefix is correct
    let length_prefix = u16::from_be_bytes([encoded_config[0], encoded_config[1]]);
    assert_eq!(length_prefix as usize, encoded_config.len() - 2);

    // Verify it contains reasonable OHTTP key configuration data
    assert!(encoded_config.len() < 1000); // Reasonable upper bound

    // The config should be the length-prefixed encoded configuration
    let config_data = &encoded_config[2..]; // Skip the length prefix
    assert!(!config_data.is_empty());
}

#[tokio::test]
async fn test_error_conditions() {
    // Test various error conditions

    // Invalid seed length
    let config = KeyManagerConfig::default();
    let short_seed = vec![0u8; 16];
    let result = KeyManager::new_with_seed(config.clone(), short_seed).await;
    assert!(result.is_err());

    // Test with empty cipher suites (if your implementation supports this validation)
    let invalid_config = KeyManagerConfig {
        cipher_suites: vec![], // Empty cipher suites
        ..Default::default()
    };

    // Should return an error for empty cipher suites
    let result = KeyManager::new(invalid_config).await;
    assert!(
        result.is_err(),
        "KeyManager should reject empty cipher suites"
    );
}