use crate::config::{AnthropicConfig, LLMConfig, LMStudioConfig, OpenAIConfig, ProviderConfig};
use crate::error::LlmError;
use crate::tests::helpers::create_test_config;
#[cfg(test)]
mod llm_config_tests {
use super::*;
#[test]
fn test_create_test_config_with_anthropic_provider() {
let config = create_test_config("anthropic");
assert_eq!(config.provider.provider_name(), "anthropic");
assert_eq!(config.provider.max_context_tokens(), 200_000);
assert!(
config.provider.api_key().is_some(),
"Test config should include API key"
);
}
#[test]
fn test_create_test_config_with_openai_provider() {
let config = create_test_config("openai");
assert_eq!(config.provider.provider_name(), "openai");
assert_eq!(config.provider.max_context_tokens(), 128_000);
assert!(
config.provider.api_key().is_some(),
"Test config should include API key"
);
}
#[test]
fn test_create_test_config_with_lmstudio_provider() {
let config = create_test_config("lmstudio");
assert_eq!(config.provider.provider_name(), "lmstudio");
assert_eq!(config.provider.max_context_tokens(), 4_096);
assert!(
config.provider.api_key().is_none(),
"LM Studio should not require API key"
);
}
#[test]
#[should_panic(expected = "Unsupported test provider")]
fn test_create_test_config_with_unsupported_provider() {
create_test_config("invalid-provider");
}
#[test]
fn test_default_params_applied_correctly() {
let config = create_test_config("anthropic");
assert_eq!(config.default_params.temperature, 0.7);
assert_eq!(config.default_params.max_tokens, 1000);
assert_eq!(config.default_params.top_p, 0.9);
assert_eq!(config.default_params.top_k, 40);
assert_eq!(config.default_params.min_p, 0.05);
assert_eq!(config.default_params.presence_penalty, 0.0);
}
}
#[cfg(test)]
mod llm_config_from_env_tests {
use super::*;
use serial_test::serial;
#[test]
#[serial]
fn test_from_env_defaults_to_anthropic_when_no_provider_set() {
std::env::remove_var("AI_PROVIDER");
std::env::set_var("ANTHROPIC_API_KEY", "test-key-anthropic");
let config = LLMConfig::from_env().expect("Should create config with defaults");
assert_eq!(config.provider.provider_name(), "anthropic");
assert_eq!(config.provider.api_key(), Some("test-key-anthropic"));
std::env::remove_var("ANTHROPIC_API_KEY");
}
#[test]
#[serial]
fn test_from_env_anthropic_with_api_key() {
std::env::set_var("AI_PROVIDER", "anthropic");
std::env::set_var("ANTHROPIC_API_KEY", "test-anthropic-key");
let config = LLMConfig::from_env().expect("Should create Anthropic config");
assert_eq!(config.provider.provider_name(), "anthropic");
assert_eq!(config.provider.api_key(), Some("test-anthropic-key"));
assert_eq!(config.provider.max_context_tokens(), 200_000);
std::env::remove_var("AI_PROVIDER");
std::env::remove_var("ANTHROPIC_API_KEY");
}
#[test]
#[serial]
fn test_from_env_openai_with_api_key_and_custom_base_url() {
std::env::set_var("AI_PROVIDER", "openai");
std::env::set_var("OPENAI_API_KEY", "test-openai-key");
std::env::set_var("OPENAI_BASE_URL", "https://custom.openai.com");
let config = LLMConfig::from_env().expect("Should create OpenAI config");
assert_eq!(config.provider.provider_name(), "openai");
assert_eq!(config.provider.api_key(), Some("test-openai-key"));
assert_eq!(config.provider.base_url(), "https://custom.openai.com");
std::env::remove_var("AI_PROVIDER");
std::env::remove_var("OPENAI_API_KEY");
std::env::remove_var("OPENAI_BASE_URL");
}
#[test]
#[serial]
fn test_from_env_openai_defaults_base_url_when_missing() {
std::env::set_var("AI_PROVIDER", "openai");
std::env::set_var("OPENAI_API_KEY", "test-key");
let config = LLMConfig::from_env().expect("Should create config with defaults");
assert_eq!(config.provider.base_url(), "https://api.openai.com");
std::env::remove_var("AI_PROVIDER");
std::env::remove_var("OPENAI_API_KEY");
}
#[test]
#[serial]
fn test_from_env_lmstudio_with_custom_base_url() {
std::env::set_var("AI_PROVIDER", "lmstudio");
std::env::set_var("LM_STUDIO_BASE_URL", "http://localhost:8080");
let config = LLMConfig::from_env().expect("Should create LM Studio config");
assert_eq!(config.provider.provider_name(), "lmstudio");
assert_eq!(config.provider.base_url(), "http://localhost:8080");
assert!(config.provider.api_key().is_none());
std::env::remove_var("AI_PROVIDER");
std::env::remove_var("LM_STUDIO_BASE_URL");
}
#[test]
#[serial]
fn test_from_env_lmstudio_falls_back_to_openai_base_url() {
std::env::set_var("AI_PROVIDER", "lmstudio");
std::env::set_var("OPENAI_BASE_URL", "http://localhost:9999");
std::env::remove_var("LM_STUDIO_BASE_URL");
let config = LLMConfig::from_env().expect("Should create config with fallback");
assert_eq!(config.provider.base_url(), "http://localhost:9999");
std::env::remove_var("AI_PROVIDER");
std::env::remove_var("OPENAI_BASE_URL");
}
#[test]
#[serial]
fn test_from_env_lmstudio_uses_default_when_no_url_set() {
std::env::set_var("AI_PROVIDER", "lmstudio");
std::env::remove_var("LM_STUDIO_BASE_URL");
std::env::remove_var("OPENAI_BASE_URL");
let config = LLMConfig::from_env().expect("Should create config with defaults");
assert_eq!(config.provider.base_url(), "http://localhost:1234");
std::env::remove_var("AI_PROVIDER");
}
#[test]
#[serial]
fn test_from_env_unsupported_provider_returns_error() {
std::env::set_var("AI_PROVIDER", "unknown-provider");
let result = LLMConfig::from_env();
assert!(result.is_err());
match result.unwrap_err() {
LlmError::UnsupportedProvider { provider } => {
assert_eq!(provider, "unknown-provider");
}
_ => panic!("Expected UnsupportedProvider error"),
}
std::env::remove_var("AI_PROVIDER");
}
#[test]
#[serial]
fn test_from_env_validates_anthropic_missing_api_key() {
std::env::set_var("AI_PROVIDER", "anthropic");
std::env::remove_var("ANTHROPIC_API_KEY");
let result = LLMConfig::from_env();
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
LlmError::ConfigurationError { .. }
));
std::env::remove_var("AI_PROVIDER");
}
#[test]
#[serial]
fn test_from_env_validates_openai_missing_api_key() {
std::env::set_var("AI_PROVIDER", "openai");
std::env::remove_var("OPENAI_API_KEY");
let result = LLMConfig::from_env();
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
LlmError::ConfigurationError { .. }
));
std::env::remove_var("AI_PROVIDER");
}
}
#[cfg(test)]
mod anthropic_config_tests {
use super::*;
#[test]
fn test_anthropic_config_validation_with_api_key() {
let config = AnthropicConfig {
api_key: Some("test-api-key".to_string()),
..AnthropicConfig::default()
};
let result = config.validate();
assert!(result.is_ok(), "Should validate successfully with API key");
}
#[test]
fn test_anthropic_config_validation_without_api_key() {
let config = AnthropicConfig {
api_key: None,
..AnthropicConfig::default()
};
let result = config.validate();
assert!(result.is_err(), "Should fail validation without API key");
let error = result.unwrap_err();
assert!(matches!(error, LlmError::ConfigurationError { .. }));
}
#[test]
fn test_anthropic_config_provider_capabilities() {
let config = AnthropicConfig::default();
assert_eq!(config.provider_name(), "anthropic");
assert_eq!(config.max_context_tokens(), 200_000);
assert_eq!(config.base_url(), "https://api.anthropic.com");
assert_eq!(config.default_model(), "claude-3-5-sonnet-20241022");
}
}
#[cfg(test)]
mod openai_config_tests {
use super::*;
#[test]
fn test_openai_config_validation_with_api_key() {
let config = OpenAIConfig {
api_key: Some("test-openai-key".to_string()),
..OpenAIConfig::default()
};
let result = config.validate();
assert!(result.is_ok(), "Should validate successfully with API key");
}
#[test]
fn test_openai_config_validation_without_api_key() {
let config = OpenAIConfig {
api_key: None,
..OpenAIConfig::default()
};
let result = config.validate();
assert!(result.is_err(), "Should fail validation without API key");
let error = result.unwrap_err();
assert!(matches!(error, LlmError::ConfigurationError { .. }));
}
#[test]
fn test_openai_config_provider_capabilities() {
let config = OpenAIConfig::default();
assert_eq!(config.provider_name(), "openai");
assert_eq!(config.max_context_tokens(), 128_000);
assert_eq!(config.base_url(), "https://api.openai.com");
assert_eq!(config.default_model(), "gpt-4");
}
}
#[cfg(test)]
mod lmstudio_config_tests {
use super::*;
#[test]
fn test_lmstudio_config_validation_with_base_url() {
let config = LMStudioConfig {
base_url: "http://localhost:1234".to_string(),
..LMStudioConfig::default()
};
let result = config.validate();
assert!(result.is_ok(), "Should validate successfully with base URL");
}
#[test]
fn test_lmstudio_config_validation_without_base_url() {
let config = LMStudioConfig {
base_url: "".to_string(),
..LMStudioConfig::default()
};
let result = config.validate();
assert!(result.is_err(), "Should fail validation without base URL");
let error = result.unwrap_err();
assert!(matches!(error, LlmError::ConfigurationError { .. }));
}
#[test]
fn test_lmstudio_config_provider_capabilities() {
let config = LMStudioConfig::default();
assert_eq!(config.provider_name(), "lmstudio");
assert_eq!(config.max_context_tokens(), 4_096);
assert_eq!(config.base_url(), "http://localhost:1234");
assert_eq!(config.default_model(), "local-model");
assert!(
config.api_key().is_none(),
"LM Studio should not require API key"
);
}
}
#[cfg(test)]
mod llm_config_create_provider_tests {
use super::*;
#[test]
fn test_create_provider_anthropic_with_api_key() {
let result =
LLMConfig::create_provider("anthropic", Some("test-key".to_string()), None, None);
assert!(result.is_ok(), "Should create Anthropic provider");
let config = result.unwrap();
assert_eq!(config.provider.provider_name(), "anthropic");
assert!(config.provider.api_key().is_some());
}
#[test]
fn test_create_provider_openai_with_api_key() {
let result = LLMConfig::create_provider("openai", Some("test-key".to_string()), None, None);
assert!(result.is_ok(), "Should create OpenAI provider");
let config = result.unwrap();
assert_eq!(config.provider.provider_name(), "openai");
assert!(config.provider.api_key().is_some());
}
#[test]
fn test_create_provider_lmstudio() {
let result = LLMConfig::create_provider(
"lmstudio",
None,
Some("http://localhost:1234".to_string()),
None,
);
assert!(result.is_ok(), "Should create LM Studio provider");
let config = result.unwrap();
assert_eq!(config.provider.provider_name(), "lmstudio");
assert!(config.provider.api_key().is_none());
}
#[test]
fn test_create_provider_ollama() {
let result = LLMConfig::create_provider(
"ollama",
None,
Some("http://localhost:11434".to_string()),
None,
);
assert!(result.is_ok(), "Should create Ollama provider");
let config = result.unwrap();
assert_eq!(config.provider.provider_name(), "ollama");
}
#[test]
fn test_create_provider_unsupported() {
let result = LLMConfig::create_provider("unsupported", None, None, None);
assert!(result.is_err(), "Should reject unsupported provider");
match result.unwrap_err() {
LlmError::ConfigurationError { .. } => {} e => panic!("Expected ConfigurationError, got: {:?}", e),
}
}
#[test]
fn test_create_provider_validates_configuration() {
let result = LLMConfig::create_provider("openai", None, None, None);
assert!(
result.is_err(),
"Should fail validation when API key missing"
);
}
#[test]
fn test_create_provider_with_custom_model() {
let result = LLMConfig::create_provider(
"anthropic",
Some("test-key".to_string()),
None,
Some("claude-3-opus".to_string()),
);
assert!(result.is_ok());
let config = result.unwrap();
assert_eq!(config.provider.default_model(), "claude-3-opus");
}
#[test]
fn test_create_provider_case_insensitive() {
let result =
LLMConfig::create_provider("ANTHROPIC", Some("test-key".to_string()), None, None);
assert!(result.is_ok(), "Should handle uppercase provider name");
let config = result.unwrap();
assert_eq!(config.provider.provider_name(), "anthropic");
}
}
#[cfg(test)]
mod coverage_tests {
use super::*;
#[test]
fn test_llm_config_clone() {
let config = create_test_config("anthropic");
let cloned = config.clone();
assert_eq!(
cloned.provider.provider_name(),
config.provider.provider_name()
);
assert_eq!(
cloned.default_params.temperature,
config.default_params.temperature
);
}
#[test]
fn test_provider_config_downcasting() {
let config = create_test_config("anthropic");
let any_ref = config.provider.as_any();
let downcast = any_ref.downcast_ref::<AnthropicConfig>();
assert!(downcast.is_some(), "Should downcast to AnthropicConfig");
}
}