use mecha10_behavior_runtime::BehaviorConfig;
use std::fs;
use std::path::Path;
#[test]
fn test_basic_navigation_seed() {
let seed_path = "seeds/basic_navigation.json";
validate_seed_file(seed_path);
}
#[test]
fn test_obstacle_avoidance_seed() {
let seed_path = "seeds/obstacle_avoidance.json";
validate_seed_file(seed_path);
}
#[test]
fn test_patrol_simple_seed() {
let seed_path = "seeds/patrol_simple.json";
validate_seed_file(seed_path);
}
#[test]
fn test_idle_wander_seed() {
let seed_path = "seeds/idle_wander.json";
validate_seed_file(seed_path);
}
#[test]
fn test_all_seeds_have_required_fields() {
let seed_files = vec![
"seeds/basic_navigation.json",
"seeds/obstacle_avoidance.json",
"seeds/patrol_simple.json",
"seeds/idle_wander.json",
];
for seed_path in seed_files {
let config = load_config(seed_path);
assert!(!config.name.is_empty(), "{}: name must not be empty", seed_path);
assert!(config.description.is_some(), "{}: should have description", seed_path);
assert!(config.schema.is_some(), "{}: should have $schema field", seed_path);
if let Some(schema) = &config.schema {
assert!(
schema.contains("mecha10.dev/schemas/behavior-composition"),
"{}: schema should reference mecha10.dev",
seed_path
);
}
}
}
#[test]
fn test_seed_names_match_filenames() {
let seeds = vec![
("seeds/basic_navigation.json", "basic_navigation"),
("seeds/obstacle_avoidance.json", "obstacle_avoidance"),
("seeds/patrol_simple.json", "patrol_simple"),
("seeds/idle_wander.json", "idle_wander"),
];
for (path, expected_name) in seeds {
let config = load_config(path);
assert_eq!(config.name, expected_name, "{}: name should match filename", path);
}
}
#[test]
fn test_seeds_have_valid_composition() {
let seed_files = vec![
"seeds/basic_navigation.json",
"seeds/obstacle_avoidance.json",
"seeds/patrol_simple.json",
"seeds/idle_wander.json",
];
for seed_path in seed_files {
let config = load_config(seed_path);
validate_composition(&config.root, seed_path);
}
}
#[test]
fn test_seeds_configs_are_referenced() {
let seed_files = vec![
"seeds/basic_navigation.json",
"seeds/obstacle_avoidance.json",
"seeds/patrol_simple.json",
"seeds/idle_wander.json",
];
for seed_path in seed_files {
let config = load_config(seed_path);
let mut refs_used = std::collections::HashSet::new();
collect_config_refs(&config.root, &mut refs_used);
for ref_key in refs_used {
assert!(
config.configs.contains_key(&ref_key),
"{}: config_ref '{}' not found in configs map",
seed_path,
ref_key
);
}
}
}
#[test]
fn test_json_schema_generation() {
let schema = BehaviorConfig::generate_schema().expect("Failed to generate schema");
let _parsed: serde_json::Value = serde_json::from_str(&schema).expect("Generated schema is not valid JSON");
assert!(schema.contains("BehaviorConfig"));
assert!(schema.contains("CompositionConfig"));
assert!(schema.contains("ParallelPolicyConfig"));
}
fn validate_seed_file(seed_path: &str) {
assert!(Path::new(seed_path).exists(), "Seed file not found: {}", seed_path);
let content = fs::read_to_string(seed_path).unwrap_or_else(|e| panic!("Failed to read {}: {}", seed_path, e));
let _json: serde_json::Value =
serde_json::from_str(&content).unwrap_or_else(|e| panic!("{} is not valid JSON: {}", seed_path, e));
BehaviorConfig::from_json(&content)
.unwrap_or_else(|e| panic!("{} failed to parse as BehaviorConfig: {}", seed_path, e));
}
fn load_config(seed_path: &str) -> BehaviorConfig {
let content = fs::read_to_string(seed_path).unwrap_or_else(|e| panic!("Failed to read {}: {}", seed_path, e));
BehaviorConfig::from_json(&content).unwrap_or_else(|e| panic!("Failed to parse {}: {}", seed_path, e))
}
fn validate_composition(comp: &mecha10_behavior_runtime::CompositionConfig, context: &str) {
use mecha10_behavior_runtime::CompositionConfig;
match comp {
CompositionConfig::Sequence { children, .. } => {
assert!(!children.is_empty(), "{}: sequence should have children", context);
for child in children {
validate_composition(child, context);
}
}
CompositionConfig::Selector { children, .. } => {
assert!(!children.is_empty(), "{}: selector should have children", context);
for child in children {
validate_composition(child, context);
}
}
CompositionConfig::Parallel { children, .. } => {
assert!(
children.len() >= 2,
"{}: parallel should have at least 2 children",
context
);
for child in children {
validate_composition(child, context);
}
}
CompositionConfig::Node { node, .. } => {
assert!(!node.is_empty(), "{}: node type must not be empty", context);
}
}
}
fn collect_config_refs(
comp: &mecha10_behavior_runtime::CompositionConfig,
refs: &mut std::collections::HashSet<String>,
) {
use mecha10_behavior_runtime::CompositionConfig;
match comp {
CompositionConfig::Sequence { children, .. }
| CompositionConfig::Selector { children, .. }
| CompositionConfig::Parallel { children, .. } => {
for child in children {
collect_config_refs(child, refs);
}
}
CompositionConfig::Node { config_ref, .. } => {
if let Some(ref_key) = config_ref {
refs.insert(ref_key.clone());
}
}
}
}