use mecha10_core::prelude::*;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::Mutex;
use tokio::time::sleep;
#[derive(Debug, Clone)]
struct SuccessNode {
#[allow(dead_code)]
config: SuccessConfig,
run_count: Arc<AtomicU32>,
shutdown_count: Arc<AtomicU32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct SuccessConfig {
value: i32,
}
#[async_trait]
impl NodeImpl for SuccessNode {
type Config = SuccessConfig;
async fn init(config: Self::Config) -> Result<Self> {
Ok(Self {
config,
run_count: Arc::new(AtomicU32::new(0)),
shutdown_count: Arc::new(AtomicU32::new(0)),
})
}
async fn run(&mut self, _ctx: &Context) -> Result<()> {
self.run_count.fetch_add(1, Ordering::SeqCst);
Ok(())
}
async fn shutdown(&mut self) -> Result<()> {
self.shutdown_count.fetch_add(1, Ordering::SeqCst);
Ok(())
}
async fn health_check(&self) -> HealthStatus {
HealthStatus::healthy("SuccessNode is healthy")
}
}
#[derive(Debug)]
struct FailInitNode;
#[derive(Debug, Clone, Serialize, Deserialize)]
struct FailInitConfig;
#[async_trait]
impl NodeImpl for FailInitNode {
type Config = FailInitConfig;
async fn init(_config: Self::Config) -> Result<Self> {
Err(Mecha10Error::NodeInit("Initialization failed".to_string()))
}
async fn run(&mut self, _ctx: &Context) -> Result<()> {
Ok(())
}
}
#[derive(Debug, Clone)]
struct FailShutdownNode {
shutdown_error: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct FailShutdownConfig {
shutdown_error: bool,
}
#[async_trait]
impl NodeImpl for FailShutdownNode {
type Config = FailShutdownConfig;
async fn init(config: Self::Config) -> Result<Self> {
Ok(Self {
shutdown_error: config.shutdown_error,
})
}
async fn run(&mut self, _ctx: &Context) -> Result<()> {
Ok(())
}
async fn shutdown(&mut self) -> Result<()> {
if self.shutdown_error {
Err(Mecha10Error::Shutdown("Shutdown failed".to_string()))
} else {
Ok(())
}
}
}
#[derive(Debug, Clone)]
struct HealthyNode {
healthy: Arc<Mutex<bool>>,
failure_count: Arc<AtomicU32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct HealthyConfig {
initially_healthy: bool,
}
#[async_trait]
impl NodeImpl for HealthyNode {
type Config = HealthyConfig;
async fn init(config: Self::Config) -> Result<Self> {
Ok(Self {
healthy: Arc::new(Mutex::new(config.initially_healthy)),
failure_count: Arc::new(AtomicU32::new(0)),
})
}
async fn run(&mut self, ctx: &Context) -> Result<()> {
while !ctx.is_shutdown().await {
sleep(Duration::from_millis(10)).await;
}
Ok(())
}
async fn health_check(&self) -> HealthStatus {
let is_healthy = *self.healthy.lock().await;
if is_healthy {
HealthStatus {
healthy: true,
last_check: now_micros(),
failure_count: 0,
message: Some("Node is healthy".to_string()),
}
} else {
let failures = self.failure_count.fetch_add(1, Ordering::SeqCst) + 1;
HealthStatus {
healthy: false,
last_check: now_micros(),
failure_count: failures,
message: Some("Node is unhealthy".to_string()),
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct RateNodeConfig {
rate_hz: f32,
}
impl RateConfig for RateNodeConfig {
fn rate_hz(&self) -> f32 {
self.rate_hz
}
}
#[tokio::test]
async fn test_node_init_success() {
let config = SuccessConfig { value: 42 };
let node = Node::<SuccessNode>::from_config(config).await;
assert!(node.is_ok(), "Node initialization should succeed");
let node = node.unwrap();
let state = node.state().await;
assert_eq!(state, NodeState::Starting);
}
#[tokio::test]
async fn test_node_init_failure() {
let config = FailInitConfig;
let result = Node::<FailInitNode>::from_config(config).await;
assert!(result.is_err(), "Node initialization should fail");
if let Err(e) = result {
assert!(e.to_string().contains("Initialization failed"));
}
}
#[tokio::test]
async fn test_node_init_with_config_validation() {
let config = SuccessConfig { value: 123 };
let node = Node::<SuccessNode>::from_config(config).await;
assert!(node.is_ok());
}
#[tokio::test]
async fn test_node_shutdown_success() {
let config = SuccessConfig { value: 42 };
let node = Node::<SuccessNode>::from_config(config).await.unwrap();
let result = node.shutdown().await;
assert!(result.is_ok());
assert_eq!(node.state().await, NodeState::Stopped);
}
#[tokio::test]
async fn test_node_shutdown_error_still_transitions() {
let config = FailShutdownConfig { shutdown_error: true };
let node = Node::<FailShutdownNode>::from_config(config).await.unwrap();
let result = node.shutdown().await;
assert!(result.is_err());
assert_eq!(node.state().await, NodeState::Stopped);
if let Err(e) = result {
assert!(e.to_string().contains("Shutdown failed"));
}
}
#[tokio::test]
async fn test_node_double_shutdown() {
let config = SuccessConfig { value: 42 };
let node = Node::<SuccessNode>::from_config(config).await.unwrap();
let result1 = node.shutdown().await;
assert!(result1.is_ok());
let result2 = node.shutdown().await;
assert!(result2.is_ok());
assert_eq!(node.state().await, NodeState::Stopped);
}
#[tokio::test]
async fn test_node_health_check_default() {
let config = SuccessConfig { value: 42 };
let node = Node::<SuccessNode>::from_config(config).await.unwrap();
let health = node.health_check().await;
assert!(health.healthy);
assert!(health.message.is_some());
}
#[tokio::test]
async fn test_node_health_check_custom() {
let config = HealthyConfig {
initially_healthy: true,
};
let node = Node::<HealthyNode>::from_config(config).await.unwrap();
let health = node.health_check().await;
assert!(health.healthy);
assert_eq!(health.failure_count, 0);
}
#[tokio::test]
async fn test_node_health_check_unhealthy() {
let config = HealthyConfig {
initially_healthy: false,
};
let node = Node::<HealthyNode>::from_config(config).await.unwrap();
let health = node.health_check().await;
assert!(!health.healthy);
assert!(health.failure_count > 0);
}
#[tokio::test]
async fn test_node_health_check_multiple_calls() {
let config = HealthyConfig {
initially_healthy: false,
};
let node = Node::<HealthyNode>::from_config(config).await.unwrap();
let health1 = node.health_check().await;
assert!(!health1.healthy);
let failures1 = health1.failure_count;
let health2 = node.health_check().await;
assert!(!health2.healthy);
assert!(health2.failure_count > failures1);
}
#[test]
fn test_health_status_ext_healthy() {
let status = HealthStatus::healthy("All systems operational");
assert!(status.healthy);
assert_eq!(status.failure_count, 0);
assert_eq!(status.message, Some("All systems operational".to_string()));
assert!(status.last_check > 0);
}
#[test]
fn test_health_status_ext_unhealthy() {
let status = HealthStatus::unhealthy("Connection failed");
assert!(!status.healthy);
assert_eq!(status.failure_count, 1);
assert_eq!(status.message, Some("Connection failed".to_string()));
assert!(status.last_check > 0);
}
#[test]
fn test_health_status_ext_with_string() {
let status = HealthStatus::healthy("OK".to_string());
assert!(status.healthy);
assert_eq!(status.message, Some("OK".to_string()));
}
#[test]
fn test_rate_config_rate_hz() {
let config = RateNodeConfig { rate_hz: 30.0 };
assert_eq!(config.rate_hz(), 30.0);
}
#[test]
fn test_rate_config_period() {
let config = RateNodeConfig { rate_hz: 10.0 };
let period = config.period();
let expected = Duration::from_millis(100);
let diff = if period > expected {
period - expected
} else {
expected - period
};
assert!(
diff < Duration::from_micros(1),
"Period should be approximately 100ms, got {:?}",
period
);
}
#[test]
fn test_rate_config_period_high_frequency() {
let config = RateNodeConfig { rate_hz: 100.0 };
let period = config.period();
assert_eq!(period, Duration::from_millis(10));
}
#[tokio::test]
async fn test_rate_config_create_interval() {
let config = RateNodeConfig { rate_hz: 10.0 };
let mut interval = config.create_interval();
let start = Instant::now();
interval.tick().await;
interval.tick().await;
let elapsed = start.elapsed();
assert!(elapsed >= Duration::from_millis(90));
assert!(elapsed < Duration::from_millis(150));
}
#[test]
fn test_health_reporting_config_default() {
let config = HealthReportingConfig::default();
assert_eq!(config.priority.interval(), Duration::from_secs(5));
assert!(config.enabled);
}
#[test]
fn test_health_reporting_config_priority_levels() {
let critical = HealthReportingConfig::critical();
assert_eq!(critical.priority.interval(), Duration::from_secs(1));
assert!(critical.enabled);
let normal = HealthReportingConfig::normal();
assert_eq!(normal.priority.interval(), Duration::from_secs(5));
assert!(normal.enabled);
let background = HealthReportingConfig::background();
assert_eq!(background.priority.interval(), Duration::from_secs(30));
assert!(background.enabled);
}
#[test]
fn test_health_reporting_config_clone() {
let config1 = HealthReportingConfig::critical();
let config2 = config1.clone();
assert_eq!(config1.priority.interval(), config2.priority.interval());
assert_eq!(config1.enabled, config2.enabled);
}
#[tokio::test]
async fn test_node_clone_shares_state() {
let config = SuccessConfig { value: 42 };
let node1 = Node::<SuccessNode>::from_config(config).await.unwrap();
let node2 = node1.clone();
assert_eq!(node1.state().await, node2.state().await);
let _ = node1.shutdown().await;
assert_eq!(node2.state().await, NodeState::Stopped);
}
#[test]
fn test_node_types_are_debug() {
let config = SuccessConfig { value: 42 };
let debug_str = format!("{:?}", config);
assert!(debug_str.contains("SuccessConfig"));
}
#[tokio::test]
async fn test_rapid_node_creation() {
let mut nodes = Vec::new();
for i in 0..10 {
let config = SuccessConfig { value: i };
let node = Node::<SuccessNode>::from_config(config).await;
assert!(node.is_ok());
nodes.push(node.unwrap());
}
assert_eq!(nodes.len(), 10);
}
#[tokio::test]
async fn test_rapid_health_checks() {
let config = SuccessConfig { value: 42 };
let node = Node::<SuccessNode>::from_config(config).await.unwrap();
for _ in 0..100 {
let health = node.health_check().await;
assert!(health.healthy);
}
}
#[tokio::test]
async fn test_rapid_state_queries() {
let config = SuccessConfig { value: 42 };
let node = Node::<SuccessNode>::from_config(config).await.unwrap();
for _ in 0..100 {
let state = node.state().await;
assert_eq!(state, NodeState::Starting);
}
}