use serde_json::json;
use std::collections::HashMap;
use std::time::Duration;
use tempfile::tempdir;
use zinit_client::{ClientConfig, Result, ServiceState, StreamExt, ZinitClient};
mod mock_server;
use mock_server::{MockService, MockServiceState, MockServiceTarget, MockZinitServer};
#[tokio::test]
async fn test_client_list_services() -> Result<()> {
let temp_dir = tempdir().expect("Failed to create temp dir");
let socket_path = temp_dir.path().join("mock-zinit.sock");
let mut server = MockZinitServer::new(&socket_path).await;
server.start().await.expect("Failed to start mock server");
server.add_service(MockService {
name: "web-server".to_string(),
pid: 1001,
state: MockServiceState::Running,
target: MockServiceTarget::Up,
after: HashMap::new(),
});
server.add_service(MockService {
name: "database".to_string(),
pid: 1002,
state: MockServiceState::Running,
target: MockServiceTarget::Up,
after: HashMap::new(),
});
let client = ZinitClient::new(&socket_path);
let services = client.list().await?;
println!("Found {} services", services.len());
assert!(!services.is_empty(), "No services found");
assert_eq!(services.len(), 2, "Expected 2 services");
assert!(
services.contains_key("web-server"),
"Expected web-server service"
);
assert!(
services.contains_key("database"),
"Expected database service"
);
server.stop().await;
Ok(())
}
#[tokio::test]
async fn test_client_service_lifecycle() -> Result<()> {
let temp_dir = tempdir().expect("Failed to create temp dir");
let socket_path = temp_dir.path().join("mock-zinit.sock");
let mut server = MockZinitServer::new(&socket_path).await;
server.start().await.expect("Failed to start mock server");
let service_name = "test-service";
server.add_service(MockService {
name: service_name.to_string(),
pid: 1001,
state: MockServiceState::Running,
target: MockServiceTarget::Up,
after: HashMap::new(),
});
let client = ZinitClient::new(&socket_path);
let status = client.status(service_name).await?;
assert_eq!(
status.state,
ServiceState::Running,
"Service should be running"
);
assert!(status.pid > 0, "Service should have a valid PID");
client.stop(service_name).await?;
let status = client.status(service_name).await?;
assert_ne!(
status.state,
ServiceState::Running,
"Service should not be running"
);
client.start(service_name).await?;
let status = client.status(service_name).await?;
assert_eq!(
status.state,
ServiceState::Running,
"Service should be running again"
);
client.stop(service_name).await?;
client.forget(service_name).await?;
let services = client.list().await?;
assert!(
!services.contains_key(service_name),
"Service should be forgotten"
);
server.stop().await;
Ok(())
}
#[tokio::test]
async fn test_client_reconnection() -> Result<()> {
let temp_dir = tempdir().expect("Failed to create temp dir");
let socket_path = temp_dir.path().join("mock-zinit.sock");
let mut server = MockZinitServer::new(&socket_path).await;
server.start().await.expect("Failed to start mock server");
server.add_service(MockService {
name: "test-service".to_string(),
pid: 1001,
state: MockServiceState::Running,
target: MockServiceTarget::Up,
after: HashMap::new(),
});
let config = ClientConfig {
socket_path: socket_path.to_str().unwrap().to_string().into(),
connection_timeout: Duration::from_secs(1),
operation_timeout: Duration::from_secs(5),
max_retries: 3,
retry_delay: Duration::from_millis(100),
max_retry_delay: Duration::from_secs(1),
retry_jitter: false,
};
let client = ZinitClient::with_config(config);
let services = client.list().await?;
println!("Found {} services", services.len());
assert_eq!(services.len(), 1, "Expected 1 service");
server.stop().await;
Ok(())
}
#[tokio::test]
async fn test_client_logs() -> Result<()> {
let temp_dir = tempdir().expect("Failed to create temp dir");
let socket_path = temp_dir.path().join("mock-zinit.sock");
let mut server = MockZinitServer::new(&socket_path).await;
server.start().await.expect("Failed to start mock server");
server.add_service(MockService {
name: "test-service".to_string(),
pid: 1001,
state: MockServiceState::Running,
target: MockServiceTarget::Up,
after: HashMap::new(),
});
let client = ZinitClient::new(&socket_path);
let mut logs = client.logs(false, None::<&str>).await?;
let mut count = 0;
while let Some(log_result) = logs.next().await {
let _ = log_result?;
count += 1;
if count >= 10 {
break; }
}
println!("Received {} log entries", count);
assert!(count > 0, "Expected at least one log entry");
server.stop().await;
Ok(())
}
#[tokio::test]
async fn test_client_custom_config() -> Result<()> {
let temp_dir = tempdir().expect("Failed to create temp dir");
let socket_path = temp_dir.path().join("mock-zinit.sock");
let mut server = MockZinitServer::new(&socket_path).await;
server.start().await.expect("Failed to start mock server");
server.add_service(MockService {
name: "test-service".to_string(),
pid: 1001,
state: MockServiceState::Running,
target: MockServiceTarget::Up,
after: HashMap::new(),
});
let config = ClientConfig {
socket_path: socket_path.to_str().unwrap().to_string().into(),
connection_timeout: Duration::from_secs(2),
operation_timeout: Duration::from_secs(10),
max_retries: 5,
retry_delay: Duration::from_millis(200),
max_retry_delay: Duration::from_secs(2),
retry_jitter: true,
};
let client = ZinitClient::with_config(config);
let services = client.list().await?;
assert_eq!(services.len(), 1, "Expected 1 service");
server.stop().await;
Ok(())
}
#[tokio::test]
async fn test_create_service() -> Result<()> {
let temp_dir = tempdir().expect("Failed to create temp dir");
let socket_path = temp_dir.path().join("mock-zinit.sock");
let mut server = MockZinitServer::new(&socket_path).await;
server.start().await.expect("Failed to start mock server");
let client = ZinitClient::new(&socket_path);
let service_config = json!({
"exec": "/usr/bin/test-app",
"args": ["--port", "8080"],
"oneshot": false
});
client.create_service("test-app", service_config).await?;
let services = client.list().await?;
assert!(
services.contains_key("test-app"),
"Service should be created"
);
assert_eq!(services["test-app"], ServiceState::Unknown);
server.stop().await;
Ok(())
}
#[tokio::test]
async fn test_create_service_already_exists() -> Result<()> {
let temp_dir = tempdir().expect("Failed to create temp dir");
let socket_path = temp_dir.path().join("mock-zinit.sock");
let mut server = MockZinitServer::new(&socket_path).await;
server.start().await.expect("Failed to start mock server");
server.add_service(MockService {
name: "existing-service".to_string(),
pid: 1001,
state: MockServiceState::Running,
target: MockServiceTarget::Up,
after: HashMap::new(),
});
let client = ZinitClient::new(&socket_path);
let service_config = json!({
"exec": "/usr/bin/test-app",
"oneshot": false
});
let result = client
.create_service("existing-service", service_config)
.await;
assert!(result.is_err(), "Creating duplicate service should fail");
server.stop().await;
Ok(())
}
#[tokio::test]
async fn test_create_service_invalid_config() -> Result<()> {
let temp_dir = tempdir().expect("Failed to create temp dir");
let socket_path = temp_dir.path().join("mock-zinit.sock");
let mut server = MockZinitServer::new(&socket_path).await;
server.start().await.expect("Failed to start mock server");
let client = ZinitClient::new(&socket_path);
let invalid_config = json!("invalid");
let result = client
.create_service("invalid-service", invalid_config)
.await;
assert!(result.is_ok(), "Valid JSON should be accepted");
server.stop().await;
Ok(())
}
#[tokio::test]
async fn test_get_service() -> Result<()> {
let temp_dir = tempdir().expect("Failed to create temp dir");
let socket_path = temp_dir.path().join("mock-zinit.sock");
let mut server = MockZinitServer::new(&socket_path).await;
server.start().await.expect("Failed to start mock server");
server.add_service(MockService {
name: "test-service".to_string(),
pid: 1001,
state: MockServiceState::Running,
target: MockServiceTarget::Up,
after: HashMap::new(),
});
let client = ZinitClient::new(&socket_path);
let service_info = client.get_service("test-service").await?;
assert!(
service_info.is_object(),
"Service info should be a JSON object"
);
let service_obj = service_info.as_object().unwrap();
assert_eq!(service_obj["name"], "test-service");
assert_eq!(service_obj["pid"], 1001);
assert_eq!(service_obj["state"], "Running");
assert_eq!(service_obj["target"], "Up");
server.stop().await;
Ok(())
}
#[tokio::test]
async fn test_get_service_not_found() -> Result<()> {
let temp_dir = tempdir().expect("Failed to create temp dir");
let socket_path = temp_dir.path().join("mock-zinit.sock");
let mut server = MockZinitServer::new(&socket_path).await;
server.start().await.expect("Failed to start mock server");
let client = ZinitClient::new(&socket_path);
let result = client.get_service("non-existent-service").await;
assert!(result.is_err(), "Getting non-existent service should fail");
server.stop().await;
Ok(())
}
#[tokio::test]
async fn test_delete_service() -> Result<()> {
let temp_dir = tempdir().expect("Failed to create temp dir");
let socket_path = temp_dir.path().join("mock-zinit.sock");
let mut server = MockZinitServer::new(&socket_path).await;
server.start().await.expect("Failed to start mock server");
server.add_service(MockService {
name: "test-service".to_string(),
pid: 0,
state: MockServiceState::Success,
target: MockServiceTarget::Down,
after: HashMap::new(),
});
let client = ZinitClient::new(&socket_path);
let services = client.list().await?;
assert!(
services.contains_key("test-service"),
"Service should exist"
);
client.delete_service("test-service").await?;
let services = client.list().await?;
assert!(
!services.contains_key("test-service"),
"Service should be deleted"
);
server.stop().await;
Ok(())
}
#[tokio::test]
async fn test_delete_running_service() -> Result<()> {
let temp_dir = tempdir().expect("Failed to create temp dir");
let socket_path = temp_dir.path().join("mock-zinit.sock");
let mut server = MockZinitServer::new(&socket_path).await;
server.start().await.expect("Failed to start mock server");
server.add_service(MockService {
name: "running-service".to_string(),
pid: 1001,
state: MockServiceState::Running,
target: MockServiceTarget::Up,
after: HashMap::new(),
});
let client = ZinitClient::new(&socket_path);
let status = client.status("running-service").await?;
assert_eq!(status.state, ServiceState::Running);
client.delete_service("running-service").await?;
let services = client.list().await?;
assert!(
!services.contains_key("running-service"),
"Service should be deleted"
);
server.stop().await;
Ok(())
}
#[tokio::test]
async fn test_delete_service_not_found() -> Result<()> {
let temp_dir = tempdir().expect("Failed to create temp dir");
let socket_path = temp_dir.path().join("mock-zinit.sock");
let mut server = MockZinitServer::new(&socket_path).await;
server.start().await.expect("Failed to start mock server");
let client = ZinitClient::new(&socket_path);
let result = client.delete_service("non-existent-service").await;
assert!(result.is_err(), "Deleting non-existent service should fail");
server.stop().await;
Ok(())
}
#[tokio::test]
async fn test_service_crud_lifecycle() -> Result<()> {
let temp_dir = tempdir().expect("Failed to create temp dir");
let socket_path = temp_dir.path().join("mock-zinit.sock");
let mut server = MockZinitServer::new(&socket_path).await;
server.start().await.expect("Failed to start mock server");
let client = ZinitClient::new(&socket_path);
let service_name = "lifecycle-test-service";
let service_config = json!({
"exec": "/usr/bin/test-app",
"args": ["--config", "/etc/test.conf"],
"env": {
"PORT": "8080",
"ENV": "test"
},
"oneshot": false,
"working_dir": "/opt/test"
});
client.create_service(service_name, service_config).await?;
let services = client.list().await?;
assert!(
services.contains_key(service_name),
"Service should be created"
);
assert_eq!(services[service_name], ServiceState::Unknown);
let service_info = client.get_service(service_name).await?;
assert!(
service_info.is_object(),
"Service info should be a JSON object"
);
let service_obj = service_info.as_object().unwrap();
assert_eq!(service_obj["name"], service_name);
assert_eq!(service_obj["state"], "Unknown");
assert_eq!(service_obj["target"], "Down");
assert_eq!(service_obj["pid"], 0);
client.start(service_name).await?;
let updated_info = client.get_service(service_name).await?;
let updated_obj = updated_info.as_object().unwrap();
assert_eq!(updated_obj["state"], "Running");
assert_eq!(updated_obj["target"], "Up");
assert!(updated_obj["pid"].as_u64().unwrap() > 0);
client.delete_service(service_name).await?;
let services = client.list().await?;
assert!(
!services.contains_key(service_name),
"Service should be deleted"
);
let result = client.get_service(service_name).await;
assert!(result.is_err(), "Getting deleted service should fail");
server.stop().await;
Ok(())
}