use crate::config::{Config, ProviderConfig};
use chrono::Utc;
use std::collections::HashMap;
use std::fs;
use tempfile::TempDir;
const TEST_PROVIDER_PREFIX: &str = "test-";
fn create_test_config() -> (Config, TempDir) {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
std::env::set_var("LC_TEST_CONFIG_DIR", temp_dir.path());
let _config_path = temp_dir.path().join("config.toml");
let config = Config {
providers: HashMap::new(),
default_provider: None,
default_model: None,
aliases: HashMap::new(),
system_prompt: None,
templates: HashMap::new(),
max_tokens: None,
temperature: None,
stream: None,
};
(config, temp_dir)
}
fn create_test_provider_config(endpoint: &str) -> ProviderConfig {
ProviderConfig {
endpoint: endpoint.to_string(),
api_key: Some("test-api-key".to_string()),
models: vec!["test-model-1".to_string(), "test-model-2".to_string()],
models_path: "/models".to_string(),
chat_path: "/chat/completions".to_string(),
images_path: Some("/images/generations".to_string()),
embeddings_path: Some("/embeddings".to_string()),
headers: HashMap::new(),
token_url: None,
cached_token: None,
auth_type: None,
vars: HashMap::new(),
chat_templates: None,
images_templates: None,
embeddings_templates: None,
models_templates: None,
audio_path: None,
speech_path: None,
audio_templates: None,
speech_templates: None,
}
}
fn create_config_with_providers() -> (Config, TempDir) {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
std::env::set_var("LC_TEST_CONFIG_DIR", temp_dir.path());
let mut config = Config {
providers: HashMap::new(),
default_provider: None,
default_model: None,
aliases: HashMap::new(),
system_prompt: None,
templates: HashMap::new(),
max_tokens: None,
temperature: None,
stream: None,
};
let openai_name = format!("{}openai", TEST_PROVIDER_PREFIX);
let anthropic_name = format!("{}anthropic", TEST_PROVIDER_PREFIX);
config.providers.insert(
openai_name.clone(),
create_test_provider_config("https://api.openai.com"),
);
config.providers.insert(
anthropic_name.clone(),
create_test_provider_config("https://api.anthropic.com"),
);
config.default_provider = Some(openai_name);
(config, temp_dir)
}
fn get_test_provider_name(base_name: &str) -> String {
format!("{}{}", TEST_PROVIDER_PREFIX, base_name)
}
#[allow(dead_code)]
pub fn cleanup_test_providers() -> Result<(), Box<dyn std::error::Error>> {
let home_dir = dirs::home_dir().ok_or("Could not find home directory")?;
let config_dir = home_dir.join("Library/Application Support/lc/providers");
if !config_dir.exists() {
return Ok(());
}
let mut cleaned_count = 0;
for entry in fs::read_dir(&config_dir)? {
let entry = entry?;
let file_name = entry.file_name();
let file_name_str = file_name.to_string_lossy();
if file_name_str.starts_with(TEST_PROVIDER_PREFIX) {
let file_path = entry.path();
if file_path.is_file() {
fs::remove_file(&file_path)?;
cleaned_count += 1;
}
}
}
if cleaned_count > 0 {
println!("Cleaned up {} test provider files", cleaned_count);
}
Ok(())
}
#[allow(dead_code)]
pub fn setup_tests() {
if let Err(e) = cleanup_test_providers() {
eprintln!("Warning: Failed to clean up test providers: {}", e);
}
}
#[allow(dead_code)]
pub fn teardown_tests() {
if let Err(e) = cleanup_test_providers() {
eprintln!("Warning: Failed to clean up test providers: {}", e);
}
}
#[cfg(test)]
mod provider_tests {
use super::*;
#[test]
fn test_provider_add_basic() {
let mut config = Config {
providers: HashMap::new(),
default_provider: None,
default_model: None,
aliases: HashMap::new(),
system_prompt: None,
templates: HashMap::new(),
max_tokens: None,
temperature: None,
stream: None,
};
let result = config.add_provider(
"test-provider".to_string(),
"https://api.test.com".to_string(),
);
assert!(result.is_ok());
assert!(config.has_provider("test-provider"));
let provider = config.get_provider("test-provider").unwrap();
assert_eq!(provider.endpoint, "https://api.test.com");
assert_eq!(provider.models_path, "/models");
assert_eq!(provider.chat_path, "/chat/completions");
assert!(provider.api_key.is_none());
assert!(provider.headers.is_empty());
assert_eq!(config.default_provider, Some("test-provider".to_string()));
}
#[test]
fn test_provider_add_with_custom_paths() {
let mut config = Config {
providers: HashMap::new(),
default_provider: None,
default_model: None,
aliases: HashMap::new(),
system_prompt: None,
templates: HashMap::new(),
max_tokens: None,
temperature: None,
stream: None,
};
let result = config.add_provider_with_paths(
"test-custom-provider".to_string(),
"https://api.custom.com".to_string(),
Some("/v1/models".to_string()),
Some("/v1/completions".to_string()),
);
assert!(result.is_ok());
assert!(config.has_provider("test-custom-provider"));
let provider = config.get_provider("test-custom-provider").unwrap();
assert_eq!(provider.endpoint, "https://api.custom.com");
assert_eq!(provider.models_path, "/v1/models");
assert_eq!(provider.chat_path, "/v1/completions");
}
#[test]
fn test_provider_add_second_provider_doesnt_change_default() {
let (mut config, _temp_dir) = create_config_with_providers();
let original_default = config.default_provider.clone();
let result = config.add_provider(
"test-new-provider".to_string(),
"https://api.new.com".to_string(),
);
assert!(result.is_ok());
assert!(config.has_provider("test-new-provider"));
assert_eq!(config.default_provider, original_default);
}
#[test]
fn test_provider_update_existing() {
let (mut config, _temp_dir) = create_config_with_providers();
let openai_name = get_test_provider_name("openai");
let result = config.add_provider(
openai_name.clone(),
"https://api.openai.com/v2".to_string(),
);
assert!(result.is_ok());
let provider = config.get_provider(&openai_name).unwrap();
assert_eq!(provider.endpoint, "https://api.openai.com/v2");
}
#[test]
fn test_provider_remove_existing() {
let (mut config, _temp_dir) = create_config_with_providers();
let anthropic_name = get_test_provider_name("anthropic");
assert!(config.has_provider(&anthropic_name));
config.providers.remove(&anthropic_name);
assert!(!config.has_provider(&anthropic_name));
}
#[test]
fn test_provider_remove_nonexistent() {
let (config, _temp_dir) = create_config_with_providers();
let result = config.get_provider("nonexistent");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not found"));
}
#[test]
fn test_provider_list_empty() {
let config = Config {
providers: HashMap::new(),
default_provider: None,
default_model: None,
aliases: HashMap::new(),
system_prompt: None,
templates: HashMap::new(),
max_tokens: None,
temperature: None,
stream: None,
};
assert!(config.providers.is_empty());
}
#[test]
fn test_provider_list_with_providers() {
let (config, _temp_dir) = create_config_with_providers();
let openai_name = get_test_provider_name("openai");
let anthropic_name = get_test_provider_name("anthropic");
assert_eq!(config.providers.len(), 2);
assert!(config.has_provider(&openai_name));
assert!(config.has_provider(&anthropic_name));
}
#[test]
fn test_provider_api_key_management() {
let (mut config, _temp_dir) = create_config_with_providers();
let openai_name = get_test_provider_name("openai");
let result = config.set_api_key(openai_name.clone(), "new-api-key".to_string());
assert!(result.is_ok());
let provider_with_auth = config.get_provider_with_auth(&openai_name).unwrap();
assert_eq!(provider_with_auth.api_key, Some("new-api-key".to_string()));
let result = config.set_api_key("nonexistent".to_string(), "key".to_string());
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not found"));
}
#[test]
fn test_provider_headers_management() {
let (mut config, _temp_dir) = create_config_with_providers();
let openai_name = get_test_provider_name("openai");
let result = config.add_header(
openai_name.clone(),
"X-Custom-Header".to_string(),
"custom-value".to_string(),
);
assert!(result.is_ok());
let headers = config.list_headers(&openai_name).unwrap();
assert_eq!(
headers.get("X-Custom-Header"),
Some(&"custom-value".to_string())
);
let result = config.remove_header(openai_name.clone(), "X-Custom-Header".to_string());
assert!(result.is_ok());
let headers = config.list_headers(&openai_name).unwrap();
assert!(!headers.contains_key("X-Custom-Header"));
let result = config.remove_header(openai_name.clone(), "Non-Existent".to_string());
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not found"));
let result = config.add_header(
"nonexistent".to_string(),
"header".to_string(),
"value".to_string(),
);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not found"));
}
#[test]
fn test_provider_token_url_management() {
let (mut config, _temp_dir) = create_config_with_providers();
let openai_name = get_test_provider_name("openai");
let result = config.set_token_url(
openai_name.clone(),
"https://auth.openai.com/token".to_string(),
);
assert!(result.is_ok());
let token_url = config.get_token_url(&openai_name);
assert_eq!(
token_url,
Some(&"https://auth.openai.com/token".to_string())
);
let result =
config.set_token_url("nonexistent".to_string(), "https://example.com".to_string());
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not found"));
}
#[test]
fn test_provider_cached_token_management() {
let (mut config, _temp_dir) = create_config_with_providers();
let openai_name = get_test_provider_name("openai");
let expires_at = Utc::now() + chrono::Duration::hours(1);
let result = config.set_cached_token(
openai_name.clone(),
"cached-token-123".to_string(),
expires_at,
);
assert!(result.is_ok());
let cached_token = config.get_cached_token(&openai_name);
assert!(cached_token.is_some());
assert_eq!(cached_token.unwrap().token, "cached-token-123");
assert_eq!(cached_token.unwrap().expires_at, expires_at);
let result =
config.set_cached_token("nonexistent".to_string(), "token".to_string(), expires_at);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not found"));
}
#[test]
fn test_provider_token_url_clears_cached_token() {
let (mut config, _temp_dir) = create_config_with_providers();
let openai_name = get_test_provider_name("openai");
let expires_at = Utc::now() + chrono::Duration::hours(1);
config
.set_cached_token(openai_name.clone(), "cached-token".to_string(), expires_at)
.unwrap();
assert!(config.get_cached_token(&openai_name).is_some());
config
.set_token_url(
openai_name.clone(),
"https://auth.openai.com/token".to_string(),
)
.unwrap();
assert!(config.get_cached_token(&openai_name).is_none());
}
}
#[cfg(test)]
mod provider_command_tests {
use crate::cli::{HeaderCommands, ProviderCommands};
#[test]
fn test_provider_add_command_structure() {
let command = ProviderCommands::Add {
name: "test-provider".to_string(),
url: "https://api.test.com".to_string(),
models_path: Some("/v1/models".to_string()),
chat_path: Some("/v1/chat".to_string()),
};
match command {
ProviderCommands::Add {
name,
url,
models_path,
chat_path,
} => {
assert_eq!(name, "test-provider");
assert_eq!(url, "https://api.test.com");
assert_eq!(models_path, Some("/v1/models".to_string()));
assert_eq!(chat_path, Some("/v1/chat".to_string()));
}
_ => panic!("Expected Add command"),
}
}
#[test]
fn test_provider_update_command_structure() {
let command = ProviderCommands::Update {
name: "existing-provider".to_string(),
url: "https://api.updated.com".to_string(),
};
match command {
ProviderCommands::Update { name, url } => {
assert_eq!(name, "existing-provider");
assert_eq!(url, "https://api.updated.com");
}
_ => panic!("Expected Update command"),
}
}
#[test]
fn test_provider_remove_command_structure() {
let command = ProviderCommands::Remove {
name: "provider-to-remove".to_string(),
};
match command {
ProviderCommands::Remove { name } => {
assert_eq!(name, "provider-to-remove");
}
_ => panic!("Expected Remove command"),
}
}
#[test]
fn test_provider_list_command_structure() {
let command = ProviderCommands::List;
match command {
ProviderCommands::List => {
}
_ => panic!("Expected List command"),
}
}
#[test]
fn test_provider_models_command_structure() {
let command = ProviderCommands::Models {
name: "test-provider".to_string(),
refresh: true,
};
match command {
ProviderCommands::Models { name, refresh } => {
assert_eq!(name, "test-provider");
assert_eq!(refresh, true);
}
_ => panic!("Expected Models command"),
}
}
#[test]
fn test_provider_headers_command_structure() {
let add_command = ProviderCommands::Headers {
provider: "test-provider".to_string(),
command: HeaderCommands::Add {
name: "X-API-Version".to_string(),
value: "v1".to_string(),
},
};
match add_command {
ProviderCommands::Headers { provider, command } => {
assert_eq!(provider, "test-provider");
match command {
HeaderCommands::Add { name, value } => {
assert_eq!(name, "X-API-Version");
assert_eq!(value, "v1");
}
_ => panic!("Expected Add header command"),
}
}
_ => panic!("Expected Headers command"),
}
let delete_command = ProviderCommands::Headers {
provider: "test-provider".to_string(),
command: HeaderCommands::Delete {
name: "X-API-Version".to_string(),
},
};
match delete_command {
ProviderCommands::Headers { provider, command } => {
assert_eq!(provider, "test-provider");
match command {
HeaderCommands::Delete { name } => {
assert_eq!(name, "X-API-Version");
}
_ => panic!("Expected Delete header command"),
}
}
_ => panic!("Expected Headers command"),
}
let list_command = ProviderCommands::Headers {
provider: "test-provider".to_string(),
command: HeaderCommands::List,
};
match list_command {
ProviderCommands::Headers { provider, command } => {
assert_eq!(provider, "test-provider");
match command {
HeaderCommands::List => {
}
_ => panic!("Expected List header command"),
}
}
_ => panic!("Expected Headers command"),
}
}
#[test]
fn test_provider_token_url_command_structure() {
let command = ProviderCommands::TokenUrl {
provider: "test-provider".to_string(),
url: "https://auth.test.com/token".to_string(),
};
match command {
ProviderCommands::TokenUrl { provider, url } => {
assert_eq!(provider, "test-provider");
assert_eq!(url, "https://auth.test.com/token");
}
_ => panic!("Expected TokenUrl command"),
}
}
}
#[cfg(test)]
mod provider_edge_cases {
use super::*;
#[test]
fn test_provider_name_validation() {
let mut config = Config {
providers: HashMap::new(),
default_provider: None,
default_model: None,
aliases: HashMap::new(),
system_prompt: None,
templates: HashMap::new(),
max_tokens: None,
temperature: None,
stream: None,
};
let result = config.add_provider("".to_string(), "https://api.test.com".to_string());
assert!(result.is_ok());
let result = config.add_provider(
"test-provider_123".to_string(),
"https://api.test.com".to_string(),
);
assert!(result.is_ok());
let result = config.add_provider(
"test-test provider".to_string(),
"https://api.test.com".to_string(),
);
assert!(result.is_ok());
}
#[test]
fn test_provider_url_validation() {
let mut config = Config {
providers: HashMap::new(),
default_provider: None,
default_model: None,
aliases: HashMap::new(),
system_prompt: None,
templates: HashMap::new(),
max_tokens: None,
temperature: None,
stream: None,
};
let urls = vec![
"https://api.test.com",
"http://localhost:8080",
"https://api.test.com/",
"https://api.test.com/v1",
"invalid-url",
"",
];
for url in urls {
let result = config.add_provider(format!("test-provider-{}", url.len()), url.to_string());
assert!(result.is_ok()); }
}
#[test]
fn test_provider_paths_validation() {
let mut config = Config {
providers: HashMap::new(),
default_provider: None,
default_model: None,
aliases: HashMap::new(),
system_prompt: None,
templates: HashMap::new(),
max_tokens: None,
temperature: None,
stream: None,
};
let test_cases = vec![
(
Some("/models".to_string()),
Some("/chat/completions".to_string()),
),
(
Some("/v1/models".to_string()),
Some("/v1/chat/completions".to_string()),
),
(Some("models".to_string()), Some("chat".to_string())), (Some("".to_string()), Some("".to_string())), (None, None), ];
for (i, (models_path, chat_path)) in test_cases.into_iter().enumerate() {
let result = config.add_provider_with_paths(
format!("test-provider-{}", i),
"https://api.test.com".to_string(),
models_path.clone(),
chat_path.clone(),
);
assert!(result.is_ok());
let provider = config.get_provider(&format!("test-provider-{}", i)).unwrap();
assert_eq!(
provider.models_path,
models_path.unwrap_or_else(|| "/models".to_string())
);
assert_eq!(
provider.chat_path,
chat_path.unwrap_or_else(|| "/chat/completions".to_string())
);
}
}
#[test]
fn test_provider_duplicate_names() {
let mut config = Config {
providers: HashMap::new(),
default_provider: None,
default_model: None,
aliases: HashMap::new(),
system_prompt: None,
templates: HashMap::new(),
max_tokens: None,
temperature: None,
stream: None,
};
let result =
config.add_provider("test-duplicate".to_string(), "https://api1.test.com".to_string());
assert!(result.is_ok());
let provider1 = config.get_provider("test-duplicate").unwrap();
assert_eq!(provider1.endpoint, "https://api1.test.com");
let result =
config.add_provider("test-duplicate".to_string(), "https://api2.test.com".to_string());
assert!(result.is_ok());
let provider2 = config.get_provider("test-duplicate").unwrap();
assert_eq!(provider2.endpoint, "https://api2.test.com");
}
#[test]
fn test_provider_case_sensitivity() {
let mut config = Config {
providers: HashMap::new(),
default_provider: None,
default_model: None,
aliases: HashMap::new(),
system_prompt: None,
templates: HashMap::new(),
max_tokens: None,
temperature: None,
stream: None,
};
config
.add_provider("test-OpenAI".to_string(), "https://api.openai.com".to_string())
.unwrap();
config
.add_provider(
"test-openai".to_string(),
"https://api.openai.com/v2".to_string(),
)
.unwrap();
config
.add_provider(
"test-OPENAI".to_string(),
"https://api.openai.com/v3".to_string(),
)
.unwrap();
assert!(config.has_provider("test-OpenAI"));
assert!(config.has_provider("test-openai"));
assert!(config.has_provider("test-OPENAI"));
assert_eq!(config.providers.len(), 3);
}
#[test]
fn test_provider_header_edge_cases() {
let (mut config, _temp_dir) = create_config_with_providers();
let openai_name = get_test_provider_name("openai");
let result = config.add_header(openai_name.clone(), "".to_string(), "value".to_string());
assert!(result.is_ok());
let result = config.add_header(openai_name.clone(), "X-Empty".to_string(), "".to_string());
assert!(result.is_ok());
let result = config.add_header(
openai_name.clone(),
"X-Special-Chars!@#".to_string(),
"value!@#$%".to_string(),
);
assert!(result.is_ok());
config
.add_header(
openai_name.clone(),
"X-Test".to_string(),
"original".to_string(),
)
.unwrap();
config
.add_header(
openai_name.clone(),
"X-Test".to_string(),
"updated".to_string(),
)
.unwrap();
let headers = config.list_headers(&openai_name).unwrap();
assert_eq!(headers.get("X-Test"), Some(&"updated".to_string()));
}
}
#[cfg(test)]
mod provider_integration_tests {
use super::*;
#[test]
fn test_provider_workflow_complete() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
std::env::set_var("LC_TEST_CONFIG_DIR", temp_dir.path());
let mut config = Config {
providers: HashMap::new(),
default_provider: None,
default_model: None,
aliases: HashMap::new(),
system_prompt: None,
templates: HashMap::new(),
max_tokens: None,
temperature: None,
stream: None,
};
config
.add_provider_with_paths(
"test-provider-100".to_string(),
"https://api.test.com".to_string(),
Some("/v1/models".to_string()),
Some("/v1/chat".to_string()),
)
.unwrap();
assert!(config.has_provider("test-provider-100"));
assert_eq!(config.default_provider, Some("test-provider-100".to_string()));
config
.set_api_key("test-provider-100".to_string(), "secret-key".to_string())
.unwrap();
let provider_with_auth = config.get_provider_with_auth("test-provider-100").unwrap();
assert_eq!(provider_with_auth.api_key, Some("secret-key".to_string()));
config
.add_header(
"test-provider-100".to_string(),
"X-API-Version".to_string(),
"v1".to_string(),
)
.unwrap();
config
.add_header(
"test-provider-100".to_string(),
"X-Client".to_string(),
"lc-cli".to_string(),
)
.unwrap();
let headers = config.list_headers("test-provider-100").unwrap();
assert_eq!(headers.len(), 2);
assert_eq!(headers.get("X-API-Version"), Some(&"v1".to_string()));
assert_eq!(headers.get("X-Client"), Some(&"lc-cli".to_string()));
config
.set_token_url(
"test-provider-100".to_string(),
"https://auth.test.com/token".to_string(),
)
.unwrap();
assert_eq!(
config.get_token_url("test-provider-100"),
Some(&"https://auth.test.com/token".to_string())
);
let expires_at = Utc::now() + chrono::Duration::hours(1);
config
.set_cached_token(
"test-provider-100".to_string(),
"cached-token".to_string(),
expires_at,
)
.unwrap();
let cached_token = config.get_cached_token("test-provider-100").unwrap();
assert_eq!(cached_token.token, "cached-token");
config
.add_provider(
"test-provider-100".to_string(),
"https://api.test.com/v2".to_string(),
)
.unwrap();
let updated_provider = config.get_provider("test-provider-100").unwrap();
assert_eq!(updated_provider.endpoint, "https://api.test.com/v2");
assert_eq!(updated_provider.api_key, None);
config
.set_api_key(
"test-provider-100".to_string(),
"secret-key-updated".to_string(),
)
.unwrap();
let provider_with_key = config.get_provider_with_auth("test-provider-100").unwrap();
assert_eq!(
provider_with_key.api_key,
Some("secret-key-updated".to_string())
);
config
.add_header(
"test-provider-100".to_string(),
"X-API-Version".to_string(),
"v1".to_string(),
)
.unwrap();
config
.add_header(
"test-provider-100".to_string(),
"X-Client".to_string(),
"lc-cli".to_string(),
)
.unwrap();
config
.remove_header("test-provider-100".to_string(), "X-API-Version".to_string())
.unwrap();
let headers = config.list_headers("test-provider-100").unwrap();
assert_eq!(headers.len(), 1);
assert!(!headers.contains_key("X-API-Version"));
assert!(headers.contains_key("X-Client"));
config.providers.remove("test-provider-100");
assert!(!config.has_provider("test-provider-100"));
drop(temp_dir);
}
#[test]
fn test_multiple_providers_workflow() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
std::env::set_var("LC_TEST_CONFIG_DIR", temp_dir.path());
let mut config = Config {
providers: HashMap::new(),
default_provider: None,
default_model: None,
aliases: HashMap::new(),
system_prompt: None,
templates: HashMap::new(),
max_tokens: None,
temperature: None,
stream: None,
};
let providers = vec![
("test-openai-1", "https://api.openai.com"),
("test-anthropic-1", "https://api.anthropic.com"),
("test-cohere-1", "https://api.cohere.ai"),
];
for (name, url) in providers {
config
.add_provider(name.to_string(), url.to_string())
.unwrap();
config
.set_api_key(name.to_string(), format!("{}-api-key", name))
.unwrap();
}
assert_eq!(config.providers.len(), 3);
assert!(config.has_provider("test-openai-1"));
assert!(config.has_provider("test-anthropic-1"));
assert!(config.has_provider("test-cohere-1"));
assert_eq!(config.default_provider, Some("test-openai-1".to_string()));
for (name, _) in &[("test-openai-1", ""), ("test-anthropic-1", ""), ("test-cohere-1", "")] {
let provider_with_auth = config.get_provider_with_auth(name).unwrap();
assert_eq!(provider_with_auth.api_key, Some(format!("{}-api-key", name)));
}
config
.add_header(
"test-openai-1".to_string(),
"X-OpenAI-Version".to_string(),
"2023-12-01".to_string(),
)
.unwrap();
config
.add_header(
"test-anthropic-1".to_string(),
"X-Anthropic-Version".to_string(),
"2023-06-01".to_string(),
)
.unwrap();
config
.add_header(
"test-cohere-1".to_string(),
"X-Cohere-Version".to_string(),
"2023-08-01".to_string(),
)
.unwrap();
let openai_headers = config.list_headers("test-openai-1").unwrap();
let anthropic_headers = config.list_headers("test-anthropic-1").unwrap();
let cohere_headers = config.list_headers("test-cohere-1").unwrap();
assert!(openai_headers.contains_key("X-OpenAI-Version"));
assert!(!openai_headers.contains_key("X-Anthropic-Version"));
assert!(!openai_headers.contains_key("X-Cohere-Version"));
assert!(anthropic_headers.contains_key("X-Anthropic-Version"));
assert!(!anthropic_headers.contains_key("X-OpenAI-Version"));
assert!(!anthropic_headers.contains_key("X-Cohere-Version"));
assert!(cohere_headers.contains_key("X-Cohere-Version"));
assert!(!cohere_headers.contains_key("X-OpenAI-Version"));
assert!(!cohere_headers.contains_key("X-Anthropic-Version"));
drop(temp_dir);
}
#[test]
fn test_provider_error_scenarios() {
let (mut config, _temp_dir) = create_config_with_providers();
let nonexistent_provider = "nonexistent".to_string();
let result = config.set_api_key(nonexistent_provider.clone(), "key".to_string());
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not found"));
let result = config.add_header(
nonexistent_provider.clone(),
"header".to_string(),
"value".to_string(),
);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not found"));
let result = config.remove_header(nonexistent_provider.clone(), "header".to_string());
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not found"));
let result = config.list_headers(&nonexistent_provider);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not found"));
let result = config.set_token_url(
nonexistent_provider.clone(),
"https://example.com".to_string(),
);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not found"));
let expires_at = Utc::now() + chrono::Duration::hours(1);
let result = config.set_cached_token(
nonexistent_provider.clone(),
"token".to_string(),
expires_at,
);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not found"));
}
#[test]
fn test_provider_concurrent_operations() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
std::env::set_var("LC_TEST_CONFIG_DIR", temp_dir.path());
let (mut config, _config_temp_dir) = create_config_with_providers();
let provider_name = get_test_provider_name("openai");
config
.set_api_key(provider_name.clone(), "api-key-1".to_string())
.unwrap();
config
.add_header(
provider_name.clone(),
"X-Header-1".to_string(),
"value-1".to_string(),
)
.unwrap();
config
.add_header(
provider_name.clone(),
"X-Header-2".to_string(),
"value-2".to_string(),
)
.unwrap();
config
.add_header(
provider_name.clone(),
"X-Header-3".to_string(),
"value-3".to_string(),
)
.unwrap();
config
.set_token_url(
provider_name.clone(),
"https://auth.openai.com/token".to_string(),
)
.unwrap();
let provider_with_auth = config.get_provider_with_auth(&provider_name).unwrap();
assert_eq!(provider_with_auth.api_key, Some("api-key-1".to_string()));
assert_eq!(
provider_with_auth.token_url,
Some("https://auth.openai.com/token".to_string())
);
let headers = config.list_headers(&provider_name).unwrap();
assert_eq!(headers.len(), 3);
assert!(headers.contains_key("X-Header-1"));
assert!(headers.contains_key("X-Header-2"));
assert!(headers.contains_key("X-Header-3"));
config
.set_api_key(provider_name.clone(), "api-key-2".to_string())
.unwrap();
let provider_with_auth = config.get_provider_with_auth(&provider_name).unwrap();
assert_eq!(provider_with_auth.api_key, Some("api-key-2".to_string()));
let headers_before = config.list_headers(&provider_name).unwrap();
if headers_before.contains_key("X-Header-2") {
config
.remove_header(provider_name.clone(), "X-Header-2".to_string())
.unwrap();
let headers = config.list_headers(&provider_name).unwrap();
assert_eq!(headers.len(), 2);
assert!(!headers.contains_key("X-Header-2"));
}
drop(temp_dir);
drop(_config_temp_dir);
}
}
#[cfg(test)]
mod provider_alias_integration_tests {
use super::*;
#[test]
fn test_provider_alias_workflow() {
let (mut config, _temp_dir) = create_config_with_providers();
let openai_name = get_test_provider_name("openai");
let result = config.add_alias("gpt4".to_string(), format!("{}:gpt-4", openai_name));
assert!(result.is_ok());
let alias = config.get_alias("gpt4");
assert_eq!(alias, Some(&format!("{}:gpt-4", openai_name)));
let result = config.add_alias("invalid".to_string(), "nonexistent:model".to_string());
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not found"));
let result = config.add_alias("invalid-format".to_string(), "just-a-model".to_string());
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("format"));
let result = config.remove_alias("gpt4".to_string());
assert!(result.is_ok());
assert!(config.get_alias("gpt4").is_none());
let result = config.remove_alias("nonexistent".to_string());
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not found"));
}
}
#[cfg(test)]
mod provider_config_persistence_tests {
use super::*;
use std::fs;
#[test]
fn test_config_save_and_load() {
let (mut config, temp_dir) = create_test_config();
let config_path = temp_dir.path().join("config.toml");
config
.add_provider_with_paths(
"test-provider".to_string(),
"https://api.test.com".to_string(),
Some("/v1/models".to_string()),
Some("/v1/chat".to_string()),
)
.unwrap();
config
.set_api_key("test-provider".to_string(), "secret-key".to_string())
.unwrap();
config
.add_header(
"test-provider".to_string(),
"X-Custom".to_string(),
"value".to_string(),
)
.unwrap();
let toml_content = toml::to_string_pretty(&config).unwrap();
fs::write(&config_path, &toml_content).unwrap();
let loaded_content = fs::read_to_string(&config_path).unwrap();
let loaded_config: Config = toml::from_str(&loaded_content).unwrap();
assert!(loaded_config.has_provider("test-provider"));
let provider = loaded_config.get_provider("test-provider").unwrap();
assert_eq!(provider.endpoint, "https://api.test.com");
assert_eq!(provider.models_path, "/v1/models");
assert_eq!(provider.chat_path, "/v1/chat");
assert_eq!(provider.api_key, None);
assert_eq!(provider.headers.get("X-Custom"), Some(&"value".to_string()));
let provider_with_auth = loaded_config.get_provider_with_auth("test-provider").unwrap();
assert_eq!(provider_with_auth.api_key, Some("secret-key".to_string()));
}
#[test]
fn test_config_with_cached_token_serialization() {
let (mut config, temp_dir) = create_test_config();
let config_path = temp_dir.path().join("config.toml");
config
.add_provider(
"test-provider".to_string(),
"https://api.test.com".to_string(),
)
.unwrap();
let expires_at = Utc::now() + chrono::Duration::hours(1);
config
.set_cached_token(
"test-provider".to_string(),
"cached-token-123".to_string(),
expires_at,
)
.unwrap();
let toml_content = toml::to_string_pretty(&config).unwrap();
fs::write(&config_path, &toml_content).unwrap();
let loaded_content = fs::read_to_string(&config_path).unwrap();
let loaded_config: Config = toml::from_str(&loaded_content).unwrap();
let cached_token = loaded_config.get_cached_token("test-provider");
assert!(cached_token.is_some());
assert_eq!(cached_token.unwrap().token, "cached-token-123");
assert_eq!(cached_token.unwrap().expires_at, expires_at);
}
}