#![allow( clippy::manual_range_contains, clippy::int_plus_one, dead_code, unused_variables )] //! Comprehensive Rate Limiting Tests - Wave 100 Agent 3 //! //! Tests for achieving 95%+ coverage of rate_limiter.rs: //! 1. Redis backend integration (Lua scripts, connection handling) //! 2. Cache management (LRU eviction, stats, invalidation) //! 3. Endpoint configuration (dynamic updates, defaults) //! 4. Error paths (Redis failures, timeouts) //! 5. Integration scenarios (multi-endpoint, cross-user) //! //! Target: 30+ test cases covering all untested code paths use anyhow::Result; use std::time::{Duration, Instant}; use uuid::Uuid; use api::routing::{RateLimitConfig, RateLimiter}; const REDIS_URL: &str = "redis://localhost:6379"; // ============================================================================ // SECTION 1: Redis Backend Integration Tests (10 tests) // ============================================================================ #[tokio::test] async fn test_redis_backend_basic_check() -> Result<()> { println!("\n=== Test: Redis Backend Basic Check ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; let user_id = Uuid::new_v4(); // First request should succeed let result1 = rate_limiter .check_limit(&user_id, "trading.submit_order") .await?; println!(" First request: {}", result1); assert!(result1, "First request should be allowed"); // Subsequent requests should succeed up to capacity let mut allowed = 0; for i in 0..150 { if rate_limiter .check_limit(&user_id, "trading.submit_order") .await? { allowed += 1; } else { println!(" First denial at request #{}", i + 2); break; } } println!(" Total allowed: {}", allowed + 1); // Should be limited by capacity (100 for trading.submit_order) assert!( allowed + 1 <= 110, "Should not exceed capacity by more than 10%" ); Ok(()) } #[tokio::test] async fn test_redis_lua_script_execution() -> Result<()> { println!("\n=== Test: Redis Lua Script Atomic Execution ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; let user_id = Uuid::new_v4(); // Concurrent requests should be handled atomically by Lua script let mut handles = Vec::new(); println!(" Spawning 100 concurrent requests..."); for _ in 0..100 { let limiter = rate_limiter.clone(); let uid = user_id; let handle = tokio::spawn(async move { limiter.check_limit(&uid, "trading.submit_order").await }); handles.push(handle); } // Collect results let mut allowed = 0; let mut denied = 0; for handle in handles { match handle.await? { Ok(true) => allowed += 1, Ok(false) => denied += 1, Err(_) => {}, } } println!(" Allowed: {}, Denied: {}", allowed, denied); // Lua script should ensure exact limit enforcement assert_eq!(allowed + denied, 100, "All requests should complete"); assert!(allowed <= 100, "Should not exceed capacity"); Ok(()) } #[tokio::test] async fn test_redis_token_refill() -> Result<()> { println!("\n=== Test: Redis Token Bucket Refill ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; let user_id = Uuid::new_v4(); // Exhaust tokens let mut initial_allowed = 0; for _ in 0..150 { if rate_limiter.check_limit(&user_id, "config.update").await? { initial_allowed += 1; } } println!(" Initial allowed: {}", initial_allowed); assert!( initial_allowed <= 12, "Should be limited to capacity (10 + tolerance)" ); // Wait for refill (config.update has 10 req/s refill rate) println!(" Waiting 1s for token refill..."); tokio::time::sleep(Duration::from_secs(1)).await; // Should have refilled tokens let mut refilled = 0; for _ in 0..20 { if rate_limiter.check_limit(&user_id, "config.update").await? { refilled += 1; } } println!(" After refill: {}", refilled); assert!( refilled >= 8 && refilled <= 12, "Should refill ~10 tokens, got {}", refilled ); Ok(()) } #[tokio::test] async fn test_redis_persistence() -> Result<()> { println!("\n=== Test: Redis State Persistence ==="); let rate_limiter1 = RateLimiter::new(REDIS_URL).await?; let user_id = Uuid::new_v4(); // Make some requests with first limiter instance let mut count1 = 0; for _ in 0..5 { if rate_limiter1 .check_limit(&user_id, "backtesting.run") .await? { count1 += 1; } } println!(" Instance 1 allowed: {}", count1); // Create new limiter instance (should share Redis state) let rate_limiter2 = RateLimiter::new(REDIS_URL).await?; // Remaining requests should respect previous consumption let mut count2 = 0; for _ in 0..5 { if rate_limiter2 .check_limit(&user_id, "backtesting.run") .await? { count2 += 1; } } println!(" Instance 2 allowed: {}", count2); // Total should not exceed capacity (5 for backtesting.run) assert!(count1 + count2 <= 6, "Total should respect shared state"); Ok(()) } #[tokio::test] async fn test_redis_ttl_expiration() -> Result<()> { println!("\n=== Test: Redis Key TTL Expiration ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; let user_id = Uuid::new_v4(); // Make a request to create Redis key let _ = rate_limiter .check_limit(&user_id, "trading.submit_order") .await?; println!(" Key created with 300s TTL"); println!(" (TTL validation requires manual Redis inspection)"); // Note: Full TTL test would require waiting 300s or manual Redis commands // This test validates the key is created; TTL is set in Lua script Ok(()) } #[tokio::test] async fn test_redis_connection_pool() -> Result<()> { println!("\n=== Test: Redis Connection Pool Behavior ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; // Make 1000 requests to stress connection pool let mut handles = Vec::new(); println!(" Spawning 1000 concurrent Redis requests..."); for i in 0..1000 { let limiter = rate_limiter.clone(); let user_id = Uuid::new_v4(); let handle = tokio::spawn( async move { limiter.check_limit(&user_id, "trading.submit_order").await }, ); handles.push(handle); } // All should succeed without connection pool exhaustion let mut success = 0; let mut errors = 0; for handle in handles { match handle.await? { Ok(_) => success += 1, Err(_) => errors += 1, } } println!(" Success: {}, Errors: {}", success, errors); assert!(errors < 10, "Should have minimal connection errors"); Ok(()) } #[tokio::test] async fn test_redis_error_handling() -> Result<()> { println!("\n=== Test: Redis Connection Error Handling ==="); // Attempt connection to invalid Redis URL let result = RateLimiter::new("redis://invalid-host:9999").await; println!(" Invalid Redis URL result: {:?}", result.is_err()); assert!(result.is_err(), "Should fail for invalid Redis URL"); if let Err(e) = result { println!(" Error message: {}", e); assert!( e.to_string().contains("Failed to"), "Should have descriptive error" ); } Ok(()) } #[tokio::test] async fn test_redis_multiple_endpoints() -> Result<()> { println!("\n=== Test: Redis Multiple Endpoint Tracking ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; let user_id = Uuid::new_v4(); // Same user, different endpoints - should be tracked separately let mut trading_allowed = 0; let mut config_allowed = 0; let mut backtest_allowed = 0; for _ in 0..20 { if rate_limiter .check_limit(&user_id, "trading.submit_order") .await? { trading_allowed += 1; } if rate_limiter.check_limit(&user_id, "config.update").await? { config_allowed += 1; } if rate_limiter .check_limit(&user_id, "backtesting.run") .await? { backtest_allowed += 1; } } println!(" Trading: {}", trading_allowed); println!(" Config: {}", config_allowed); println!(" Backtest: {}", backtest_allowed); // Each endpoint should have independent limits assert!(trading_allowed >= 15, "Trading should allow most requests"); assert!(config_allowed >= 8, "Config should allow some requests"); assert!(backtest_allowed <= 6, "Backtest should be most restrictive"); Ok(()) } #[tokio::test] async fn test_redis_cross_user_isolation() -> Result<()> { println!("\n=== Test: Redis Cross-User Isolation ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; let user1 = Uuid::new_v4(); let user2 = Uuid::new_v4(); // User 1 exhausts their limit let mut user1_allowed = 0; for _ in 0..20 { if rate_limiter.check_limit(&user1, "config.update").await? { user1_allowed += 1; } } // User 2 should still have full quota let mut user2_allowed = 0; for _ in 0..20 { if rate_limiter.check_limit(&user2, "config.update").await? { user2_allowed += 1; } } println!(" User 1: {}", user1_allowed); println!(" User 2: {}", user2_allowed); assert!(user1_allowed <= 12, "User 1 should be limited"); assert!( user2_allowed <= 12, "User 2 should be independently limited" ); assert_eq!( user1_allowed, user2_allowed, "Users should have equal quotas" ); Ok(()) } #[tokio::test] async fn test_redis_system_time_error() -> Result<()> { println!("\n=== Test: Redis System Time Error Handling ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; let user_id = Uuid::new_v4(); // Normal requests should succeed (validates system time is working) let result = rate_limiter .check_limit(&user_id, "trading.submit_order") .await?; println!(" System time operational: {}", result); assert!(result, "Should work with valid system time"); // Note: Testing actual system time errors would require mocking, // which is outside scope. This validates the happy path. Ok(()) } // ============================================================================ // SECTION 2: Cache Management Tests (8 tests) // ============================================================================ #[tokio::test] async fn test_cache_basic_operation() -> Result<()> { println!("\n=== Test: Cache Basic Operation ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; let user_id = Uuid::new_v4(); // First request (cache miss, Redis hit) let start1 = Instant::now(); let _ = rate_limiter .check_limit(&user_id, "trading.submit_order") .await?; let latency1 = start1.elapsed(); // Second request (cache hit) let start2 = Instant::now(); let _ = rate_limiter .check_limit(&user_id, "trading.submit_order") .await?; let latency2 = start2.elapsed(); println!(" First request (Redis): {:?}", latency1); println!(" Second request (cache): {:?}", latency2); println!( " Cache speedup: {:.1}x", latency1.as_nanos() as f64 / latency2.as_nanos() as f64 ); // Cache hit should be significantly faster assert!(latency2 < latency1, "Cached request should be faster"); Ok(()) } #[tokio::test] async fn test_cache_ttl_expiration() -> Result<()> { println!("\n=== Test: Cache TTL Expiration ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; let user_id = Uuid::new_v4(); // Request to populate cache let _ = rate_limiter .check_limit(&user_id, "trading.submit_order") .await?; println!(" Cache populated, TTL: 1s"); // Wait for cache TTL to expire (1 second) println!(" Waiting 1.1s for cache expiration..."); tokio::time::sleep(Duration::from_millis(1100)).await; // Next request should be slower (cache miss) let start = Instant::now(); let _ = rate_limiter .check_limit(&user_id, "trading.submit_order") .await?; let latency = start.elapsed(); println!(" Post-expiration latency: {:?}", latency); // Should go back to Redis (higher latency) assert!( latency > Duration::from_micros(1), "Should bypass expired cache" ); Ok(()) } #[tokio::test] async fn test_cache_lru_eviction() -> Result<()> { println!("\n=== Test: Cache LRU Eviction ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; // Fill cache beyond max size (10,000 entries) println!(" Filling cache with 10,500 unique users..."); for i in 0..10_500 { let user_id = Uuid::new_v4(); let _ = rate_limiter .check_limit(&user_id, "trading.submit_order") .await?; if i % 1000 == 0 { let stats = rate_limiter.get_cache_stats().await; println!(" Progress: {} users, cache size: {}", i, stats.size); } } // Cache should have evicted oldest 10% (1,000 entries) let final_stats = rate_limiter.get_cache_stats().await; println!(" Final cache size: {}", final_stats.size); assert!( final_stats.size <= 10_000, "Cache should not exceed max size" ); assert!( final_stats.size >= 9_000, "Cache should retain most recent entries" ); Ok(()) } #[tokio::test] async fn test_cache_stats() -> Result<()> { println!("\n=== Test: Cache Statistics ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; // Initial stats let stats1 = rate_limiter.get_cache_stats().await; println!(" Initial stats:"); println!(" ├─ Size: {}", stats1.size); println!(" ├─ Max: {}", stats1.max_size); println!(" └─ TTL: {}s", stats1.ttl_seconds); assert_eq!(stats1.max_size, 10_000, "Max size should be 10,000"); assert_eq!(stats1.ttl_seconds, 1, "TTL should be 1 second"); // Add some entries for _ in 0..100 { let user_id = Uuid::new_v4(); let _ = rate_limiter .check_limit(&user_id, "trading.submit_order") .await?; } let stats2 = rate_limiter.get_cache_stats().await; println!(" After 100 requests:"); println!(" └─ Size: {}", stats2.size); assert!(stats2.size >= 50, "Should have cached some entries"); Ok(()) } #[tokio::test] async fn test_cache_clear() -> Result<()> { println!("\n=== Test: Cache Clear Operation ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; // Populate cache for _ in 0..50 { let user_id = Uuid::new_v4(); let _ = rate_limiter .check_limit(&user_id, "trading.submit_order") .await?; } let stats_before = rate_limiter.get_cache_stats().await; println!(" Cache size before clear: {}", stats_before.size); // Clear cache rate_limiter.clear_cache().await; let stats_after = rate_limiter.get_cache_stats().await; println!(" Cache size after clear: {}", stats_after.size); assert_eq!(stats_after.size, 0, "Cache should be empty after clear"); Ok(()) } #[tokio::test] async fn test_cache_concurrent_access() -> Result<()> { println!("\n=== Test: Cache Concurrent Access (DashMap Lock-Free) ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; let user_id = Uuid::new_v4(); // Populate cache for this user let _ = rate_limiter .check_limit(&user_id, "trading.submit_order") .await?; // Concurrent cache hits let mut handles = Vec::new(); println!(" Spawning 1000 concurrent cache hits..."); for _ in 0..1000 { let limiter = rate_limiter.clone(); let uid = user_id; let handle = tokio::spawn(async move { let start = Instant::now(); let _ = limiter.check_limit(&uid, "trading.submit_order").await; start.elapsed() }); handles.push(handle); } // Collect latencies let mut latencies = Vec::new(); for handle in handles { if let Ok(latency) = handle.await { latencies.push(latency); } } // Calculate percentiles latencies.sort(); let p50 = latencies[499]; let p99 = latencies[989]; println!(" Concurrent cache performance:"); println!(" ├─ P50: {:?}", p50); println!(" ├─ P99: {:?}", p99); println!(" └─ Target: <8ns (DashMap lock-free)"); // Note: Actual latency includes network and system overhead // Target <8ns is for the DashMap operation itself Ok(()) } #[tokio::test] async fn test_cache_invalidation_on_error() -> Result<()> { println!("\n=== Test: Cache Invalidation on Error ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; let user_id = Uuid::new_v4(); // Normal request to populate cache let result1 = rate_limiter .check_limit(&user_id, "trading.submit_order") .await?; println!(" Initial request: {}", result1); // Subsequent requests should use cache let result2 = rate_limiter .check_limit(&user_id, "trading.submit_order") .await?; println!(" Cached request: {}", result2); // Cache should remain valid across requests assert!(result1 || result2, "At least one request should succeed"); Ok(()) } #[tokio::test] async fn test_cache_size_overflow_handling() -> Result<()> { println!("\n=== Test: Cache Size Overflow Handling ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; // Attempt to overflow cache with rapid insertions println!(" Rapid insertion of 11,000 entries..."); let start = Instant::now(); for i in 0..11_000 { let user_id = Uuid::new_v4(); let _ = rate_limiter .check_limit(&user_id, "trading.submit_order") .await?; if i % 2000 == 0 { let stats = rate_limiter.get_cache_stats().await; println!(" {} entries: cache size = {}", i, stats.size); } } let duration = start.elapsed(); let final_stats = rate_limiter.get_cache_stats().await; println!(" Final: {} entries in {:?}", final_stats.size, duration); // Should handle overflow gracefully via LRU eviction assert!( final_stats.size <= 10_000, "Should not exceed max cache size" ); Ok(()) } // ============================================================================ // SECTION 3: Endpoint Configuration Tests (6 tests) // ============================================================================ #[tokio::test] async fn test_default_endpoint_configs() -> Result<()> { println!("\n=== Test: Default Endpoint Configurations ==="); let trading = RateLimitConfig::trading_submit_order(); let config = RateLimitConfig::config_update(); let backtest = RateLimitConfig::backtesting_run(); println!(" Trading config:"); println!(" ├─ Capacity: {}", trading.capacity); println!(" ├─ Refill rate: {}/s", trading.refill_rate); println!(" └─ Burst size: {}", trading.burst_size); println!(" Config update:"); println!(" ├─ Capacity: {}", config.capacity); println!(" ├─ Refill rate: {}/s", config.refill_rate); println!(" └─ Burst size: {}", config.burst_size); println!(" Backtesting:"); println!(" ├─ Capacity: {}", backtest.capacity); println!(" ├─ Refill rate: {}/min", backtest.refill_rate * 60.0); println!(" └─ Burst size: {}", backtest.burst_size); assert_eq!(trading.capacity, 100.0, "Trading capacity"); assert_eq!(config.capacity, 10.0, "Config capacity"); assert_eq!(backtest.capacity, 5.0, "Backtest capacity"); Ok(()) } #[tokio::test] async fn test_dynamic_endpoint_config() -> Result<()> { println!("\n=== Test: Dynamic Endpoint Configuration ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; // Add custom endpoint config let custom_config = RateLimitConfig { endpoint: "custom.endpoint".to_string(), capacity: 20.0, refill_rate: 20.0, burst_size: 5, }; rate_limiter.set_endpoint_config(custom_config).await; println!(" Custom endpoint config added"); // Use the custom endpoint let user_id = Uuid::new_v4(); let mut allowed = 0; for _ in 0..30 { if rate_limiter .check_limit(&user_id, "custom.endpoint") .await? { allowed += 1; } } println!(" Custom endpoint allowed: {}", allowed); assert!( allowed >= 18 && allowed <= 22, "Should respect custom capacity" ); Ok(()) } #[tokio::test] async fn test_default_for_unknown_endpoint() -> Result<()> { println!("\n=== Test: Default Config for Unknown Endpoint ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; let user_id = Uuid::new_v4(); // Use unknown endpoint (should get default config) let mut allowed = 0; for _ in 0..70 { if rate_limiter .check_limit(&user_id, "unknown.endpoint") .await? { allowed += 1; } } println!(" Unknown endpoint allowed: {}", allowed); // Default is 50 req/s capacity assert!( allowed >= 45 && allowed <= 55, "Should use default 50 capacity" ); Ok(()) } #[tokio::test] async fn test_endpoint_config_update() -> Result<()> { println!("\n=== Test: Endpoint Configuration Update ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; let user_id = Uuid::new_v4(); // Set initial config let config1 = RateLimitConfig { endpoint: "mutable.endpoint".to_string(), capacity: 10.0, refill_rate: 10.0, burst_size: 2, }; rate_limiter.set_endpoint_config(config1).await; // Test with initial config let mut count1 = 0; for _ in 0..20 { if rate_limiter .check_limit(&user_id, "mutable.endpoint") .await? { count1 += 1; } } println!(" With capacity 10: {} allowed", count1); assert!(count1 <= 12, "Should respect initial capacity"); // Wait for refill tokio::time::sleep(Duration::from_millis(1100)).await; // Update config let config2 = RateLimitConfig { endpoint: "mutable.endpoint".to_string(), capacity: 50.0, refill_rate: 50.0, burst_size: 10, }; rate_limiter.set_endpoint_config(config2).await; // Clear cache to use new config rate_limiter.clear_cache().await; // Test with new config let user_id2 = Uuid::new_v4(); let mut count2 = 0; for _ in 0..70 { if rate_limiter .check_limit(&user_id2, "mutable.endpoint") .await? { count2 += 1; } } println!(" With capacity 50: {} allowed", count2); assert!(count2 >= 45, "Should respect updated capacity"); Ok(()) } #[tokio::test] async fn test_multiple_endpoint_configs() -> Result<()> { println!("\n=== Test: Multiple Endpoint Configurations ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; // Add multiple custom configs for i in 1..=10 { let config = RateLimitConfig { endpoint: format!("endpoint_{}", i), capacity: (i * 10) as f64, refill_rate: (i * 10) as f64, burst_size: i, }; rate_limiter.set_endpoint_config(config).await; } println!(" Added 10 endpoint configs"); // Verify each endpoint has correct limit for i in 1..=10 { let user_id = Uuid::new_v4(); let mut allowed = 0; let endpoint = format!("endpoint_{}", i); for _ in 0..(i * 20) { if rate_limiter.check_limit(&user_id, &endpoint).await? { allowed += 1; } } let expected = i * 10; println!( " Endpoint {}: {} allowed (expected ~{})", i, allowed, expected ); assert!(allowed <= expected + 2, "Should respect individual limits"); } Ok(()) } #[tokio::test] async fn test_endpoint_config_concurrency() -> Result<()> { println!("\n=== Test: Concurrent Endpoint Config Updates ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; // Concurrent config updates (DashMap should handle safely) let mut handles = Vec::new(); println!(" Spawning 100 concurrent config updates..."); for i in 0..100 { let limiter = rate_limiter.clone(); let handle = tokio::spawn(async move { let config = RateLimitConfig { endpoint: format!("concurrent_{}", i % 10), capacity: ((i % 10 + 1) * 10) as f64, refill_rate: ((i % 10 + 1) * 10) as f64, burst_size: i % 10 + 1, }; limiter.set_endpoint_config(config).await; }); handles.push(handle); } for handle in handles { handle.await?; } println!(" ✓ All concurrent updates completed"); // Verify configs are usable let user_id = Uuid::new_v4(); let result = rate_limiter.check_limit(&user_id, "concurrent_5").await?; println!(" Test request after concurrent updates: {}", result); Ok(()) } // ============================================================================ // SECTION 4: Performance Validation (3 tests) // ============================================================================ #[tokio::test] async fn test_cache_hit_performance() -> Result<()> { println!("\n=== Test: Cache Hit Performance (<8ns target) ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; let user_id = Uuid::new_v4(); // Warm up cache let _ = rate_limiter .check_limit(&user_id, "trading.submit_order") .await?; // Measure cache hit latency let mut latencies = Vec::new(); for _ in 0..1000 { let start = Instant::now(); let _ = rate_limiter .check_limit(&user_id, "trading.submit_order") .await; latencies.push(start.elapsed()); } latencies.sort(); let p50 = latencies[499]; let p95 = latencies[949]; let p99 = latencies[989]; println!(" Cache hit latency:"); println!(" ├─ P50: {:?}", p50); println!(" ├─ P95: {:?}", p95); println!(" ├─ P99: {:?}", p99); println!(" └─ Target: <8ns (DashMap operation only)"); // Note: Includes async/await overhead, actual DashMap is <8ns Ok(()) } #[tokio::test] async fn test_redis_hit_performance() -> Result<()> { println!("\n=== Test: Redis Hit Performance (<500μs target) ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; // Clear cache to force Redis hits rate_limiter.clear_cache().await; let mut latencies = Vec::new(); for _ in 0..100 { let user_id = Uuid::new_v4(); let start = Instant::now(); let _ = rate_limiter .check_limit(&user_id, "trading.submit_order") .await; latencies.push(start.elapsed()); } latencies.sort(); let p50 = latencies[49]; let p95 = latencies[94]; let p99 = latencies[99]; println!(" Redis hit latency:"); println!(" ├─ P50: {:?}", p50); println!(" ├─ P95: {:?}", p95); println!(" ├─ P99: {:?}", p99); println!(" └─ Target: <500μs"); assert!( p99 < Duration::from_millis(1), "P99 should be under 1ms for local Redis" ); Ok(()) } #[tokio::test] async fn test_throughput_performance() -> Result<()> { println!("\n=== Test: Throughput Performance ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; let start = Instant::now(); let mut requests = 0; // Make requests for 1 second while start.elapsed() < Duration::from_secs(1) { let user_id = Uuid::new_v4(); let _ = rate_limiter .check_limit(&user_id, "trading.submit_order") .await; requests += 1; } let duration = start.elapsed(); let req_per_sec = (requests as f64) / duration.as_secs_f64(); println!(" Throughput: {:.0} req/s", req_per_sec); println!(" Total requests: {}", requests); assert!(req_per_sec > 1000.0, "Should handle >1000 req/s"); Ok(()) } // ============================================================================ // SECTION 5: Integration Scenarios (3 tests) // ============================================================================ #[tokio::test] async fn test_full_workflow_integration() -> Result<()> { println!("\n=== Test: Full Workflow Integration ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; // Simulate realistic trading workflow let trader = Uuid::new_v4(); // 1. Config queries (high frequency) for _ in 0..5 { let _ = rate_limiter.check_limit(&trader, "config.get").await; } // 2. Trading submissions (burst) let mut trades_allowed = 0; for _ in 0..50 { if rate_limiter .check_limit(&trader, "trading.submit_order") .await? { trades_allowed += 1; } } // 3. Backtest request (rate-limited) let backtest_allowed = rate_limiter.check_limit(&trader, "backtesting.run").await?; println!(" Workflow results:"); println!(" ├─ Trades allowed: {}/50", trades_allowed); println!(" └─ Backtest allowed: {}", backtest_allowed); assert!(trades_allowed >= 40, "Should allow most trades"); Ok(()) } #[tokio::test] async fn test_multi_user_multi_endpoint() -> Result<()> { println!("\n=== Test: Multi-User Multi-Endpoint Integration ==="); let rate_limiter = RateLimiter::new(REDIS_URL).await?; // 10 users, 3 endpoints each let mut results = Vec::new(); for user_idx in 0..10 { let user_id = Uuid::new_v4(); let mut user_results = (0, 0, 0); for _ in 0..20 { if rate_limiter .check_limit(&user_id, "trading.submit_order") .await? { user_results.0 += 1; } if rate_limiter.check_limit(&user_id, "config.update").await? { user_results.1 += 1; } if rate_limiter .check_limit(&user_id, "backtesting.run") .await? { user_results.2 += 1; } } results.push(user_results); if user_idx % 3 == 0 { println!( " User {} results: trade={}, config={}, backtest={}", user_idx, user_results.0, user_results.1, user_results.2 ); } } // Verify all users got similar treatment let avg_trading: usize = results.iter().map(|(t, _, _)| t).sum::() / 10; println!(" Average trading per user: {}", avg_trading); assert!(avg_trading >= 15, "Users should get consistent limits"); Ok(()) } #[tokio::test] async fn test_cache_redis_consistency() -> Result<()> { println!("\n=== Test: Cache-Redis Consistency ==="); let limiter1 = RateLimiter::new(REDIS_URL).await?; let limiter2 = RateLimiter::new(REDIS_URL).await?; let user_id = Uuid::new_v4(); // Instance 1 makes requests (populates its cache) let mut count1 = 0; for _ in 0..10 { if limiter1.check_limit(&user_id, "config.update").await? { count1 += 1; } } println!(" Instance 1 allowed: {}", count1); // Instance 2 makes requests (separate cache, shared Redis) let mut count2 = 0; for _ in 0..10 { if limiter2.check_limit(&user_id, "config.update").await? { count2 += 1; } } println!(" Instance 2 allowed: {}", count2); println!(" Total: {}", count1 + count2); // Combined total should respect Redis state (10 capacity) assert!(count1 + count2 <= 12, "Combined should not exceed capacity"); Ok(()) }