sal/zinit_client/tests/zinit_client_tests.rs
Mahmoud-Emad 511729c477
Some checks are pending
Rhai Tests / Run Rhai Tests (push) Waiting to run
feat: Add zinit_client package to workspace
- Add `zinit_client` package to the workspace, enabling its use
  in the SAL monorepo.  This allows for better organization and
  dependency management.
- Update `MONOREPO_CONVERSION_PLAN.md` to reflect the addition
  of `zinit_client` and its status.  This ensures the conversion
  plan stays up-to-date.
- Move `src/zinit_client/` directory to `zinit_client/` for better
   organization.  This improves the overall structure of the
   project.
- Update references to `zinit_client` to use the new path.  This
  ensures the codebase correctly links to the `zinit_client`
  package.
2025-06-22 10:59:19 +03:00

406 lines
16 KiB
Rust

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<String> {
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());
}
}
}