// 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"); } } } }