//! Core pairing test using the new cargo test subprocess framework //! //! This test demonstrates the new approach where ALL test logic remains in the test file //! while still supporting subprocess-based testing for multi-device scenarios. use sd_core::testing::CargoTestRunner; use sd_core::Core; use std::env; use std::path::PathBuf; use std::time::Duration; use tokio::time::timeout; /// Alice's pairing scenario - ALL logic stays in this test file! #[tokio::test] #[ignore] // Only run when explicitly called via subprocess async fn alice_pairing_scenario() { // Exit early if not running as Alice if env::var("TEST_ROLE").unwrap_or_default() != "alice" { return; } // Set test directory for file-based discovery env::set_var("SPACEDRIVE_TEST_DIR", "/tmp/spacedrive-pairing-test"); let data_dir = PathBuf::from("/tmp/spacedrive-pairing-test/alice"); let device_name = "Alice's Test Device"; println!("Alice: Starting Core pairing test"); println!("Alice: Data dir: {:?}", data_dir); // Initialize Core println!("Alice: Initializing Core..."); let mut core = timeout(Duration::from_secs(10), Core::new(data_dir)) .await .unwrap() .unwrap(); println!("Alice: Core initialized successfully"); // Set device name println!("Alice: Setting device name for testing..."); core.device.set_name(device_name.to_string()).unwrap(); // Initialize networking println!("Alice: Initializing networking..."); timeout(Duration::from_secs(10), core.init_networking()) .await .unwrap() .unwrap(); // Wait longer for networking to fully initialize and detect external addresses tokio::time::sleep(Duration::from_secs(3)).await; println!("Alice: Networking initialized successfully"); // Start pairing as initiator println!("Alice: Starting pairing as initiator..."); let (pairing_code, expires_in) = if let Some(networking) = core.networking() { timeout( Duration::from_secs(15), networking.start_pairing_as_initiator(false), ) .await .unwrap() .unwrap() } else { panic!("Networking not initialized"); }; let short_code = pairing_code .split_whitespace() .take(3) .collect::>() .join(" "); println!( "Alice: Pairing code generated: {}... (expires in {}s)", short_code, expires_in ); // Write pairing code to shared location for Bob to read std::fs::create_dir_all("/tmp/spacedrive-pairing-test").unwrap(); std::fs::write( "/tmp/spacedrive-pairing-test/pairing_code.txt", &pairing_code, ) .unwrap(); println!("Alice: Pairing code written to /tmp/spacedrive-pairing-test/pairing_code.txt"); // Wait for pairing completion (Alice waits for Bob to connect) println!("Alice: Waiting for pairing to complete..."); let mut attempts = 0; let max_attempts = 45; // 45 seconds loop { tokio::time::sleep(Duration::from_secs(1)).await; let connected_devices = core.services.device.get_connected_devices().await.unwrap(); if !connected_devices.is_empty() { println!("Alice: Pairing completed successfully!"); println!("Alice: Checking connected devices..."); println!("Alice: Connected {} devices", connected_devices.len()); // Get detailed device info let device_info = core .services .device .get_connected_devices_info() .await .unwrap(); for device in &device_info { println!( "Alice sees: {} (ID: {}, OS: {}, App: {})", device.device_name, device.device_id, device.os_version, device.app_version ); } println!("PAIRING_SUCCESS: Alice's Test Device connected to Bob successfully"); // Write success marker for orchestrator to detect std::fs::write("/tmp/spacedrive-pairing-test/alice_success.txt", "success").unwrap(); // Wait a bit longer to give Bob time to detect the connection before Alice exits println!("Alice: Waiting for Bob to also detect the connection..."); tokio::time::sleep(Duration::from_secs(5)).await; break; } attempts += 1; if attempts >= max_attempts { panic!("Alice: Pairing timeout - no devices connected"); } if attempts % 5 == 0 { println!("Alice: Pairing status check {} - waiting", attempts / 5); } } println!("Alice: Test completed"); } /// Bob's pairing scenario - ALL logic stays in this test file! #[tokio::test] #[ignore] // Only run when explicitly called via subprocess async fn bob_pairing_scenario() { // Exit early if not running as Bob if env::var("TEST_ROLE").unwrap_or_default() != "bob" { return; } // Set test directory for file-based discovery env::set_var("SPACEDRIVE_TEST_DIR", "/tmp/spacedrive-pairing-test"); let data_dir = PathBuf::from("/tmp/spacedrive-pairing-test/bob"); let device_name = "Bob's Test Device"; println!("Bob: Starting Core pairing test"); println!("Bob: Data dir: {:?}", data_dir); // Initialize Core println!("Bob: Initializing Core..."); let mut core = timeout(Duration::from_secs(10), Core::new(data_dir)) .await .unwrap() .unwrap(); println!("Bob: Core initialized successfully"); // Set device name println!("Bob: Setting device name for testing..."); core.device.set_name(device_name.to_string()).unwrap(); // Initialize networking println!("Bob: Initializing networking..."); timeout(Duration::from_secs(10), core.init_networking()) .await .unwrap() .unwrap(); // Wait longer for networking to fully initialize and detect external addresses tokio::time::sleep(Duration::from_secs(3)).await; println!("Bob: Networking initialized successfully"); // Wait for initiator to create pairing code println!("Bob: Looking for pairing code..."); let pairing_code = loop { if let Ok(code) = std::fs::read_to_string("/tmp/spacedrive-pairing-test/pairing_code.txt") { break code.trim().to_string(); } tokio::time::sleep(Duration::from_millis(500)).await; }; println!("Bob: Found pairing code"); // Join pairing session println!("Bob: Joining pairing with code..."); if let Some(networking) = core.networking() { timeout( Duration::from_secs(15), networking.start_pairing_as_joiner(&pairing_code, false), ) .await .unwrap() .unwrap(); } else { panic!("Networking not initialized"); } println!("Bob: Successfully joined pairing"); // Wait for pairing completion println!("Bob: Waiting for pairing to complete..."); let mut attempts = 0; let max_attempts = 30; // 30 seconds loop { tokio::time::sleep(Duration::from_secs(1)).await; // Check pairing status by looking at connected devices let connected_devices = core.services.device.get_connected_devices().await.unwrap(); if !connected_devices.is_empty() { println!("Bob: Pairing completed successfully!"); println!("Bob: Checking connected devices..."); println!("Bob: Connected {} devices", connected_devices.len()); // Get detailed device info let device_info = core .services .device .get_connected_devices_info() .await .unwrap(); for device in &device_info { println!( "Bob sees: {} (ID: {}, OS: {}, App: {})", device.device_name, device.device_id, device.os_version, device.app_version ); } println!("PAIRING_SUCCESS: Bob's Test Device connected to Alice successfully"); // Wait longer to allow persistent connection to be established via auto-reconnection // The pairing stream is temporary; we need to wait for Bob to reconnect println!("Bob: Waiting for persistent connection to be established..."); tokio::time::sleep(Duration::from_secs(10)).await; // Write success marker for orchestrator to detect std::fs::write("/tmp/spacedrive-pairing-test/bob_success.txt", "success").unwrap(); break; } attempts += 1; if attempts >= max_attempts { panic!("Bob: Pairing timeout - no devices connected"); } if attempts % 5 == 0 { println!("Bob: Pairing status check {} - waiting", attempts / 5); } } println!("Bob: Test completed"); } /// Main test orchestrator - spawns cargo test subprocesses #[tokio::test] async fn test_device_pairing() { const PAIRING_CODE_PATH: &str = "/tmp/spacedrive-pairing-test/pairing_code.txt"; // Clean up stale pairing code file from previous test runs // This prevents Bob from reading old data and fixes the file I/O race condition if std::path::Path::new(PAIRING_CODE_PATH).exists() { let _ = std::fs::remove_file(PAIRING_CODE_PATH); println!("Cleaned up stale pairing code file"); } println!("Testing Core pairing with cargo test subprocess framework"); // Clean up any old pairing files to avoid race conditions let _ = std::fs::remove_dir_all("/tmp/spacedrive-pairing-test"); std::fs::create_dir_all("/tmp/spacedrive-pairing-test").unwrap(); let mut runner = CargoTestRunner::for_test_file("device_pairing_test") .with_timeout(Duration::from_secs(180)) .add_subprocess("alice", "alice_pairing_scenario") .add_subprocess("bob", "bob_pairing_scenario"); // Spawn Alice first println!("Starting Alice as initiator..."); runner .spawn_single_process("alice") .await .expect("Failed to spawn Alice"); // Wait for Alice to initialize and generate pairing code tokio::time::sleep(Duration::from_secs(8)).await; // Start Bob as joiner println!("Starting Bob as joiner..."); runner .spawn_single_process("bob") .await .expect("Failed to spawn Bob"); // Run until both devices successfully pair using file markers let result = runner .wait_for_success(|_outputs| { let alice_success = std::fs::read_to_string("/tmp/spacedrive-pairing-test/alice_success.txt") .map(|content| content.trim() == "success") .unwrap_or(false); let bob_success = std::fs::read_to_string("/tmp/spacedrive-pairing-test/bob_success.txt") .map(|content| content.trim() == "success") .unwrap_or(false); alice_success && bob_success }) .await; match result { Ok(_) => { println!( "Cargo test subprocess pairing test successful with mutual device recognition!" ); } Err(e) => { println!("Cargo test subprocess pairing test failed: {}", e); for (name, output) in runner.get_all_outputs() { println!("\\n{} output:\\n{}", name, output); } panic!("Cargo test subprocess pairing test failed - devices did not properly recognize each other"); } } }