mecha10-behavior-patterns 0.1.23

Common behavior patterns for Mecha10 - subsumption, ensemble, and more
Documentation
// Tests for mecha10_behavior_patterns::ensemble

use async_trait::async_trait;
use mecha10_behavior_patterns::*;
use mecha10_behavior_runtime::prelude::*;

#[derive(Debug)]
struct MockModel {
    status: NodeStatus,
}

impl MockModel {
    fn new(status: NodeStatus) -> Self {
        Self { status }
    }
}

#[async_trait]
impl BehaviorNode for MockModel {
    async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
        Ok(self.status)
    }
}

#[tokio::test]
#[ignore = "Requires Redis infrastructure"]
async fn test_ensemble_conservative_all_succeed() {
    let ctx = Context::new("test").await.unwrap();

    let mut ensemble = EnsembleNode::new(EnsembleStrategy::Conservative)
        .add_model(Box::new(MockModel::new(NodeStatus::Success)), 1.0)
        .add_model(Box::new(MockModel::new(NodeStatus::Success)), 1.0);

    let status = ensemble.tick(&ctx).await.unwrap();
    assert_eq!(status, NodeStatus::Success);
}

#[tokio::test]
#[ignore = "Requires Redis infrastructure"]
async fn test_ensemble_conservative_one_fails() {
    let ctx = Context::new("test").await.unwrap();

    let mut ensemble = EnsembleNode::new(EnsembleStrategy::Conservative)
        .add_model(Box::new(MockModel::new(NodeStatus::Success)), 1.0)
        .add_model(Box::new(MockModel::new(NodeStatus::Failure)), 1.0);

    let status = ensemble.tick(&ctx).await.unwrap();
    assert_eq!(status, NodeStatus::Failure);
}

#[tokio::test]
#[ignore = "Requires Redis infrastructure"]
async fn test_ensemble_optimistic_any_succeeds() {
    let ctx = Context::new("test").await.unwrap();

    let mut ensemble = EnsembleNode::new(EnsembleStrategy::Optimistic)
        .add_model(Box::new(MockModel::new(NodeStatus::Success)), 1.0)
        .add_model(Box::new(MockModel::new(NodeStatus::Failure)), 1.0);

    let status = ensemble.tick(&ctx).await.unwrap();
    assert_eq!(status, NodeStatus::Success);
}

#[tokio::test]
#[ignore = "Requires Redis infrastructure"]
async fn test_ensemble_majority() {
    let ctx = Context::new("test").await.unwrap();

    let mut ensemble = EnsembleNode::new(EnsembleStrategy::Majority)
        .add_model(Box::new(MockModel::new(NodeStatus::Success)), 1.0)
        .add_model(Box::new(MockModel::new(NodeStatus::Success)), 1.0)
        .add_model(Box::new(MockModel::new(NodeStatus::Failure)), 1.0);

    let status = ensemble.tick(&ctx).await.unwrap();
    assert_eq!(status, NodeStatus::Success); // 2 out of 3 succeed
}

#[tokio::test]
#[ignore = "Requires Redis infrastructure"]
async fn test_ensemble_weighted_vote() {
    let ctx = Context::new("test").await.unwrap();

    let mut ensemble = EnsembleNode::new(EnsembleStrategy::WeightedVote)
        .with_threshold(0.6)
        .add_model(Box::new(MockModel::new(NodeStatus::Success)), 0.7) // 70% weight succeeds
        .add_model(Box::new(MockModel::new(NodeStatus::Failure)), 0.3); // 30% weight fails

    let status = ensemble.tick(&ctx).await.unwrap();
    assert_eq!(status, NodeStatus::Success); // 0.7 >= 0.6 threshold
}