mecha10-behavior-runtime 4.2.2

Behavior tree runtime for Mecha10 - unified AI and logic composition system
Documentation
use mecha10_behavior_runtime::{BehaviorConfig, CompositionConfig, ParallelPolicyConfig};
use std::collections::HashMap;

#[test]
fn test_parse_simple_sequence() {
    let json = r#"{
        "name": "test",
        "root": {
            "type": "sequence",
            "children": []
        }
    }"#;

    let config = BehaviorConfig::from_json(json).unwrap();
    assert_eq!(config.name, "test");

    match config.root {
        CompositionConfig::Sequence { children, .. } => {
            assert_eq!(children.len(), 0);
        }
        _ => panic!("Expected sequence"),
    }
}

#[test]
fn test_parse_node_with_config_ref() {
    let json = r#"{
        "name": "test",
        "root": {
            "type": "node",
            "node": "my_behavior",
            "config_ref": "my_config"
        },
        "configs": {
            "my_config": {
                "speed": 1.5
            }
        }
    }"#;

    let config = BehaviorConfig::from_json(json).unwrap();

    match &config.root {
        CompositionConfig::Node { node, config_ref, .. } => {
            assert_eq!(node, "my_behavior");
            assert_eq!(config_ref, &Some("my_config".to_string()));
        }
        _ => panic!("Expected node"),
    }

    let node_config = config.get_config("my_config").unwrap();
    assert_eq!(node_config["speed"], 1.5);
}

#[test]
fn test_parse_parallel_with_policy() {
    let json = r#"{
        "name": "test",
        "root": {
            "type": "parallel",
            "policy": "require_one",
            "children": []
        }
    }"#;

    let config = BehaviorConfig::from_json(json).unwrap();

    match config.root {
        CompositionConfig::Parallel { policy, .. } => {
            assert!(matches!(policy, ParallelPolicyConfig::RequireOne));
        }
        _ => panic!("Expected parallel"),
    }
}

#[test]
fn test_parse_inverter_decorator() {
    let json = r#"{
        "name": "test",
        "root": {
            "type": "inverter",
            "child": { "type": "node", "node": "my_behavior" }
        }
    }"#;

    let config = BehaviorConfig::from_json(json).unwrap();

    match config.root {
        CompositionConfig::Inverter { child, .. } => {
            assert!(matches!(*child, CompositionConfig::Node { .. }));
        }
        _ => panic!("Expected inverter"),
    }
}

#[test]
fn test_parse_repeat_decorator_with_count() {
    let json = r#"{
        "name": "test",
        "root": {
            "type": "repeat",
            "count": 3,
            "child": { "type": "node", "node": "my_behavior" }
        }
    }"#;

    let config = BehaviorConfig::from_json(json).unwrap();

    match config.root {
        CompositionConfig::Repeat { count, .. } => {
            assert_eq!(count, Some(3));
        }
        _ => panic!("Expected repeat"),
    }
}

#[test]
fn test_parse_repeat_decorator_without_count_defaults_to_infinite() {
    let json = r#"{
        "name": "test",
        "root": {
            "type": "repeat",
            "child": { "type": "node", "node": "my_behavior" }
        }
    }"#;

    let config = BehaviorConfig::from_json(json).unwrap();

    match config.root {
        CompositionConfig::Repeat { count, .. } => {
            assert_eq!(count, None);
        }
        _ => panic!("Expected repeat"),
    }
}

#[test]
fn test_parse_retry_decorator() {
    let json = r#"{
        "name": "test",
        "root": {
            "type": "retry",
            "max_attempts": 3,
            "child": { "type": "node", "node": "my_behavior" }
        }
    }"#;

    let config = BehaviorConfig::from_json(json).unwrap();

    match config.root {
        CompositionConfig::Retry { max_attempts, .. } => {
            assert_eq!(max_attempts, 3);
        }
        _ => panic!("Expected retry"),
    }
}

#[test]
fn test_parse_timeout_decorator() {
    let json = r#"{
        "name": "test",
        "root": {
            "type": "timeout",
            "timeout_secs": 30.0,
            "child": { "type": "node", "node": "my_behavior" }
        }
    }"#;

    let config = BehaviorConfig::from_json(json).unwrap();

    match config.root {
        CompositionConfig::Timeout { timeout_secs, .. } => {
            assert_eq!(timeout_secs, 30.0);
        }
        _ => panic!("Expected timeout"),
    }
}

#[test]
fn test_parse_force_success_and_force_failure_decorators() {
    let json = r#"{
        "name": "test",
        "root": {
            "type": "sequence",
            "children": [
                { "type": "force_success", "child": { "type": "node", "node": "a" } },
                { "type": "force_failure", "child": { "type": "node", "node": "b" } }
            ]
        }
    }"#;

    let config = BehaviorConfig::from_json(json).unwrap();

    match config.root {
        CompositionConfig::Sequence { children, .. } => {
            assert!(matches!(children[0], CompositionConfig::ForceSuccess { .. }));
            assert!(matches!(children[1], CompositionConfig::ForceFailure { .. }));
        }
        _ => panic!("Expected sequence"),
    }
}

#[test]
fn test_generate_schema() {
    let schema = BehaviorConfig::generate_schema().unwrap();
    assert!(schema.contains("BehaviorConfig"));
    assert!(schema.contains("CompositionConfig"));
}

#[test]
fn test_roundtrip() {
    let config = BehaviorConfig {
        schema: None,
        name: "test".to_string(),
        description: Some("Test behavior".to_string()),
        root: CompositionConfig::Sequence {
            children: vec![],
            name: None,
        },
        configs: HashMap::new(),
        requires_capabilities: Vec::new(),
    };

    let json = config.to_json().unwrap();
    let parsed = BehaviorConfig::from_json(&json).unwrap();
    assert_eq!(parsed.name, config.name);
    assert_eq!(parsed.description, config.description);
}