api_openai 0.3.0

OpenAI's API for accessing large language models (LLMs).
Documentation
//! Enhanced Rate Limiting Integration Tests
//!
//! This module contains integration tests that validate actual rate limiting behavior
//! within the `OpenAI` client HTTP layer. These tests ensure rate limiting functionality
//! works correctly with real HTTP requests.

#[ cfg( feature = "rate_limiting" ) ]
mod rate_limiting_integration_tests
{
  use api_openai::
  {
    Client,
    ClientApiAccessors,
    environment ::{ OpenaiEnvironmentImpl, OpenAIRecommended },
    secret ::Secret,
    enhanced_rate_limiting ::{ EnhancedRateLimitingConfig, RateLimitingAlgorithm },
  };

  use core::time::Duration;
  use tokio::time::sleep;

  /// Create test client with rate limiting configuration
  fn create_test_client_with_rate_limiting() -> Result< Client< OpenaiEnvironmentImpl >, Box< dyn std::error::Error > >
  {
    // Use test API key that won't work for actual requests
    let secret = Secret::new( "sk-test-key-rate-limiting".to_string() )?;
    let environment = OpenaiEnvironmentImpl::build(
      secret,
      None,
      None,
      OpenAIRecommended::base_url().to_string(),
      OpenAIRecommended::realtime_base_url().to_string()
    )?;

    let rate_limiting_config = EnhancedRateLimitingConfig::new()
      .with_max_requests( 2 ) // Very low limit for testing
      .with_window_duration( 1000 ) // 1 second window
      .with_algorithm( RateLimitingAlgorithm::TokenBucket )
      .with_burst_capacity( 2 );

    let client = Client::build( environment )?
      .with_rate_limiting_config( rate_limiting_config );

    Ok( client )
  }

  #[ tokio::test ]
  async fn test_rate_limiting_integration_with_client()
  {
    let client = create_test_client_with_rate_limiting().expect( "Failed to create test client" );

    // Verify rate limiting configuration is properly set
    assert!( client.rate_limiting_config().is_some() );
    let config = client.rate_limiting_config().unwrap();
    assert_eq!( config.max_requests, 2 );
    assert_eq!( config.window_duration_ms, 1000 );
    assert_eq!( config.burst_capacity, 2 );
    assert_eq!( config.algorithm, RateLimitingAlgorithm::TokenBucket );
  }

  #[ tokio::test ]
  async fn test_rate_limiting_with_multiple_requests()
  {
    let client = create_test_client_with_rate_limiting().expect( "Failed to create test client" );

    // Make requests that will be rate limited (invalid API key, but rate limiter should engage first)

    // First two requests - should succeed through rate limiter but fail due to invalid API key
    let result1 = client.models().list().await;
    assert!( result1.is_err() );

    let result2 = client.models().list().await;
    assert!( result2.is_err() );

    // Third request - should be rejected by rate limiter immediately
    let result3 = client.models().list().await;
    assert!( result3.is_err() );

    // Check if the error indicates rate limiting rejection
    // (This is tricky to test precisely since we can't easily distinguish
    // rate limiting errors from API errors in this integration test)

    // Wait for rate limit window to reset
    sleep( Duration::from_millis( 1100 ) ).await;

    // Next request should be allowed through rate limiter again
    let result4 = client.models().list().await;
    assert!( result4.is_err() ); // Still fails due to invalid API key, but rate limiter allows it
  }

  #[ tokio::test ]
  async fn test_rate_limiting_zero_overhead_when_not_configured()
  {
    // Create client without rate limiting configuration
    let secret = Secret::new( "sk-test-key-no-rate-limiting".to_string() ).unwrap();
    let environment = OpenaiEnvironmentImpl::build(
      secret,
      None,
      None,
      OpenAIRecommended::base_url().to_string(),
      OpenAIRecommended::realtime_base_url().to_string()
    ).unwrap();
    let client = Client::build( environment ).unwrap();

    // Verify no rate limiting configuration
    assert!( client.rate_limiting_config().is_none() );

    // Requests should work normally (fail due to invalid API key, not rate limiting)
    let result = client.models().list().await;
    assert!( result.is_err() );

    // Multiple rapid requests should not be rate limited
    for _ in 0..5
    {
      let result = client.models().list().await;
      assert!( result.is_err() );
    }
  }

  #[ tokio::test ]
  async fn test_rate_limiting_config_validation()
  {
    let secret = Secret::new( "sk-test-key".to_string() ).unwrap();
    let environment = OpenaiEnvironmentImpl::build(
      secret,
      None,
      None,
      OpenAIRecommended::base_url().to_string(),
      OpenAIRecommended::realtime_base_url().to_string()
    ).unwrap();

    // Test with invalid configuration (should use fallback)
    let invalid_config = EnhancedRateLimitingConfig::new()
      .with_max_requests( 0 ); // Invalid max_requests

    // The client should handle invalid configuration gracefully
    let client = Client::build( environment ).unwrap()
      .with_rate_limiting_config( invalid_config );

    // The invalid config should still be stored (validation happens in EnhancedRateLimiter::new)
    assert!( client.rate_limiting_config().is_some() );
  }

  #[ tokio::test ]
  async fn test_rate_limiting_sliding_window_algorithm()
  {
    let secret = Secret::new( "sk-test-key-sliding-window".to_string() ).unwrap();
    let environment = OpenaiEnvironmentImpl::build(
      secret,
      None,
      None,
      OpenAIRecommended::base_url().to_string(),
      OpenAIRecommended::realtime_base_url().to_string()
    ).unwrap();

    let rate_limiting_config = EnhancedRateLimitingConfig::new()
      .with_max_requests( 3 )
      .with_window_duration( 2000 ) // 2 second window
      .with_algorithm( RateLimitingAlgorithm::SlidingWindow );

    let client = Client::build( environment ).unwrap()
      .with_rate_limiting_config( rate_limiting_config );

    // Verify sliding window configuration
    let config = client.rate_limiting_config().unwrap();
    assert_eq!( config.algorithm, RateLimitingAlgorithm::SlidingWindow );
    assert_eq!( config.max_requests, 3 );
    assert_eq!( config.window_duration_ms, 2000 );

    // Test that sliding window allows requests within the limit
    for _i in 0..3
    {
      let result = client.models().list().await;
      assert!( result.is_err() ); // Fails due to invalid API key, not rate limiting
    }

    // Fourth request should be rate limited
    let result = client.models().list().await;
    assert!( result.is_err() );
  }
}

#[ cfg( not( feature = "rate_limiting" ) ) ]
mod no_rate_limiting_integration_tests
{
  use api_openai::
  {
    Client,
    environment ::{ OpenaiEnvironmentImpl, OpenAIRecommended },
    secret ::Secret,
  };

  #[ tokio::test ]
  async fn test_zero_overhead_when_rate_limiting_feature_disabled()
  {
    // When rate limiting feature is disabled, client should work normally
    let secret = Secret::new( "sk-test-key".to_string() ).unwrap();
    let environment = OpenaiEnvironmentImpl::build(
      secret,
      None,
      None,
      OpenAIRecommended::base_url().to_string(),
      OpenAIRecommended::realtime_base_url().to_string()
    ).unwrap();
    let client = Client::build( environment ).unwrap();

    // Rate limiting configuration methods should not be available
    // (This is enforced at compile time by feature gates)

    // Requests should work normally without rate limiting overhead
    let result = client.models().list().await;
    assert!( result.is_err() ); // Fails due to invalid API key, not feature absence
  }
}