capabable spawning multiple openvpn instances
This commit is contained in:
379
test/vpn_integration_tests.rs
Normal file
379
test/vpn_integration_tests.rs
Normal file
@@ -0,0 +1,379 @@
|
||||
// tests/vpn_integration_tests.rs
|
||||
//! Integration tests for VPN rotation system
|
||||
|
||||
#[cfg(test)]
|
||||
mod vpn_tests {
|
||||
use event_backtest_engine::{
|
||||
scraper::{
|
||||
webdriver::ChromeDriverPool,
|
||||
vpn_manager::{VpnInstance, VpnPool},
|
||||
},
|
||||
util::{directories::DataPaths, opnv},
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Helper to create a test VPN instance without connecting
|
||||
fn create_test_vpn_instance() -> VpnInstance {
|
||||
VpnInstance::new(
|
||||
PathBuf::from("test.ovpn"),
|
||||
"testuser".to_string(),
|
||||
"testpass".to_string(),
|
||||
)
|
||||
.expect("Failed to create test VPN instance")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vpn_instance_creation() {
|
||||
let vpn = create_test_vpn_instance();
|
||||
assert_eq!(vpn.hostname(), "test");
|
||||
assert!(!vpn.is_healthy());
|
||||
assert!(vpn.external_ip().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vpn_task_counting() {
|
||||
let mut vpn = create_test_vpn_instance();
|
||||
|
||||
// Should not rotate initially
|
||||
assert!(!vpn.increment_task_count(10));
|
||||
|
||||
// Increment tasks
|
||||
for i in 1..10 {
|
||||
assert!(!vpn.increment_task_count(10), "Should not rotate at task {}", i);
|
||||
}
|
||||
|
||||
// Should rotate at threshold
|
||||
assert!(vpn.increment_task_count(10), "Should rotate at task 10");
|
||||
|
||||
// Reset and verify
|
||||
vpn.reset_task_count();
|
||||
assert!(!vpn.increment_task_count(10), "Should not rotate after reset");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vpn_task_counting_zero_threshold() {
|
||||
let mut vpn = create_test_vpn_instance();
|
||||
|
||||
// With threshold=0, should never auto-rotate
|
||||
for _ in 0..100 {
|
||||
assert!(!vpn.increment_task_count(0));
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_chromedriver_pool_creation_no_vpn() {
|
||||
let result = ChromeDriverPool::new(2).await;
|
||||
|
||||
match result {
|
||||
Ok(pool) => {
|
||||
assert_eq!(pool.get_number_of_instances(), 2);
|
||||
assert!(!pool.is_vpn_enabled());
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("ChromeDriver pool creation failed (expected if chromedriver not installed): {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_data_paths_creation() {
|
||||
let paths = DataPaths::new("./test_data").expect("Failed to create paths");
|
||||
|
||||
assert!(paths.data_dir().exists());
|
||||
assert!(paths.cache_dir().exists());
|
||||
assert!(paths.logs_dir().exists());
|
||||
assert!(paths.cache_openvpn_dir().exists());
|
||||
|
||||
// Cleanup
|
||||
let _ = std::fs::remove_dir_all("./test_data");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // This test requires actual network access and VPNBook availability
|
||||
async fn test_fetch_vpnbook_configs() {
|
||||
let paths = DataPaths::new(".").expect("Failed to create paths");
|
||||
|
||||
// This test requires a ChromeDriver pool
|
||||
let pool_result = ChromeDriverPool::new(1).await;
|
||||
if pool_result.is_err() {
|
||||
eprintln!("Skipping VPNBook fetch test: ChromeDriver not available");
|
||||
return;
|
||||
}
|
||||
|
||||
let pool = Arc::new(pool_result.unwrap());
|
||||
|
||||
let result = opnv::fetch_vpnbook_configs(&pool, paths.cache_dir()).await;
|
||||
|
||||
match result {
|
||||
Ok((username, password, files)) => {
|
||||
assert!(!username.is_empty(), "Username should not be empty");
|
||||
assert!(!password.is_empty(), "Password should not be empty");
|
||||
assert!(!files.is_empty(), "Should fetch at least one config file");
|
||||
|
||||
println!("Fetched {} VPN configs", files.len());
|
||||
for file in &files {
|
||||
assert!(file.exists(), "Config file should exist: {:?}", file);
|
||||
assert_eq!(file.extension().and_then(|s| s.to_str()), Some("ovpn"));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("VPNBook fetch failed (may be temporary): {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // Requires actual VPN configs and OpenVPN installation
|
||||
async fn test_vpn_pool_creation() {
|
||||
let paths = DataPaths::new(".").expect("Failed to create paths");
|
||||
|
||||
// First fetch configs
|
||||
let pool_result = ChromeDriverPool::new(1).await;
|
||||
if pool_result.is_err() {
|
||||
eprintln!("Skipping VPN pool test: ChromeDriver not available");
|
||||
return;
|
||||
}
|
||||
|
||||
let temp_pool = Arc::new(pool_result.unwrap());
|
||||
let fetch_result = opnv::fetch_vpnbook_configs(&temp_pool, paths.cache_dir()).await;
|
||||
|
||||
if fetch_result.is_err() {
|
||||
eprintln!("Skipping VPN pool test: Could not fetch configs");
|
||||
return;
|
||||
}
|
||||
|
||||
let (username, password, _) = fetch_result.unwrap();
|
||||
|
||||
// Create VPN pool
|
||||
let vpn_pool_result = VpnPool::new(
|
||||
paths.cache_openvpn_dir(),
|
||||
username,
|
||||
password,
|
||||
false,
|
||||
0,
|
||||
).await;
|
||||
|
||||
match vpn_pool_result {
|
||||
Ok(vpn_pool) => {
|
||||
assert!(vpn_pool.len() > 0, "VPN pool should have at least one instance");
|
||||
println!("Created VPN pool with {} instances", vpn_pool.len());
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("VPN pool creation failed: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // Full integration test - requires all components
|
||||
async fn test_full_vpn_integration() {
|
||||
let paths = DataPaths::new(".").expect("Failed to create paths");
|
||||
|
||||
// Step 1: Create temp ChromeDriver pool for fetching
|
||||
let temp_pool = match ChromeDriverPool::new(1).await {
|
||||
Ok(p) => Arc::new(p),
|
||||
Err(e) => {
|
||||
eprintln!("Skipping integration test: ChromeDriver not available - {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Step 2: Fetch VPNBook configs
|
||||
let (username, password, files) = match opnv::fetch_vpnbook_configs(
|
||||
&temp_pool,
|
||||
paths.cache_dir()
|
||||
).await {
|
||||
Ok(result) => result,
|
||||
Err(e) => {
|
||||
eprintln!("Skipping integration test: Config fetch failed - {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
assert!(!files.is_empty(), "Should have fetched configs");
|
||||
|
||||
// Step 3: Create VPN pool
|
||||
let vpn_pool = match VpnPool::new(
|
||||
paths.cache_openvpn_dir(),
|
||||
username,
|
||||
password,
|
||||
true,
|
||||
5,
|
||||
).await {
|
||||
Ok(pool) => Arc::new(pool),
|
||||
Err(e) => {
|
||||
eprintln!("Skipping integration test: VPN pool creation failed - {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Step 4: Connect one VPN
|
||||
let vpn_instance = vpn_pool.acquire().await.expect("Failed to acquire VPN");
|
||||
let connect_result = {
|
||||
let mut vpn = vpn_instance.lock().await;
|
||||
vpn.connect().await
|
||||
};
|
||||
|
||||
match connect_result {
|
||||
Ok(_) => {
|
||||
let vpn = vpn_instance.lock().await;
|
||||
println!("✓ VPN connected: {} ({})",
|
||||
vpn.hostname(),
|
||||
vpn.external_ip().unwrap_or("unknown")
|
||||
);
|
||||
assert!(vpn.is_healthy());
|
||||
assert!(vpn.external_ip().is_some());
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("VPN connection failed: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Create ChromeDriver pool with VPN
|
||||
let driver_pool_result = ChromeDriverPool::new_with_vpn(
|
||||
1,
|
||||
Some(vpn_pool.clone())
|
||||
).await;
|
||||
|
||||
match driver_pool_result {
|
||||
Ok(driver_pool) => {
|
||||
assert!(driver_pool.is_vpn_enabled());
|
||||
println!("✓ ChromeDriver pool created with VPN binding");
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("ChromeDriver pool creation failed: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 6: Cleanup
|
||||
vpn_pool.disconnect_all().await.expect("Failed to disconnect VPNs");
|
||||
println!("✓ Integration test complete");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hostname_extraction() {
|
||||
// Test the hostname extraction logic
|
||||
let test_cases = vec![
|
||||
("test/ca149.vpnbook.com/config.ovpn", "ca149.vpnbook.com"),
|
||||
("test/us1.vpnbook.com/config.ovpn", "us1.vpnbook.com"),
|
||||
("test/de4.vpnbook.com/config.ovpn", "de4.vpnbook.com"),
|
||||
];
|
||||
|
||||
for (path, expected_hostname) in test_cases {
|
||||
let pb = PathBuf::from(path);
|
||||
let hostname = pb.parent()
|
||||
.and_then(|p| p.file_name())
|
||||
.and_then(|n| n.to_str())
|
||||
.unwrap_or("unknown");
|
||||
|
||||
assert_eq!(hostname, expected_hostname);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[test]
|
||||
fn test_forcebindip_manager_creation() {
|
||||
use event_backtest_engine::ForceBindIpManager;
|
||||
|
||||
match ForceBindIpManager::new() {
|
||||
Ok(manager) => {
|
||||
println!("✓ ForceBindIP found at: {:?}", manager.path());
|
||||
assert!(manager.path().exists());
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("ForceBindIP not found (expected in dev): {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[test]
|
||||
fn test_forcebindip_command_creation() {
|
||||
use event_backtest_engine::ForceBindIpManager;
|
||||
use std::path::Path;
|
||||
|
||||
if let Ok(manager) = ForceBindIpManager::new() {
|
||||
let cmd = manager.create_bound_command(
|
||||
"192.168.1.100",
|
||||
Path::new("test.exe"),
|
||||
&["--arg1", "value1"],
|
||||
);
|
||||
|
||||
let cmd_str = format!("{:?}", cmd);
|
||||
assert!(cmd_str.contains("192.168.1.100"));
|
||||
assert!(cmd_str.contains("test.exe"));
|
||||
println!("✓ ForceBindIP command created successfully");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_defaults() {
|
||||
use event_backtest_engine::Config;
|
||||
|
||||
let config = Config::default();
|
||||
assert_eq!(config.economic_start_date, "2007-02-13");
|
||||
assert_eq!(config.corporate_start_date, "2010-01-01");
|
||||
assert_eq!(config.economic_lookahead_months, 3);
|
||||
assert_eq!(config.max_parallel_instances, 10);
|
||||
assert!(!config.enable_vpn_rotation);
|
||||
assert_eq!(config.tasks_per_vpn_session, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod benchmark_tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // Performance test
|
||||
async fn benchmark_vpn_rotation_overhead() {
|
||||
use std::time::Instant;
|
||||
|
||||
// This test measures the overhead of VPN rotation
|
||||
let start = Instant::now();
|
||||
|
||||
// Simulate rotation cycle
|
||||
// 1. Disconnect (instant)
|
||||
// 2. Wait 2 seconds
|
||||
// 3. Connect (5-10 seconds)
|
||||
// 4. Verify IP (1-2 seconds)
|
||||
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
println!("Rotation cycle took: {:?}", elapsed);
|
||||
|
||||
// Typical rotation should complete in under 15 seconds
|
||||
assert!(elapsed.as_secs() < 15);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // Performance test
|
||||
async fn benchmark_parallel_scraping() {
|
||||
// This test measures throughput with different parallelism levels
|
||||
// Results help tune MAX_PARALLEL_INSTANCES
|
||||
|
||||
let configs = vec![1, 2, 3, 5, 10];
|
||||
|
||||
for &pool_size in &configs {
|
||||
println!("Testing with {} parallel instances...", pool_size);
|
||||
|
||||
// Would need actual scraping implementation here
|
||||
// For now, just verify pool creation time
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
let pool_result = event_backtest_engine::ChromeDriverPool::new(pool_size).await;
|
||||
|
||||
if let Ok(_pool) = pool_result {
|
||||
let elapsed = start.elapsed();
|
||||
println!(" Pool initialization: {:?}", elapsed);
|
||||
|
||||
// Pool creation should be fast (< 5 seconds per instance)
|
||||
assert!(elapsed.as_secs() < pool_size as u64 * 5);
|
||||
} else {
|
||||
eprintln!(" Skipped - ChromeDriver not available");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user