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_watch() {
let registry = create_test_registry();
let loader = BehaviorLoader::new(registry);
let json = r#"{
"name": "test",
"root": {
"type": "watch",
"policy": "require_one",
"children": [
{ "type": "node", "node": "test_behavior" },
{ "type": "node", "node": "move" }
]
}
}"#;
let behavior = loader.load_from_json(json).unwrap();
assert_eq!(behavior.name(), "watch");
}
#[test]
fn test_load_subsumption() {
let registry = create_test_registry();
let loader = BehaviorLoader::new(registry);
let json = r#"{
"name": "test",
"root": {
"type": "subsumption",
"layers": [
{
"priority": 100,
"name": "interrupt",
"node": { "type": "node", "node": "test_behavior" }
},
{
"priority": 0,
"name": "main",
"node": { "type": "node", "node": "move" }
}
]
}
}"#;
let behavior = loader.load_from_json(json).unwrap();
assert_eq!(behavior.name(), "subsumption");
}
#[test]
fn test_load_subsumption_preserves_layer_priority_order() {
let registry = create_test_registry();
let loader = BehaviorLoader::new(registry);
let json = r#"{
"name": "test",
"root": {
"type": "subsumption",
"layers": [
{ "priority": 0, "node": { "type": "node", "node": "move" } },
{ "priority": 100, "node": { "type": "node", "node": "test_behavior" } }
]
}
}"#;
let behavior = loader.load_from_json(json).unwrap();
assert_eq!(behavior.name(), "subsumption");
}
#[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());
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());
assert!(
error_msg.contains("not found") || error_msg.contains("missing_config"),
"Expected error about missing config, got: {}",
error_msg
);
}
#[test]
fn test_load_inverter_decorator() {
let registry = create_test_registry();
let loader = BehaviorLoader::new(registry);
let json = r#"{
"name": "test",
"root": {
"type": "inverter",
"child": { "type": "node", "node": "move" }
}
}"#;
let behavior = loader.load_from_json(json).unwrap();
assert_eq!(behavior.name(), "inverter");
}
#[test]
fn test_load_force_success_decorator() {
let registry = create_test_registry();
let loader = BehaviorLoader::new(registry);
let json = r#"{
"name": "test",
"root": {
"type": "force_success",
"child": { "type": "node", "node": "move" }
}
}"#;
let behavior = loader.load_from_json(json).unwrap();
assert_eq!(behavior.name(), "force_success");
}
#[test]
fn test_load_force_failure_decorator() {
let registry = create_test_registry();
let loader = BehaviorLoader::new(registry);
let json = r#"{
"name": "test",
"root": {
"type": "force_failure",
"child": { "type": "node", "node": "move" }
}
}"#;
let behavior = loader.load_from_json(json).unwrap();
assert_eq!(behavior.name(), "force_failure");
}
#[test]
fn test_load_repeat_decorator() {
let registry = create_test_registry();
let loader = BehaviorLoader::new(registry);
let json = r#"{
"name": "test",
"root": {
"type": "repeat",
"count": 2,
"child": { "type": "node", "node": "move" }
}
}"#;
let behavior = loader.load_from_json(json).unwrap();
assert_eq!(behavior.name(), "repeat");
}
#[test]
fn test_load_retry_decorator() {
let registry = create_test_registry();
let loader = BehaviorLoader::new(registry);
let json = r#"{
"name": "test",
"root": {
"type": "retry",
"max_attempts": 3,
"child": { "type": "node", "node": "move" }
}
}"#;
let behavior = loader.load_from_json(json).unwrap();
assert_eq!(behavior.name(), "retry");
}
#[test]
fn test_load_timeout_decorator() {
let registry = create_test_registry();
let loader = BehaviorLoader::new(registry);
let json = r#"{
"name": "test",
"root": {
"type": "timeout",
"timeout_secs": 10.0,
"child": { "type": "node", "node": "move" }
}
}"#;
let behavior = loader.load_from_json(json).unwrap();
assert_eq!(behavior.name(), "timeout");
}
#[test]
fn test_load_decorator_with_unregistered_child_errors() {
let registry = create_test_registry();
let loader = BehaviorLoader::new(registry);
let json = r#"{
"name": "test",
"root": {
"type": "inverter",
"child": { "type": "node", "node": "unknown_behavior" }
}
}"#;
let result = loader.load_from_json(json);
assert!(result.is_err());
}
#[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");
}