use sal_zinit_client::{ create_service, delete_service, forget, get_service, kill, list, logs, monitor, restart, start, status, stop, }; use std::path::Path; use tokio::time::{sleep, Duration}; /// Helper function to check if a zinit socket is available async fn get_available_socket_path() -> Option { let common_paths = vec![ "/var/run/zinit.sock", "/tmp/zinit.sock", "/run/zinit.sock", "./zinit.sock", ]; for path in common_paths { if Path::new(path).exists() { // Try to connect and list services to verify it's working match list(path).await { Ok(_) => { println!("✓ Found working Zinit socket at: {}", path); return Some(path.to_string()); } Err(e) => { println!("⚠ Socket exists at {} but connection failed: {}", path, e); } } } } println!("⚠ No working Zinit socket found. Tests will be skipped."); None } #[tokio::test] async fn test_list_services() { if let Some(socket_path) = get_available_socket_path().await { let result = list(&socket_path).await; match result { Ok(services) => { println!("✓ Successfully listed {} services", services.len()); // Verify the result is a proper HashMap with valid structure // Verify all service names are non-empty strings and states are valid for (name, state) in &services { assert!(!name.is_empty(), "Service name should not be empty"); assert!(!state.is_empty(), "Service state should not be empty"); } // Print some services for debugging for (name, state) in services.iter().take(3) { println!(" Service: {} -> {}", name, state); } } Err(e) => { println!("⚠ List services failed: {}", e); // Don't fail the test - zinit might not have any services } } } else { println!("⚠ Skipping test_list_services: No Zinit socket available"); } } #[tokio::test] async fn test_service_lifecycle() { if let Some(socket_path) = get_available_socket_path().await { let service_name = "test-service-lifecycle"; let exec_command = "echo 'Hello from test service'"; let oneshot = true; // Clean up any existing service first let _ = stop(&socket_path, service_name).await; let _ = forget(&socket_path, service_name).await; let _ = delete_service(&socket_path, service_name).await; // Test service creation println!("Creating test service: {}", service_name); let create_result = create_service(&socket_path, service_name, exec_command, oneshot).await; match create_result { Ok(_) => { println!("✓ Service created successfully"); // Test service monitoring println!("Monitoring service: {}", service_name); let monitor_result = monitor(&socket_path, service_name).await; match monitor_result { Ok(_) => println!("✓ Service monitoring started"), Err(e) => println!("⚠ Monitor failed: {}", e), } // Test service start println!("Starting service: {}", service_name); let start_result = start(&socket_path, service_name).await; match start_result { Ok(_) => { println!("✓ Service started successfully"); // Wait a bit for the service to run sleep(Duration::from_millis(500)).await; // Test service status println!("Getting service status: {}", service_name); let status_result = status(&socket_path, service_name).await; match status_result { Ok(service_status) => { println!("✓ Service status: {:?}", service_status.state); assert!(!service_status.name.is_empty()); } Err(e) => println!("⚠ Status check failed: {}", e), } } Err(e) => println!("⚠ Start failed: {}", e), } // Test service stop println!("Stopping service: {}", service_name); let stop_result = stop(&socket_path, service_name).await; match stop_result { Ok(_) => println!("✓ Service stopped successfully"), Err(e) => println!("⚠ Stop failed: {}", e), } // Test forget (stop monitoring) println!("Forgetting service: {}", service_name); let forget_result = forget(&socket_path, service_name).await; match forget_result { Ok(_) => println!("✓ Service forgotten successfully"), Err(e) => println!("⚠ Forget failed: {}", e), } // Test service deletion println!("Deleting service: {}", service_name); let delete_result = delete_service(&socket_path, service_name).await; match delete_result { Ok(_) => println!("✓ Service deleted successfully"), Err(e) => println!("⚠ Delete failed: {}", e), } } Err(e) => { println!("⚠ Service creation failed: {}", e); // This might be expected if zinit doesn't allow service creation } } } else { println!("⚠ Skipping test_service_lifecycle: No Zinit socket available"); } } #[tokio::test] async fn test_get_service_configuration() { if let Some(socket_path) = get_available_socket_path().await { // First, list services to find an existing one let services_result = list(&socket_path).await; match services_result { Ok(services) => { if let Some((service_name, _)) = services.iter().next() { println!("Testing get_service for: {}", service_name); let config_result = get_service(&socket_path, service_name).await; match config_result { Ok(config) => { println!("✓ Service configuration retrieved successfully"); println!(" Config: {:?}", config); // Verify it's a valid JSON value assert!(config.is_object() || config.is_string() || config.is_null()); } Err(e) => { println!("⚠ Get service config failed: {}", e); } } } else { println!("⚠ No services available to test get_service"); } } Err(e) => { println!("⚠ Could not list services for get_service test: {}", e); } } } else { println!("⚠ Skipping test_get_service_configuration: No Zinit socket available"); } } #[tokio::test] async fn test_logs_functionality() { if let Some(socket_path) = get_available_socket_path().await { println!("Testing logs functionality"); // Test getting all logs let logs_result = logs(&socket_path, None).await; match logs_result { Ok(log_entries) => { println!("✓ Retrieved {} log entries", log_entries.len()); // Print first few log entries for verification for (i, log_entry) in log_entries.iter().take(3).enumerate() { println!(" Log {}: {}", i + 1, log_entry); } // Verify logs are valid strings - if we got them, they should be properly formatted for log_entry in log_entries.iter().take(5) { // Verify it's a valid string (String type guarantees valid UTF-8) // and check it doesn't contain null bytes which would indicate corruption assert!( !log_entry.contains('\0'), "Log entry should not contain null bytes" ); } } Err(e) => { println!("⚠ Logs retrieval failed: {}", e); // This might be expected if no logs are available } } // Test getting logs with a filter let filtered_logs_result = logs(&socket_path, Some("zinit".to_string())).await; match filtered_logs_result { Ok(filtered_logs) => { println!("✓ Retrieved {} filtered log entries", filtered_logs.len()); } Err(e) => { println!("⚠ Filtered logs retrieval failed: {}", e); } } } else { println!("⚠ Skipping test_logs_functionality: No Zinit socket available"); } } #[tokio::test] async fn test_kill_signal_functionality() { if let Some(socket_path) = get_available_socket_path().await { let service_name = "test-kill-service"; let exec_command = "sleep 30"; // Long-running command let oneshot = false; // Clean up any existing service first let _ = stop(&socket_path, service_name).await; let _ = forget(&socket_path, service_name).await; let _ = delete_service(&socket_path, service_name).await; // Create and start a service for testing kill let create_result = create_service(&socket_path, service_name, exec_command, oneshot).await; if create_result.is_ok() { let _ = monitor(&socket_path, service_name).await; let start_result = start(&socket_path, service_name).await; if start_result.is_ok() { // Wait for service to start sleep(Duration::from_millis(1000)).await; // Test kill with TERM signal println!("Testing kill with TERM signal"); let kill_result = kill(&socket_path, service_name, Some("TERM")).await; match kill_result { Ok(_) => { println!("✓ Kill signal sent successfully"); // Wait a bit and check if service stopped sleep(Duration::from_millis(500)).await; let status_result = status(&socket_path, service_name).await; match status_result { Ok(service_status) => { println!(" Service state after kill: {:?}", service_status.state); } Err(e) => println!(" Status check after kill failed: {}", e), } } Err(e) => { println!("⚠ Kill signal failed: {}", e); } } } // Clean up let _ = stop(&socket_path, service_name).await; let _ = forget(&socket_path, service_name).await; let _ = delete_service(&socket_path, service_name).await; } else { println!("⚠ Could not create test service for kill test"); } } else { println!("⚠ Skipping test_kill_signal_functionality: No Zinit socket available"); } } #[tokio::test] async fn test_restart_functionality() { if let Some(socket_path) = get_available_socket_path().await { let service_name = "test-restart-service"; let exec_command = "echo 'Restart test'"; let oneshot = true; // Clean up any existing service first let _ = stop(&socket_path, service_name).await; let _ = forget(&socket_path, service_name).await; let _ = delete_service(&socket_path, service_name).await; // Create and start a service for testing restart let create_result = create_service(&socket_path, service_name, exec_command, oneshot).await; if create_result.is_ok() { let _ = monitor(&socket_path, service_name).await; let start_result = start(&socket_path, service_name).await; if start_result.is_ok() { // Wait for service to complete (it's oneshot) sleep(Duration::from_millis(1000)).await; // Test restart println!("Testing service restart"); let restart_result = restart(&socket_path, service_name).await; match restart_result { Ok(_) => { println!("✓ Service restarted successfully"); // Wait and check status sleep(Duration::from_millis(500)).await; let status_result = status(&socket_path, service_name).await; match status_result { Ok(service_status) => { println!( " Service state after restart: {:?}", service_status.state ); } Err(e) => println!(" Status check after restart failed: {}", e), } } Err(e) => { println!("⚠ Restart failed: {}", e); } } } // Clean up let _ = stop(&socket_path, service_name).await; let _ = forget(&socket_path, service_name).await; let _ = delete_service(&socket_path, service_name).await; } else { println!("⚠ Could not create test service for restart test"); } } else { println!("⚠ Skipping test_restart_functionality: No Zinit socket available"); } } #[tokio::test] async fn test_error_handling() { if let Some(socket_path) = get_available_socket_path().await { // Test operations on non-existent service let non_existent_service = "non-existent-service-12345"; println!("Testing error handling with non-existent service"); // Test status of non-existent service let status_result = status(&socket_path, non_existent_service).await; match status_result { Ok(_) => println!("⚠ Unexpected success for non-existent service status"), Err(e) => { println!("✓ Correctly failed for non-existent service status: {}", e); assert!(!e.to_string().is_empty()); } } // Test stop of non-existent service let stop_result = stop(&socket_path, non_existent_service).await; match stop_result { Ok(_) => println!("⚠ Unexpected success for non-existent service stop"), Err(e) => { println!("✓ Correctly failed for non-existent service stop: {}", e); } } } else { println!("⚠ Skipping test_error_handling: No Zinit socket available"); } } #[tokio::test] async fn test_invalid_socket_path() { let invalid_socket = "/invalid/path/to/zinit.sock"; println!("Testing with invalid socket path: {}", invalid_socket); let result = list(invalid_socket).await; match result { Ok(_) => { println!("⚠ Unexpected success with invalid socket path"); } Err(e) => { println!("✓ Correctly failed with invalid socket: {}", e); assert!(!e.to_string().is_empty()); } } }