Some checks are pending
Rhai Tests / Run Rhai Tests (push) Waiting to run
- 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.
406 lines
16 KiB
Rust
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());
|
|
}
|
|
}
|
|
}
|