mecha10-behavior-runtime 0.1.25

Behavior tree runtime for Mecha10 - unified AI and logic composition system
Documentation
// Tests for mecha10_behavior_runtime::loader

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

#[derive(Debug)]
struct TestBehavior {
    name: String,
    _speed: f64,
}

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

    fn name(&self) -> &str {
        &self.name
    }
}

fn create_test_registry() -> NodeRegistry {
    let mut registry = NodeRegistry::new();

    registry.register("test_behavior", |config| {
        let _speed = config.get("speed").and_then(|v| v.as_f64()).unwrap_or(1.0);
        Ok(Box::new(TestBehavior {
            name: "test_behavior".to_string(),
            _speed,
        }))
    });

    registry.register("move", |config| {
        let _speed = config.get("speed").and_then(|v| v.as_f64()).unwrap_or(1.0);
        Ok(Box::new(TestBehavior {
            name: "move".to_string(),
            _speed,
        }))
    });

    registry
}

#[test]
fn test_load_simple_node() {
    let registry = create_test_registry();
    let loader = BehaviorLoader::new(registry);

    let json = r#"{
        "name": "test",
        "root": {
            "type": "node",
            "node": "test_behavior"
        }
    }"#;

    let behavior = loader.load_from_json(json).unwrap();
    assert_eq!(behavior.name(), "test_behavior");
}

#[test]
fn test_load_sequence() {
    let registry = create_test_registry();
    let loader = BehaviorLoader::new(registry);

    let json = r#"{
        "name": "test",
        "root": {
            "type": "sequence",
            "children": [
                {
                    "type": "node",
                    "node": "test_behavior"
                },
                {
                    "type": "node",
                    "node": "move"
                }
            ]
        }
    }"#;

    let behavior = loader.load_from_json(json).unwrap();
    assert_eq!(behavior.name(), "sequence");
}

#[test]
fn test_load_with_config_ref() {
    let registry = create_test_registry();
    let loader = BehaviorLoader::new(registry);

    let json = r#"{
        "name": "test",
        "root": {
            "type": "node",
            "node": "move",
            "config_ref": "fast_move"
        },
        "configs": {
            "fast_move": {
                "speed": 2.5
            }
        }
    }"#;

    let behavior = loader.load_from_json(json).unwrap();
    assert_eq!(behavior.name(), "move");
}

#[test]
fn test_load_with_inline_config() {
    let registry = create_test_registry();
    let loader = BehaviorLoader::new(registry);

    let json = r#"{
        "name": "test",
        "root": {
            "type": "node",
            "node": "move",
            "config": {
                "speed": 3.0
            }
        }
    }"#;

    let behavior = loader.load_from_json(json).unwrap();
    assert_eq!(behavior.name(), "move");
}

#[test]
fn test_load_nested_composition() {
    let registry = create_test_registry();
    let loader = BehaviorLoader::new(registry);

    let json = r#"{
        "name": "test",
        "root": {
            "type": "selector",
            "children": [
                {
                    "type": "sequence",
                    "children": [
                        {
                            "type": "node",
                            "node": "test_behavior"
                        }
                    ]
                },
                {
                    "type": "node",
                    "node": "move"
                }
            ]
        }
    }"#;

    let behavior = loader.load_from_json(json).unwrap();
    assert_eq!(behavior.name(), "selector");
}

#[test]
fn test_load_parallel() {
    let registry = create_test_registry();
    let loader = BehaviorLoader::new(registry);

    let json = r#"{
        "name": "test",
        "root": {
            "type": "parallel",
            "policy": "require_all",
            "children": [
                {
                    "type": "node",
                    "node": "test_behavior"
                },
                {
                    "type": "node",
                    "node": "move"
                }
            ]
        }
    }"#;

    let behavior = loader.load_from_json(json).unwrap();
    assert_eq!(behavior.name(), "parallel");
}

#[test]
fn test_load_missing_node_type() {
    let registry = create_test_registry();
    let loader = BehaviorLoader::new(registry);

    let json = r#"{
        "name": "test",
        "root": {
            "type": "node",
            "node": "unknown_behavior"
        }
    }"#;

    let result = loader.load_from_json(json);
    assert!(result.is_err());
    let error_msg = format!("{:?}", result.unwrap_err());
    // Error is wrapped with context chain, so check the debug format includes our message
    assert!(
        error_msg.contains("not registered") || error_msg.contains("unknown_behavior"),
        "Expected error about unregistered node, got: {}",
        error_msg
    );
}

#[test]
fn test_load_missing_config_ref() {
    let registry = create_test_registry();
    let loader = BehaviorLoader::new(registry);

    let json = r#"{
        "name": "test",
        "root": {
            "type": "node",
            "node": "move",
            "config_ref": "missing_config"
        }
    }"#;

    let result = loader.load_from_json(json);
    assert!(result.is_err());
    let error_msg = format!("{:?}", result.unwrap_err());
    // Error is wrapped with context chain, so check the debug format includes our message
    assert!(
        error_msg.contains("not found") || error_msg.contains("missing_config"),
        "Expected error about missing config, got: {}",
        error_msg
    );
}

#[test]
fn test_empty_sequence() {
    let registry = create_test_registry();
    let loader = BehaviorLoader::new(registry);

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

    let behavior = loader.load_from_json(json).unwrap();
    assert_eq!(behavior.name(), "sequence");
}