use futures_util::{SinkExt, StreamExt}; use circles_launcher::{new_launcher, setup_multi_circle_server, shutdown_circles, Args, CircleConfig}; use secp256k1::Secp256k1; use tokio_tungstenite::connect_async; use url::Url; use std::str::FromStr; #[tokio::test] async fn test_launcher_builder_pattern() { // Test the new builder pattern API let secp = Secp256k1::new(); let (secret_key, public_key) = secp.generate_keypair(&mut secp256k1::rand::thread_rng()); let public_key_str = public_key.to_string(); // Use the builder pattern to create a launcher let launcher = new_launcher() .add_circle(&public_key_str) .port(8088) .redis_url("redis://127.0.0.1:6379") .worker_binary("../target/debug/worker") // Use debug for tests .enable_auth(false); // Note: We can't easily test the full launch in unit tests since it requires // actual binaries and Redis. This test verifies the builder pattern works. // Verify the builder created the launcher correctly // (This is more of a compilation test than a runtime test) assert!(true, "Builder pattern works correctly"); } #[tokio::test] async fn test_circle_config_parsing() { // Test parsing circle configurations from strings let public_key_only = "02a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890"; let config = CircleConfig::from_str(public_key_only).expect("Failed to parse public key only"); assert_eq!(config.public_key, public_key_only); assert!(config.init_script.is_none()); // Test with init script let with_script = "02a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890:init.rhai"; let config = CircleConfig::from_str(with_script).expect("Failed to parse with script"); assert_eq!(config.public_key, public_key_only); assert_eq!(config.init_script, Some("init.rhai".to_string())); // Test invalid format let invalid = "invalid:too:many:colons"; let result = CircleConfig::from_str(invalid); assert!(result.is_err(), "Should fail with invalid format"); } #[tokio::test] async fn test_args_structure() { // Test that Args structure works correctly with the new API let secp = Secp256k1::new(); let (_, public_key) = secp.generate_keypair(&mut secp256k1::rand::thread_rng()); let public_key_str = public_key.to_string(); let args = Args { port: 8089, circles: vec![public_key_str.clone()], redis_url: "redis://127.0.0.1:6379".to_string(), enable_auth: false, worker_binary: Some("../target/debug/worker".to_string()), debug: true, verbose: 1, }; // Verify args structure assert_eq!(args.port, 8089); assert_eq!(args.circles.len(), 1); assert_eq!(args.circles[0], public_key_str); assert!(!args.enable_auth); assert!(args.worker_binary.is_some()); } #[tokio::test] async fn test_setup_multi_circle_server_validation() { // Test validation in setup_multi_circle_server let args = Args { port: 8090, circles: vec![], // Empty circles should cause error redis_url: "redis://127.0.0.1:6379".to_string(), enable_auth: false, worker_binary: None, // Missing worker binary should cause error debug: true, verbose: 0, }; // This should fail due to missing worker binary let result = setup_multi_circle_server(&args).await; assert!(result.is_err(), "Should fail with missing worker binary"); if let Err(e) = result { let error_msg = e.to_string(); assert!( error_msg.contains("Worker binary path is required"), "Error should mention missing worker binary, got: {}", error_msg ); } } #[tokio::test] async fn test_circle_config_validation() { // Test that invalid public keys are rejected let invalid_key = "not_a_valid_public_key"; let result = CircleConfig::from_str(invalid_key); assert!(result.is_err(), "Should reject invalid public key"); // Test valid public key format let valid_key = "02a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890"; let result = CircleConfig::from_str(valid_key); assert!(result.is_ok(), "Should accept valid public key"); } #[tokio::test] async fn test_launcher_cleanup_functionality() { // Test that cleanup functionality exists and can be called // Note: This doesn't test actual cleanup since we don't have running services use circles_launcher::cleanup_launcher; // This should not panic and should handle the case where no services exist let result = cleanup_launcher().await; // It's OK if this fails due to no services - we're just testing the API exists let _ = result; assert!(true, "Cleanup function exists and can be called"); } // Integration test that would require actual binaries and Redis // Commented out since it requires external dependencies /* #[tokio::test] #[ignore] // Use `cargo test -- --ignored` to run this test async fn test_full_launcher_integration() { // This test requires: // 1. Redis server running on localhost:6379 // 2. Worker binary built at ../target/debug/worker // 3. WebSocket server binary built at ../target/debug/circles_server let secp = Secp256k1::new(); let (_, public_key) = secp.generate_keypair(&mut secp256k1::rand::thread_rng()); let public_key_str = public_key.to_string(); let args = Args { port: 8091, circles: vec![public_key_str.clone()], redis_url: "redis://127.0.0.1:6379".to_string(), enable_auth: false, worker_binary: Some("../target/debug/worker".to_string()), debug: true, verbose: 1, }; // Setup the multi-circle server let result = setup_multi_circle_server(&args).await; assert!(result.is_ok(), "Failed to setup multi-circle server: {:?}", result.err()); let (running_circles, outputs) = result.unwrap(); // Verify outputs assert_eq!(outputs.len(), 1); assert_eq!(outputs[0].public_key, public_key_str); // Test WebSocket connection let ws_url = &outputs[0].ws_url; let connection_result = connect_async(ws_url).await; assert!(connection_result.is_ok(), "Failed to connect to WebSocket"); // Cleanup shutdown_circles(running_circles).await; } */