use crate::ssh::ssh_config::env_cache::{EnvCacheConfig, EnvironmentCache, GLOBAL_ENV_CACHE};
use crate::ssh::ssh_config::path::expand_path_internal;
use std::time::{Duration, Instant};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_path_expansion_uses_cache() {
GLOBAL_ENV_CACHE.clear();
let test_path = "~/.ssh/config_${USER}_test";
let start = Instant::now();
let result1 = expand_path_internal(test_path);
let first_duration = start.elapsed();
let start = Instant::now();
let result2 = expand_path_internal(test_path);
let second_duration = start.elapsed();
assert!(result1.is_ok());
assert!(result2.is_ok());
let path1 = result1.unwrap();
let path2 = result2.unwrap();
assert_eq!(path1, path2);
let stats = GLOBAL_ENV_CACHE.stats();
assert!(
stats.hits > 0 || stats.misses > 0,
"Cache should have been accessed"
);
println!(
"First expansion took: {first_duration:?}, Second expansion took: {second_duration:?}"
);
println!("Cache stats: hits={}, misses={}", stats.hits, stats.misses);
}
#[test]
fn test_path_expansion_with_multiple_variables() {
GLOBAL_ENV_CACHE.clear();
let test_path = "${HOME}/.ssh/${USER}_config";
let result = expand_path_internal(test_path);
assert!(result.is_ok());
let expanded = result.unwrap();
let path_str = expanded.to_string_lossy();
assert!(!path_str.contains("${HOME}"));
assert!(!path_str.contains("${USER}"));
if let Ok(Some(home)) = GLOBAL_ENV_CACHE.get_env_var("HOME") {
assert!(path_str.contains(&home));
}
let stats = GLOBAL_ENV_CACHE.stats();
println!(
"Multi-variable expansion cache stats: hits={}, misses={}",
stats.hits, stats.misses
);
}
#[test]
fn test_path_expansion_security_with_unsafe_variables() {
GLOBAL_ENV_CACHE.clear();
let test_path = "${PATH}/some/binary";
let result = expand_path_internal(test_path);
match result {
Ok(_) => {} Err(e) => {
println!("Error in path expansion: {e}");
if e.to_string().contains("security violation")
|| e.to_string().contains("Security violation")
{
println!(
"Path expansion correctly rejected unsafe variable (expected behavior)"
);
let stats = GLOBAL_ENV_CACHE.stats();
println!(
"Cache stats for rejected unsafe variable: hits={}, misses={}",
stats.hits, stats.misses
);
return; } else {
panic!("Unexpected error in path expansion: {e}");
}
}
}
let expanded = result.unwrap();
let path_str = expanded.to_string_lossy();
assert!(
path_str.contains("${PATH}") || path_str == "/some/binary",
"Unsafe variable should not be expanded: {path_str}"
);
let stats = GLOBAL_ENV_CACHE.stats();
println!(
"Unsafe variable expansion cache stats: hits={}, misses={}",
stats.hits, stats.misses
);
}
#[test]
fn test_cache_performance_improvement() {
let cache_config = EnvCacheConfig {
ttl: Duration::from_secs(10),
enabled: true,
max_entries: 10,
};
let cache = EnvironmentCache::with_config(cache_config);
let _ = cache.get_env_var("HOME");
let _ = cache.get_env_var("USER");
let mut total_cached_time = Duration::new(0, 0);
let iterations = 100;
for _ in 0..iterations {
let start = Instant::now();
let _ = cache.get_env_var("HOME");
let _ = cache.get_env_var("USER");
total_cached_time += start.elapsed();
}
let avg_cached_time = total_cached_time / iterations;
println!("Average cached access time: {avg_cached_time:?}");
let stats = cache.stats();
println!(
"Performance test cache stats: hits={}, misses={}, hit_rate={:.2}%",
stats.hits,
stats.misses,
stats.hit_rate() * 100.0
);
assert!(stats.hit_rate() > 0.8, "Cache hit rate should be > 80%");
}
#[test]
fn test_cache_with_nonexistent_variable() {
let cache = EnvironmentCache::new();
let result = cache.get_env_var("USER_NONEXISTENT_12345");
assert!(result.is_ok());
assert_eq!(result.unwrap(), None);
let result2 = cache.get_env_var("USER_NONEXISTENT_12345");
assert!(result2.is_ok());
assert_eq!(result2.unwrap(), None);
let stats = cache.stats();
if stats.hits + stats.misses == 0 {
let _result = cache.get_env_var("HOME");
let updated_stats = cache.stats();
assert!(
updated_stats.hits + updated_stats.misses > 0,
"Cache should have been accessed for safe variables"
);
} else {
assert!(
stats.hits + stats.misses > 0,
"Cache should have been accessed"
);
}
}
#[test]
fn test_cache_ttl_behavior() {
let config = EnvCacheConfig {
ttl: Duration::from_millis(50),
enabled: true,
max_entries: 10,
};
let cache = EnvironmentCache::with_config(config);
let test_var = "BSSH_TEST_CACHE_VAR";
std::env::set_var(test_var, "test_value_12345");
let var_to_test = "USER";
let result1 = cache.get_env_var(var_to_test);
assert!(result1.is_ok());
let initial_stats = cache.stats();
let result2 = cache.get_env_var(var_to_test);
assert!(result2.is_ok());
assert_eq!(result1.as_ref().unwrap(), result2.as_ref().unwrap());
let stats_after_hit = cache.stats();
assert!(
stats_after_hit.hits > initial_stats.hits,
"Should have cache hits"
);
std::thread::sleep(Duration::from_millis(100));
let result3 = cache.get_env_var(var_to_test);
assert!(result3.is_ok());
let final_stats = cache.stats();
assert!(final_stats.ttl_evictions > 0, "Should have TTL evictions");
std::env::remove_var(test_var);
}
}