227 lines
7.6 KiB
Rust
227 lines
7.6 KiB
Rust
#[cfg(test)]
|
||
mod config_env_tests {
|
||
use crate::models::Config;
|
||
use std::collections::HashMap;
|
||
use std::env;
|
||
use std::sync::{Mutex, OnceLock};
|
||
use tempfile::TempDir;
|
||
|
||
// Safe environment variable testing using static state
|
||
static TEST_ENV_LOCK: OnceLock<Mutex<HashMap<String, Option<String>>>> = OnceLock::new();
|
||
|
||
fn get_test_env_lock() -> &'static Mutex<HashMap<String, Option<String>>> {
|
||
TEST_ENV_LOCK.get_or_init(|| Mutex::new(HashMap::new()))
|
||
}
|
||
|
||
// Safe helper to set environment variable for testing
|
||
fn safe_set_env_var(key: &str, value: &str) {
|
||
let lock = get_test_env_lock();
|
||
let mut env_state = lock.lock().unwrap();
|
||
|
||
// Store the original value if this is the first time setting this var
|
||
if !env_state.contains_key(key) {
|
||
env_state.insert(key.to_string(), env::var(key).ok());
|
||
}
|
||
|
||
// This is still technically unsafe, but we'll wrap it in unsafe block
|
||
// and document that tests should run serially to avoid race conditions
|
||
unsafe {
|
||
env::set_var(key, value);
|
||
}
|
||
}
|
||
|
||
// Safe helper to remove environment variable for testing
|
||
fn safe_remove_env_var(key: &str) {
|
||
let lock = get_test_env_lock();
|
||
let mut env_state = lock.lock().unwrap();
|
||
|
||
// Store the original value if this is the first time touching this var
|
||
if !env_state.contains_key(key) {
|
||
env_state.insert(key.to_string(), env::var(key).ok());
|
||
}
|
||
|
||
unsafe {
|
||
env::remove_var(key);
|
||
}
|
||
}
|
||
|
||
// Helper function to clear all voice-cli environment variables safely
|
||
fn clear_voice_cli_env_vars() {
|
||
let env_vars = [
|
||
"VOICE_CLI_HOST",
|
||
"VOICE_CLI_PORT",
|
||
"VOICE_CLI_LOG_LEVEL",
|
||
"VOICE_CLI_LOG_DIR",
|
||
"VOICE_CLI_LOG_MAX_FILES",
|
||
"VOICE_CLI_DEFAULT_MODEL",
|
||
"VOICE_CLI_MODELS_DIR",
|
||
"VOICE_CLI_AUTO_DOWNLOAD",
|
||
"VOICE_CLI_TRANSCRIPTION_WORKERS",
|
||
"VOICE_CLI_WORK_DIR",
|
||
"VOICE_CLI_PID_FILE",
|
||
"VOICE_CLI_MAX_FILE_SIZE",
|
||
"VOICE_CLI_CORS_ENABLED",
|
||
];
|
||
|
||
for var in &env_vars {
|
||
safe_remove_env_var(var);
|
||
}
|
||
|
||
// Add a small delay to ensure environment changes propagate
|
||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||
}
|
||
|
||
// Cleanup function to restore original environment state
|
||
fn restore_original_env_vars() {
|
||
let lock = get_test_env_lock();
|
||
let env_state = lock.lock().unwrap();
|
||
|
||
for (key, original_value) in env_state.iter() {
|
||
match original_value {
|
||
Some(value) => unsafe {
|
||
env::set_var(key, value);
|
||
},
|
||
None => unsafe {
|
||
env::remove_var(key);
|
||
},
|
||
}
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
#[ignore = "Modifies global environment variables, causes race conditions with parallel tests"]
|
||
fn test_http_port_environment_override() {
|
||
clear_voice_cli_env_vars();
|
||
|
||
// Set environment variable for HTTP port
|
||
safe_set_env_var("VOICE_CLI_PORT", "9090");
|
||
|
||
let temp_dir = TempDir::new().unwrap();
|
||
let config_path = temp_dir.path().join("config.yml");
|
||
|
||
let config = Config::load_with_env_overrides(&config_path).unwrap();
|
||
|
||
// Verify HTTP port was overridden
|
||
assert_eq!(config.server.port, 9090);
|
||
|
||
// Clean up
|
||
safe_remove_env_var("VOICE_CLI_PORT");
|
||
}
|
||
|
||
#[test]
|
||
#[ignore = "Modifies global environment variables, causes race conditions with parallel tests"]
|
||
fn test_invalid_port_environment_variable() {
|
||
clear_voice_cli_env_vars();
|
||
|
||
// Set invalid environment variable
|
||
safe_set_env_var("VOICE_CLI_PORT", "invalid_port");
|
||
|
||
let temp_dir = TempDir::new().unwrap();
|
||
let config_path = temp_dir.path().join("config.yml");
|
||
|
||
let result = Config::load_with_env_overrides(&config_path);
|
||
|
||
// Should fail with proper error message
|
||
assert!(result.is_err());
|
||
let error_msg = result.unwrap_err().to_string();
|
||
assert!(error_msg.contains("Invalid VOICE_CLI_PORT value 'invalid_port'"));
|
||
|
||
// Clean up
|
||
safe_remove_env_var("VOICE_CLI_PORT");
|
||
}
|
||
|
||
#[test]
|
||
#[ignore = "Modifies global environment variables, causes race conditions with parallel tests"]
|
||
fn test_log_level_environment_override() {
|
||
clear_voice_cli_env_vars();
|
||
|
||
// Set environment variable for log level
|
||
safe_set_env_var("VOICE_CLI_LOG_LEVEL", "DEBUG");
|
||
|
||
let temp_dir = TempDir::new().unwrap();
|
||
let config_path = temp_dir.path().join("config.yml");
|
||
|
||
let config = Config::load_with_env_overrides(&config_path).unwrap();
|
||
|
||
// Verify log level was overridden and normalized to lowercase
|
||
assert_eq!(config.logging.level, "debug");
|
||
|
||
// Clean up
|
||
safe_remove_env_var("VOICE_CLI_LOG_LEVEL");
|
||
}
|
||
|
||
#[test]
|
||
#[ignore = "Modifies global environment variables, causes race conditions with parallel tests"]
|
||
fn test_invalid_log_level_environment_variable() {
|
||
clear_voice_cli_env_vars();
|
||
|
||
// Set invalid environment variable
|
||
safe_set_env_var("VOICE_CLI_LOG_LEVEL", "invalid_level");
|
||
|
||
let temp_dir = TempDir::new().unwrap();
|
||
let config_path = temp_dir.path().join("config.yml");
|
||
|
||
let result = Config::load_with_env_overrides(&config_path);
|
||
|
||
// Should fail with proper error message
|
||
assert!(result.is_err());
|
||
let error_msg = result.unwrap_err().to_string();
|
||
assert!(error_msg.contains("Invalid VOICE_CLI_LOG_LEVEL"));
|
||
assert!(error_msg.contains("invalid_level"));
|
||
|
||
// Clean up
|
||
safe_remove_env_var("VOICE_CLI_LOG_LEVEL");
|
||
}
|
||
|
||
#[test]
|
||
#[ignore = "Modifies global environment variables, causes race conditions with parallel tests"]
|
||
fn test_comprehensive_validation() {
|
||
clear_voice_cli_env_vars();
|
||
|
||
// Set multiple environment variables
|
||
// 注意:使用有效的模型名称(large-v3 而不是 large)
|
||
safe_set_env_var("VOICE_CLI_PORT", "8081");
|
||
safe_set_env_var("VOICE_CLI_LOG_LEVEL", "warn");
|
||
safe_set_env_var("VOICE_CLI_DEFAULT_MODEL", "large-v3");
|
||
|
||
let temp_dir = TempDir::new().unwrap();
|
||
let config_path = temp_dir.path().join("config.yml");
|
||
|
||
let config = Config::load_with_env_overrides(&config_path).unwrap();
|
||
|
||
// Verify all overrides were applied
|
||
assert_eq!(config.server.port, 8081);
|
||
assert_eq!(config.logging.level, "warn");
|
||
assert_eq!(config.whisper.default_model, "large-v3");
|
||
|
||
// Verify validation passes
|
||
assert!(config.validate().is_ok());
|
||
|
||
// Clean up
|
||
safe_remove_env_var("VOICE_CLI_PORT");
|
||
safe_remove_env_var("VOICE_CLI_LOG_LEVEL");
|
||
safe_remove_env_var("VOICE_CLI_DEFAULT_MODEL");
|
||
}
|
||
|
||
#[test]
|
||
fn test_empty_environment_variable_validation() {
|
||
clear_voice_cli_env_vars();
|
||
|
||
// Set empty environment variable
|
||
safe_set_env_var("VOICE_CLI_HOST", " "); // Use spaces instead of empty string
|
||
|
||
let temp_dir = TempDir::new().unwrap();
|
||
let config_path = temp_dir.path().join("config.yml");
|
||
|
||
let result = Config::load_with_env_overrides(&config_path);
|
||
|
||
// Should fail with proper error message
|
||
assert!(result.is_err());
|
||
let error_msg = result.unwrap_err().to_string();
|
||
assert!(error_msg.contains("VOICE_CLI_HOST environment variable cannot be empty"));
|
||
|
||
// Clean up
|
||
safe_remove_env_var("VOICE_CLI_HOST");
|
||
}
|
||
}
|