943 lines
30 KiB
Rust
943 lines
30 KiB
Rust
//! OpenRPC client for Hero Supervisor
|
|
//!
|
|
//! This crate provides a client library for interacting with the Hero Supervisor
|
|
//! OpenRPC server. It offers a simple, async interface for managing actors and jobs.
|
|
//!
|
|
//! ## Features
|
|
//!
|
|
//! - **Native client**: Full-featured client for native Rust applications
|
|
//! - **WASM client**: Browser-compatible client using fetch APIs
|
|
//!
|
|
//! ## Usage
|
|
//!
|
|
//! ### Native Client
|
|
//! ```rust
|
|
//! use hero_supervisor_openrpc_client::SupervisorClient;
|
|
//!
|
|
//! let client = SupervisorClient::new("http://localhost:3030")?;
|
|
//! let runners = client.list_runners().await?;
|
|
//! ```
|
|
//!
|
|
//! ### WASM Client
|
|
//! ```rust
|
|
//! use hero_supervisor_openrpc_client::wasm::WasmSupervisorClient;
|
|
//!
|
|
//! let client = WasmSupervisorClient::new("http://localhost:3030".to_string());
|
|
//! let runners = client.list_runners().await?;
|
|
//! ```
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use thiserror::Error;
|
|
use serde_json;
|
|
|
|
// Import types from the main supervisor crate
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
use std::path::PathBuf;
|
|
|
|
// WASM-compatible client module
|
|
#[cfg(target_arch = "wasm32")]
|
|
pub mod wasm;
|
|
|
|
// Re-export WASM types for convenience
|
|
#[cfg(target_arch = "wasm32")]
|
|
pub use wasm::{WasmSupervisorClient, WasmJob, WasmJobType, WasmRunnerType};
|
|
|
|
// Native client dependencies
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
use jsonrpsee::{
|
|
core::client::ClientT,
|
|
http_client::{HttpClient, HttpClientBuilder},
|
|
rpc_params,
|
|
};
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
use std::path::PathBuf;
|
|
|
|
/// Client for communicating with Hero Supervisor OpenRPC server
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
pub struct SupervisorClient {
|
|
client: HttpClient,
|
|
server_url: String,
|
|
}
|
|
|
|
/// Error types for client operations
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
#[derive(Error, Debug)]
|
|
pub enum ClientError {
|
|
#[error("JSON-RPC error: {0}")]
|
|
JsonRpc(#[from] jsonrpsee::core::ClientError),
|
|
|
|
#[error("Serialization error: {0}")]
|
|
Serialization(#[from] serde_json::Error),
|
|
|
|
#[error("HTTP client error: {0}")]
|
|
Http(String),
|
|
|
|
#[error("Server error: {message}")]
|
|
Server { message: String },
|
|
}
|
|
|
|
/// Error types for WASM client operations
|
|
#[cfg(target_arch = "wasm32")]
|
|
#[derive(Error, Debug)]
|
|
pub enum ClientError {
|
|
#[error("JSON-RPC error: {0}")]
|
|
JsonRpc(String),
|
|
|
|
#[error("Serialization error: {0}")]
|
|
Serialization(#[from] serde_json::Error),
|
|
|
|
#[error("HTTP client error: {0}")]
|
|
Http(String),
|
|
|
|
#[error("Server error: {message}")]
|
|
Server { message: String },
|
|
|
|
#[error("JavaScript error: {0}")]
|
|
JavaScript(String),
|
|
|
|
#[error("Network error: {0}")]
|
|
Network(String),
|
|
}
|
|
|
|
// Implement From for jsonrpsee ClientError for WASM
|
|
#[cfg(target_arch = "wasm32")]
|
|
impl From<wasm_bindgen::JsValue> for ClientError {
|
|
fn from(js_val: wasm_bindgen::JsValue) -> Self {
|
|
ClientError::JavaScript(format!("{:?}", js_val))
|
|
}
|
|
}
|
|
|
|
/// Result type for client operations
|
|
pub type ClientResult<T> = Result<T, ClientError>;
|
|
|
|
/// Types of runners supported by the supervisor
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub enum RunnerType {
|
|
/// SAL Runner for system abstraction layer operations
|
|
SALRunner,
|
|
/// OSIS Runner for operating system interface operations
|
|
OSISRunner,
|
|
/// V Runner for virtualization operations
|
|
VRunner,
|
|
/// Python Runner for Python-based actors
|
|
PyRunner,
|
|
}
|
|
|
|
/// Process manager type for WASM compatibility
|
|
#[cfg(target_arch = "wasm32")]
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub enum ProcessManagerType {
|
|
Simple,
|
|
Tmux(String),
|
|
}
|
|
|
|
/// Configuration for an actor runner
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct RunnerConfig {
|
|
/// Unique identifier for the actor
|
|
pub actor_id: String,
|
|
/// Type of runner
|
|
pub runner_type: RunnerType,
|
|
/// Path to the actor binary
|
|
pub binary_path: PathBuf,
|
|
/// Database path for the actor
|
|
pub db_path: String,
|
|
/// Redis URL for job queue
|
|
pub redis_url: String,
|
|
}
|
|
|
|
|
|
|
|
/// Job result response
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(untagged)]
|
|
pub enum JobResult {
|
|
Success { success: String },
|
|
Error { error: String },
|
|
}
|
|
|
|
/// Job status response
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct JobStatusResponse {
|
|
pub job_id: String,
|
|
pub status: String,
|
|
pub created_at: String,
|
|
pub started_at: Option<String>,
|
|
pub completed_at: Option<String>,
|
|
}
|
|
|
|
// Re-export Job types from shared crate
|
|
pub use hero_job::{Job, JobStatus, JobError, JobBuilder};
|
|
|
|
/// Process status wrapper for OpenRPC serialization (matches server response)
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub enum ProcessStatusWrapper {
|
|
Running,
|
|
Stopped,
|
|
Starting,
|
|
Stopping,
|
|
Error(String),
|
|
}
|
|
|
|
/// Log information wrapper for OpenRPC serialization (matches server response)
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct LogInfoWrapper {
|
|
pub timestamp: String,
|
|
pub level: String,
|
|
pub message: String,
|
|
}
|
|
|
|
/// Supervisor information response containing secret counts and server details
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SupervisorInfo {
|
|
pub server_url: String,
|
|
pub admin_secrets_count: usize,
|
|
pub user_secrets_count: usize,
|
|
pub register_secrets_count: usize,
|
|
pub runners_count: usize,
|
|
}
|
|
|
|
/// Type aliases for compatibility
|
|
#[cfg(target_arch = "wasm32")]
|
|
pub type ProcessStatus = ProcessStatusWrapper;
|
|
#[cfg(target_arch = "wasm32")]
|
|
pub type LogInfo = LogInfoWrapper;
|
|
|
|
/// Simple ProcessStatus type for native builds to avoid service manager dependency
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
pub type ProcessStatus = ProcessStatusWrapper;
|
|
|
|
/// Re-export types from supervisor crate for native builds
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
pub use hero_supervisor::{ProcessManagerType, RunnerStatus};
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
pub use hero_supervisor::runner::LogInfo;
|
|
|
|
/// Type aliases for WASM compatibility
|
|
#[cfg(target_arch = "wasm32")]
|
|
pub type RunnerStatus = ProcessStatusWrapper;
|
|
#[cfg(target_arch = "wasm32")]
|
|
pub type LogInfo = LogInfoWrapper;
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
impl SupervisorClient {
|
|
/// Create a new supervisor client
|
|
pub fn new(server_url: impl Into<String>) -> ClientResult<Self> {
|
|
let server_url = server_url.into();
|
|
|
|
let client = HttpClientBuilder::default()
|
|
.request_timeout(std::time::Duration::from_secs(30))
|
|
.build(&server_url)
|
|
.map_err(|e| ClientError::Http(e.to_string()))?;
|
|
|
|
Ok(Self {
|
|
client,
|
|
server_url,
|
|
})
|
|
}
|
|
|
|
/// Get the server URL
|
|
pub fn server_url(&self) -> &str {
|
|
&self.server_url
|
|
}
|
|
|
|
/// Test connection using OpenRPC discovery method
|
|
/// This calls the standard `rpc.discover` method that should be available on any OpenRPC server
|
|
pub async fn discover(&self) -> ClientResult<serde_json::Value> {
|
|
let result: serde_json::Value = self
|
|
.client
|
|
.request("rpc.discover", rpc_params![])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(result)
|
|
}
|
|
|
|
/// Register a new runner to the supervisor with secret authentication
|
|
pub async fn register_runner(
|
|
&self,
|
|
secret: &str,
|
|
name: &str,
|
|
queue: &str,
|
|
) -> ClientResult<()> {
|
|
let _: () = self
|
|
.client
|
|
.request(
|
|
"register_runner",
|
|
rpc_params![secret, name, queue],
|
|
)
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Create a new job without queuing it to a runner
|
|
pub async fn jobs_create(
|
|
&self,
|
|
secret: &str,
|
|
job: Job,
|
|
) -> ClientResult<String> {
|
|
let params = serde_json::json!({
|
|
"secret": secret,
|
|
"job": job
|
|
});
|
|
|
|
let job_id: String = self
|
|
.client
|
|
.request("jobs.create", rpc_params![params])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(job_id)
|
|
}
|
|
|
|
/// List all jobs
|
|
pub async fn jobs_list(&self) -> ClientResult<Vec<Job>> {
|
|
let jobs: Vec<Job> = self
|
|
.client
|
|
.request("jobs.list", rpc_params![])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(jobs)
|
|
}
|
|
|
|
/// Run a job on the appropriate runner and return the result
|
|
pub async fn job_run(
|
|
&self,
|
|
secret: &str,
|
|
job: Job,
|
|
) -> ClientResult<JobResult> {
|
|
let params = serde_json::json!({
|
|
"secret": secret,
|
|
"job": job
|
|
});
|
|
|
|
let result: JobResult = self
|
|
.client
|
|
.request("job.run", rpc_params![params])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(result)
|
|
}
|
|
|
|
/// Start a previously created job by queuing it to its assigned runner
|
|
pub async fn job_start(
|
|
&self,
|
|
secret: &str,
|
|
job_id: &str,
|
|
) -> ClientResult<()> {
|
|
let params = serde_json::json!({
|
|
"secret": secret,
|
|
"job_id": job_id
|
|
});
|
|
|
|
let _: () = self
|
|
.client
|
|
.request("job.start", rpc_params![params])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Get the current status of a job
|
|
pub async fn job_status(&self, job_id: &str) -> ClientResult<JobStatusResponse> {
|
|
let status: JobStatusResponse = self
|
|
.client
|
|
.request("job.status", rpc_params![job_id])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(status)
|
|
}
|
|
|
|
/// Get the result of a completed job (blocks until result is available)
|
|
pub async fn job_result(&self, job_id: &str) -> ClientResult<JobResult> {
|
|
let result: JobResult = self
|
|
.client
|
|
.request("job.result", rpc_params![job_id])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(result)
|
|
}
|
|
|
|
/// Remove a runner from the supervisor
|
|
pub async fn remove_runner(&self, actor_id: &str) -> ClientResult<()> {
|
|
let _: () = self
|
|
.client
|
|
.request("remove_runner", rpc_params![actor_id])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(())
|
|
}
|
|
|
|
/// List all runner IDs
|
|
pub async fn list_runners(&self) -> ClientResult<Vec<String>> {
|
|
let runners: Vec<String> = self
|
|
.client
|
|
.request("list_runners", rpc_params![])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(runners)
|
|
}
|
|
|
|
/// Start a specific runner
|
|
pub async fn start_runner(&self, actor_id: &str) -> ClientResult<()> {
|
|
let _: () = self
|
|
.client
|
|
.request("start_runner", rpc_params![actor_id])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Stop a specific runner
|
|
pub async fn stop_runner(&self, actor_id: &str, force: bool) -> ClientResult<()> {
|
|
let _: () = self
|
|
.client
|
|
.request("stop_runner", rpc_params![actor_id, force])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Add a runner to the supervisor
|
|
pub async fn add_runner(&self, config: RunnerConfig, process_manager: ProcessManagerType) -> ClientResult<()> {
|
|
let _: () = self
|
|
.client
|
|
.request("add_runner", rpc_params![config, process_manager])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Get status of a specific runner
|
|
pub async fn get_runner_status(&self, actor_id: &str) -> ClientResult<RunnerStatus> {
|
|
let status: RunnerStatus = self
|
|
.client
|
|
.request("get_runner_status", rpc_params![actor_id])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(status)
|
|
}
|
|
|
|
/// Get logs for a specific runner
|
|
pub async fn get_runner_logs(
|
|
&self,
|
|
actor_id: &str,
|
|
lines: Option<usize>,
|
|
follow: bool,
|
|
) -> ClientResult<Vec<LogInfo>> {
|
|
let logs: Vec<LogInfo> = self
|
|
.client
|
|
.request("get_runner_logs", rpc_params![actor_id, lines, follow])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(logs)
|
|
}
|
|
|
|
/// Queue a job to a specific runner
|
|
pub async fn queue_job_to_runner(&self, runner: &str, job: Job) -> ClientResult<()> {
|
|
let params = serde_json::json!({
|
|
"runner": runner,
|
|
"job": job
|
|
});
|
|
|
|
let _: () = self
|
|
.client
|
|
.request("queue_job_to_runner", rpc_params![params])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Queue a job and wait for completion
|
|
pub async fn queue_and_wait(&self, runner: &str, job: Job, timeout_secs: u64) -> ClientResult<Option<String>> {
|
|
let params = serde_json::json!({
|
|
"runner": runner,
|
|
"job": job,
|
|
"timeout_secs": timeout_secs
|
|
});
|
|
|
|
let result: Option<String> = self
|
|
.client
|
|
.request("queue_and_wait", rpc_params![params])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(result)
|
|
}
|
|
|
|
/// Run a job on a specific runner
|
|
pub async fn run_job(&self, secret: &str, job: Job) -> ClientResult<JobResult> {
|
|
let params = serde_json::json!({
|
|
"secret": secret,
|
|
"job": job
|
|
});
|
|
|
|
let result: JobResult = self
|
|
.client
|
|
.request("run_job", rpc_params![params])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(result)
|
|
}
|
|
|
|
/// Get job result by job ID
|
|
pub async fn get_job_result(&self, job_id: &str) -> ClientResult<Option<String>> {
|
|
let result: Option<String> = self
|
|
.client
|
|
.request("get_job_result", rpc_params![job_id])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(result)
|
|
}
|
|
|
|
/// Get status of all runners
|
|
pub async fn get_all_runner_status(&self) -> ClientResult<Vec<(String, RunnerStatus)>> {
|
|
let statuses: Vec<(String, RunnerStatus)> = self
|
|
.client
|
|
.request("get_all_runner_status", rpc_params![])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(statuses)
|
|
}
|
|
|
|
/// Start all runners
|
|
pub async fn start_all(&self) -> ClientResult<Vec<(String, bool)>> {
|
|
let results: Vec<(String, bool)> = self
|
|
.client
|
|
.request("start_all", rpc_params![])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(results)
|
|
}
|
|
|
|
/// Stop all runners
|
|
pub async fn stop_all(&self, force: bool) -> ClientResult<Vec<(String, bool)>> {
|
|
let results: Vec<(String, bool)> = self
|
|
.client
|
|
.request("stop_all", rpc_params![force])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(results)
|
|
}
|
|
|
|
/// Get status of all runners (alternative method)
|
|
pub async fn get_all_status(&self) -> ClientResult<Vec<(String, RunnerStatus)>> {
|
|
let statuses: Vec<(String, RunnerStatus)> = self
|
|
.client
|
|
.request("get_all_status", rpc_params![])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(statuses)
|
|
}
|
|
|
|
/// Add a secret to the supervisor
|
|
pub async fn add_secret(
|
|
&self,
|
|
admin_secret: &str,
|
|
secret_type: &str,
|
|
secret_value: &str,
|
|
) -> ClientResult<()> {
|
|
let params = serde_json::json!({
|
|
"admin_secret": admin_secret,
|
|
"secret_type": secret_type,
|
|
"secret_value": secret_value
|
|
});
|
|
|
|
let _: () = self
|
|
.client
|
|
.request("add_secret", rpc_params![params])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Remove a secret from the supervisor
|
|
pub async fn remove_secret(
|
|
&self,
|
|
admin_secret: &str,
|
|
secret_type: &str,
|
|
secret_value: &str,
|
|
) -> ClientResult<()> {
|
|
let params = serde_json::json!({
|
|
"admin_secret": admin_secret,
|
|
"secret_type": secret_type,
|
|
"secret_value": secret_value
|
|
});
|
|
|
|
let _: () = self
|
|
.client
|
|
.request("remove_secret", rpc_params![params])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(())
|
|
}
|
|
|
|
/// List secrets (returns supervisor info including secret counts)
|
|
pub async fn list_secrets(&self, admin_secret: &str) -> ClientResult<SupervisorInfo> {
|
|
let params = serde_json::json!({
|
|
"admin_secret": admin_secret
|
|
});
|
|
|
|
let info: SupervisorInfo = self
|
|
.client
|
|
.request("list_secrets", rpc_params![params])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(info)
|
|
}
|
|
|
|
/// Stop a running job
|
|
pub async fn job_stop(&self, secret: &str, job_id: &str) -> ClientResult<()> {
|
|
let params = serde_json::json!({
|
|
"secret": secret,
|
|
"job_id": job_id
|
|
});
|
|
|
|
self.client
|
|
.request("job.stop", rpc_params![params])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))
|
|
}
|
|
|
|
/// Delete a job from the system
|
|
pub async fn job_delete(&self, secret: &str, job_id: &str) -> ClientResult<()> {
|
|
let params = serde_json::json!({
|
|
"secret": secret,
|
|
"job_id": job_id
|
|
});
|
|
|
|
self.client
|
|
.request("job.delete", rpc_params![params])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))
|
|
}
|
|
|
|
/// Get supervisor information including secret counts
|
|
pub async fn get_supervisor_info(&self, admin_secret: &str) -> ClientResult<SupervisorInfo> {
|
|
let info: SupervisorInfo = self
|
|
.client
|
|
.request("get_supervisor_info", rpc_params![admin_secret])
|
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
Ok(info)
|
|
}
|
|
}
|
|
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_client_creation() {
|
|
let client = SupervisorClient::new("http://127.0.0.1:3030");
|
|
assert!(client.is_ok());
|
|
|
|
let client = client.unwrap();
|
|
assert_eq!(client.server_url(), "http://127.0.0.1:3030");
|
|
}
|
|
|
|
#[test]
|
|
fn test_job_builder() {
|
|
let job = JobBuilder::new()
|
|
.caller_id("test_client")
|
|
.context_id("test_context")
|
|
.payload("print('Hello, World!');")
|
|
.executor("osis")
|
|
.runner("test_runner")
|
|
.timeout(60)
|
|
.env_var("TEST_VAR", "test_value")
|
|
.build();
|
|
|
|
assert!(job.is_ok());
|
|
let job = job.unwrap();
|
|
|
|
assert_eq!(job.caller_id, "test_client");
|
|
assert_eq!(job.context_id, "test_context");
|
|
assert_eq!(job.payload, "print('Hello, World!');");
|
|
assert_eq!(job.executor, "osis");
|
|
assert_eq!(job.runner, "test_runner");
|
|
assert_eq!(job.timeout, 60);
|
|
assert_eq!(job.env_vars.get("TEST_VAR"), Some(&"test_value".to_string()));
|
|
}
|
|
|
|
#[test]
|
|
fn test_job_builder_validation() {
|
|
// Missing caller_id
|
|
let result = JobBuilder::new()
|
|
.context_id("test")
|
|
.payload("test")
|
|
.runner("test")
|
|
.build();
|
|
assert!(result.is_err());
|
|
|
|
// Missing context_id
|
|
let result = JobBuilder::new()
|
|
.caller_id("test")
|
|
.payload("test")
|
|
.runner("test")
|
|
.build();
|
|
assert!(result.is_err());
|
|
|
|
// Missing payload
|
|
let result = JobBuilder::new()
|
|
.caller_id("test")
|
|
.context_id("test")
|
|
.runner("test")
|
|
.executor("test")
|
|
.build();
|
|
assert!(result.is_err());
|
|
|
|
// Missing runner
|
|
let result = JobBuilder::new()
|
|
.caller_id("test")
|
|
.context_id("test")
|
|
.payload("test")
|
|
.executor("test")
|
|
.build();
|
|
assert!(result.is_err());
|
|
|
|
// Missing executor
|
|
let result = JobBuilder::new()
|
|
.caller_id("test")
|
|
.context_id("test")
|
|
.payload("test")
|
|
.runner("test")
|
|
.build();
|
|
assert!(result.is_err());
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod client_tests {
|
|
use super::*;
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
mod native_tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_client_creation() {
|
|
let client = SupervisorClient::new("http://localhost:3030");
|
|
assert!(client.is_ok());
|
|
let client = client.unwrap();
|
|
assert_eq!(client.server_url(), "http://localhost:3030");
|
|
}
|
|
|
|
#[test]
|
|
fn test_client_creation_invalid_url() {
|
|
let client = SupervisorClient::new("invalid-url");
|
|
// HTTP client builder validates URLs and should fail on invalid ones
|
|
assert!(client.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_process_status_wrapper_serialization() {
|
|
let status = ProcessStatusWrapper::Running;
|
|
let serialized = serde_json::to_string(&status).unwrap();
|
|
assert_eq!(serialized, "\"Running\"");
|
|
|
|
let status = ProcessStatusWrapper::Error("test error".to_string());
|
|
let serialized = serde_json::to_string(&status).unwrap();
|
|
assert!(serialized.contains("Error"));
|
|
assert!(serialized.contains("test error"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_log_info_wrapper_serialization() {
|
|
let log = LogInfoWrapper {
|
|
timestamp: "2023-01-01T00:00:00Z".to_string(),
|
|
level: "INFO".to_string(),
|
|
message: "test message".to_string(),
|
|
};
|
|
|
|
let serialized = serde_json::to_string(&log).unwrap();
|
|
assert!(serialized.contains("2023-01-01T00:00:00Z"));
|
|
assert!(serialized.contains("INFO"));
|
|
assert!(serialized.contains("test message"));
|
|
|
|
let deserialized: LogInfoWrapper = serde_json::from_str(&serialized).unwrap();
|
|
assert_eq!(deserialized.timestamp, log.timestamp);
|
|
assert_eq!(deserialized.level, log.level);
|
|
assert_eq!(deserialized.message, log.message);
|
|
}
|
|
|
|
#[test]
|
|
fn test_runner_type_serialization() {
|
|
let runner_type = RunnerType::SALRunner;
|
|
let serialized = serde_json::to_string(&runner_type).unwrap();
|
|
assert_eq!(serialized, "\"SALRunner\"");
|
|
|
|
let deserialized: RunnerType = serde_json::from_str(&serialized).unwrap();
|
|
assert_eq!(deserialized, RunnerType::SALRunner);
|
|
}
|
|
|
|
#[test]
|
|
fn test_job_type_conversion() {
|
|
assert_eq!(JobType::SAL, JobType::SAL);
|
|
assert_eq!(JobType::OSIS, JobType::OSIS);
|
|
assert_eq!(JobType::V, JobType::V);
|
|
assert_eq!(JobType::Python, JobType::Python);
|
|
}
|
|
|
|
#[test]
|
|
fn test_job_status_serialization() {
|
|
let status = JobStatus::Started;
|
|
let serialized = serde_json::to_string(&status).unwrap();
|
|
assert_eq!(serialized, "\"Started\"");
|
|
|
|
let deserialized: JobStatus = serde_json::from_str(&serialized).unwrap();
|
|
assert_eq!(deserialized, JobStatus::Started);
|
|
}
|
|
}
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
mod wasm_tests {
|
|
use super::*;
|
|
use wasm_bindgen_test::*;
|
|
|
|
wasm_bindgen_test_configure!(run_in_browser);
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_wasm_client_creation() {
|
|
let client = crate::wasm::WasmSupervisorClient::new("http://localhost:3030".to_string());
|
|
assert_eq!(client.server_url(), "http://localhost:3030");
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_wasm_job_creation() {
|
|
let job = crate::wasm::WasmJob::new(
|
|
"test-id".to_string(),
|
|
"test payload".to_string(),
|
|
"SAL".to_string(),
|
|
"test-runner".to_string(),
|
|
);
|
|
|
|
assert_eq!(job.id(), "test-id");
|
|
assert_eq!(job.payload(), "test payload");
|
|
assert_eq!(job.job_type(), "SAL");
|
|
assert_eq!(job.runner(), "test-runner");
|
|
assert_eq!(job.caller_id(), "wasm_client");
|
|
assert_eq!(job.context_id(), "wasm_context");
|
|
assert_eq!(job.timeout_secs(), 30);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_wasm_job_setters() {
|
|
let mut job = crate::wasm::WasmJob::new(
|
|
"test-id".to_string(),
|
|
"test payload".to_string(),
|
|
"SAL".to_string(),
|
|
"test-runner".to_string(),
|
|
);
|
|
|
|
job.set_caller_id("custom-caller".to_string());
|
|
job.set_context_id("custom-context".to_string());
|
|
job.set_timeout_secs(60);
|
|
job.set_env_vars("{\"KEY\":\"VALUE\"}".to_string());
|
|
|
|
assert_eq!(job.caller_id(), "custom-caller");
|
|
assert_eq!(job.context_id(), "custom-context");
|
|
assert_eq!(job.timeout_secs(), 60);
|
|
assert_eq!(job.env_vars(), "{\"KEY\":\"VALUE\"}");
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_wasm_job_id_generation() {
|
|
let mut job = crate::wasm::WasmJob::new(
|
|
"original-id".to_string(),
|
|
"test payload".to_string(),
|
|
"SAL".to_string(),
|
|
"test-runner".to_string(),
|
|
);
|
|
|
|
let original_id = job.id();
|
|
job.generate_id();
|
|
let new_id = job.id();
|
|
|
|
assert_ne!(original_id, new_id);
|
|
assert!(new_id.len() > 0);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_create_job_function() {
|
|
let job = crate::wasm::create_job(
|
|
"func-test-id".to_string(),
|
|
"func test payload".to_string(),
|
|
"OSIS".to_string(),
|
|
"func-test-runner".to_string(),
|
|
);
|
|
|
|
assert_eq!(job.id(), "func-test-id");
|
|
assert_eq!(job.payload(), "func test payload");
|
|
assert_eq!(job.job_type(), "OSIS");
|
|
assert_eq!(job.runner(), "func-test-runner");
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_wasm_job_type_enum() {
|
|
use crate::wasm::WasmJobType;
|
|
|
|
// Test that enum variants exist and can be created
|
|
let sal = WasmJobType::SAL;
|
|
let osis = WasmJobType::OSIS;
|
|
let v = WasmJobType::V;
|
|
|
|
// Test equality
|
|
assert_eq!(sal, WasmJobType::SAL);
|
|
assert_eq!(osis, WasmJobType::OSIS);
|
|
assert_eq!(v, WasmJobType::V);
|
|
|
|
// Test inequality
|
|
assert_ne!(sal, osis);
|
|
assert_ne!(osis, v);
|
|
assert_ne!(v, sal);
|
|
}
|
|
}
|
|
|
|
// Common tests that work on both native and WASM
|
|
#[test]
|
|
fn test_process_status_wrapper_variants() {
|
|
let running = ProcessStatusWrapper::Running;
|
|
let stopped = ProcessStatusWrapper::Stopped;
|
|
let starting = ProcessStatusWrapper::Starting;
|
|
let stopping = ProcessStatusWrapper::Stopping;
|
|
let error = ProcessStatusWrapper::Error("test".to_string());
|
|
|
|
// Test that all variants can be created
|
|
assert_eq!(running, ProcessStatusWrapper::Running);
|
|
assert_eq!(stopped, ProcessStatusWrapper::Stopped);
|
|
assert_eq!(starting, ProcessStatusWrapper::Starting);
|
|
assert_eq!(stopping, ProcessStatusWrapper::Stopping);
|
|
|
|
if let ProcessStatusWrapper::Error(msg) = error {
|
|
assert_eq!(msg, "test");
|
|
} else {
|
|
panic!("Expected Error variant");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_job_type_variants() {
|
|
assert_eq!(JobType::SAL, JobType::SAL);
|
|
assert_eq!(JobType::OSIS, JobType::OSIS);
|
|
assert_eq!(JobType::V, JobType::V);
|
|
assert_eq!(JobType::Python, JobType::Python);
|
|
|
|
assert_ne!(JobType::SAL, JobType::OSIS);
|
|
assert_ne!(JobType::OSIS, JobType::V);
|
|
assert_ne!(JobType::V, JobType::Python);
|
|
}
|
|
|
|
#[test]
|
|
fn test_job_status_variants() {
|
|
assert_eq!(JobStatus::Created, JobStatus::Created);
|
|
assert_eq!(JobStatus::Dispatched, JobStatus::Dispatched);
|
|
assert_eq!(JobStatus::Started, JobStatus::Started);
|
|
assert_eq!(JobStatus::Finished, JobStatus::Finished);
|
|
assert_eq!(JobStatus::Error, JobStatus::Error);
|
|
|
|
assert_ne!(JobStatus::Created, JobStatus::Dispatched);
|
|
assert_ne!(JobStatus::Started, JobStatus::Finished);
|
|
}
|
|
|
|
#[test]
|
|
fn test_runner_type_variants() {
|
|
assert_eq!(RunnerType::SALRunner, RunnerType::SALRunner);
|
|
assert_eq!(RunnerType::OSISRunner, RunnerType::OSISRunner);
|
|
assert_eq!(RunnerType::VRunner, RunnerType::VRunner);
|
|
assert_eq!(RunnerType::PyRunner, RunnerType::PyRunner);
|
|
|
|
assert_ne!(RunnerType::SALRunner, RunnerType::OSISRunner);
|
|
assert_ne!(RunnerType::VRunner, RunnerType::PyRunner);
|
|
}
|
|
|
|
#[test]
|
|
fn test_process_manager_type_variants() {
|
|
let simple = ProcessManagerType::Simple;
|
|
let tmux = ProcessManagerType::Tmux("test-session".to_string());
|
|
|
|
assert_eq!(simple, ProcessManagerType::Simple);
|
|
|
|
if let ProcessManagerType::Tmux(session) = tmux {
|
|
assert_eq!(session, "test-session");
|
|
} else {
|
|
panic!("Expected Tmux variant");
|
|
}
|
|
}
|
|
}
|