188 lines
5.7 KiB
Rust
188 lines
5.7 KiB
Rust
// Integration tests for LLM Proxy Gateway
|
|
|
|
use llm_proxy::config::Config;
|
|
use llm_proxy::database::Database;
|
|
use llm_proxy::state::AppState;
|
|
use llm_proxy::rate_limiting::RateLimitManager;
|
|
use tempfile::TempDir;
|
|
use std::fs;
|
|
|
|
#[tokio::test]
|
|
async fn test_config_loading() {
|
|
// Create a temporary config file
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let config_path = temp_dir.path().join("config.toml");
|
|
|
|
let config_content = r#"
|
|
[server]
|
|
port = 8080
|
|
host = "0.0.0.0"
|
|
|
|
[database]
|
|
path = "./data/test.db"
|
|
max_connections = 5
|
|
|
|
[providers.openai]
|
|
enabled = true
|
|
base_url = "https://api.openai.com/v1"
|
|
|
|
[providers.gemini]
|
|
enabled = true
|
|
base_url = "https://generativelanguage.googleapis.com/v1"
|
|
|
|
[providers.deepseek]
|
|
enabled = true
|
|
base_url = "https://api.deepseek.com"
|
|
|
|
[providers.grok]
|
|
enabled = false
|
|
base_url = "https://api.x.ai/v1"
|
|
|
|
[model_mapping]
|
|
"gpt-*" = "openai"
|
|
"gemini-*" = "gemini"
|
|
"deepseek-*" = "deepseek"
|
|
"grok-*" = "grok"
|
|
|
|
[pricing]
|
|
openai = { input = 0.01, output = 0.03 }
|
|
gemini = { input = 0.0005, output = 0.0015 }
|
|
deepseek = { input = 0.00014, output = 0.00028 }
|
|
grok = { input = 0.001, output = 0.003 }
|
|
"#;
|
|
|
|
fs::write(&config_path, config_content).unwrap();
|
|
|
|
// Test loading config
|
|
let config = Config::load_from_path(&config_path);
|
|
assert!(config.is_ok());
|
|
|
|
let config = config.unwrap();
|
|
assert_eq!(config.server.port, 8080);
|
|
assert!(config.providers.openai.is_some());
|
|
assert!(config.providers.grok.is_none());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_database_initialization() {
|
|
// Create a temporary database file
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let db_path = temp_dir.path().join("test.db");
|
|
|
|
// Test database initialization
|
|
let database = Database::new(&db_path).await;
|
|
assert!(database.is_ok());
|
|
|
|
let database = database.unwrap();
|
|
|
|
// Test connection
|
|
let test_result = database.test_connection().await;
|
|
assert!(test_result.is_ok());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_provider_manager() {
|
|
// Create a provider manager
|
|
use llm_proxy::providers::{ProviderManager, Provider};
|
|
use llm_proxy::config::OpenAIConfig;
|
|
|
|
let mut manager = ProviderManager::new();
|
|
assert_eq!(manager.providers.len(), 0);
|
|
|
|
// Test adding providers (we can't actually add real providers without API keys)
|
|
// This test just verifies the manager structure works
|
|
assert!(manager.get_provider_for_model("gpt-4").is_none());
|
|
assert!(manager.get_provider("openai").is_none());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_rate_limit_manager() {
|
|
let manager = RateLimitManager::new(60, 10);
|
|
|
|
// Test client rate limiting
|
|
let allowed = manager.check_request("test-client").await;
|
|
assert!(allowed); // First request should be allowed
|
|
|
|
// Test provider circuit breaker
|
|
let allowed = manager.check_provider("openai").await;
|
|
assert!(allowed); // Circuit should be closed initially
|
|
|
|
// Record some failures
|
|
manager.record_provider_failure("openai").await;
|
|
manager.record_provider_failure("openai").await;
|
|
manager.record_provider_failure("openai").await;
|
|
manager.record_provider_failure("openai").await;
|
|
manager.record_provider_failure("openai").await;
|
|
|
|
// After 5 failures, circuit should be open
|
|
let allowed = manager.check_provider("openai").await;
|
|
assert!(!allowed); // Circuit should be open
|
|
|
|
// Record success to close circuit
|
|
manager.record_provider_success("openai").await;
|
|
manager.record_provider_success("openai").await;
|
|
manager.record_provider_success("openai").await;
|
|
|
|
// After 3 successes in half-open state, circuit should be closed
|
|
let allowed = manager.check_provider("openai").await;
|
|
assert!(allowed); // Circuit should be closed again
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_app_state_creation() {
|
|
// Create a temporary database
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let db_path = temp_dir.path().join("test.db");
|
|
|
|
let database = Database::new(&db_path).await.unwrap();
|
|
|
|
// Test AppState creation using test utilities
|
|
use llm_proxy::test_utils::create_test_state;
|
|
let state = create_test_state().await;
|
|
|
|
// Verify state components are initialized
|
|
assert!(state.database.test_connection().await.is_ok());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_multimodal_image_converter() {
|
|
use llm_proxy::multimodal::{ImageConverter, ImageInput};
|
|
|
|
// Test model detection
|
|
assert!(ImageConverter::model_supports_multimodal("gpt-4-vision-preview"));
|
|
assert!(ImageConverter::model_supports_multimodal("gemini-pro-vision"));
|
|
assert!(!ImageConverter::model_supports_multimodal("gpt-3.5-turbo"));
|
|
assert!(!ImageConverter::model_supports_multimodal("gemini-pro"));
|
|
|
|
// Test data URL parsing (utility function)
|
|
let test_url = "data:image/jpeg;base64,SGVsbG8gV29ybGQ=";
|
|
let parts: Vec<&str> = test_url[5..].split(";base64,").collect();
|
|
assert_eq!(parts.len(), 2);
|
|
assert_eq!(parts[0], "image/jpeg");
|
|
assert_eq!(parts[1], "SGVsbG8gV29ybGQ=");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_error_conversions() {
|
|
use llm_proxy::errors::AppError;
|
|
use anyhow::anyhow;
|
|
|
|
// Test anyhow error conversion
|
|
let anyhow_error = anyhow!("Test error");
|
|
let app_error: AppError = anyhow_error.into();
|
|
|
|
match app_error {
|
|
AppError::InternalError(msg) => assert_eq!(msg, "Test error"),
|
|
_ => panic!("Expected InternalError"),
|
|
}
|
|
|
|
// Test sqlx error conversion
|
|
use sqlx::Error as SqlxError;
|
|
let sqlx_error = SqlxError::PoolClosed;
|
|
let app_error: AppError = sqlx_error.into();
|
|
|
|
match app_error {
|
|
AppError::DatabaseError(msg) => assert!(msg.contains("pool closed")),
|
|
_ => panic!("Expected DatabaseError"),
|
|
}
|
|
} |