use anyhow::Result;
use serde_json::json;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_sim_generation_e2e_rover() -> Result<()> {
let temp_dir = TempDir::new()?;
let project_path = temp_dir.path();
let config = json!({
"robot": {
"id": "test-rover-001",
"platform": "rover"
},
"drivers": [
{
"name": "lidar_front",
"type": "lidar",
"physics": {
"mount_point": [0.15, 0.0, 0.25]
}
},
{
"name": "camera_front",
"type": "camera",
"physics": {
"mount_point": [0.2, 0.0, 0.25],
"rotation": [0.0, 0.0, 0.0]
}
},
{
"name": "imu",
"type": "imu"
},
{
"name": "motor",
"type": "motor"
}
],
"nodes": {
"custom": []
}
});
let config_path = project_path.join("mecha10.json");
fs::write(&config_path, serde_json::to_string_pretty(&config)?)?;
let config_content = fs::read_to_string(&config_path)?;
let parsed: serde_json::Value = serde_json::from_str(&config_content)?;
assert_eq!(parsed["robot"]["platform"], "rover");
assert_eq!(parsed["drivers"].as_array().unwrap().len(), 4);
let drivers = parsed["drivers"].as_array().unwrap();
let driver_types: Vec<&str> = drivers.iter().filter_map(|d| d["type"].as_str()).collect();
assert!(driver_types.contains(&"lidar"));
assert!(driver_types.contains(&"camera"));
assert!(driver_types.contains(&"imu"));
assert!(driver_types.contains(&"motor"));
for driver in drivers {
let driver_type = driver["type"].as_str().unwrap();
if driver_type == "lidar" || driver_type == "camera" {
assert!(
driver.get("physics").is_some(),
"{} driver should have physics config",
driver_type
);
if let Some(physics) = driver.get("physics") {
if let Some(mount_point) = physics.get("mount_point") {
let mp = mount_point.as_array().unwrap();
assert_eq!(mp.len(), 3, "mount_point should have 3 elements [x, y, z]");
}
}
}
}
Ok(())
}
#[test]
fn test_sim_generation_multi_sensor() -> Result<()> {
let temp_dir = TempDir::new()?;
let project_path = temp_dir.path();
let config = json!({
"robot": {
"id": "multi-sensor-rover",
"platform": "rover"
},
"drivers": [
{
"name": "lidar_front",
"type": "lidar",
"physics": {
"mount_point": [0.15, 0.0, 0.25]
}
},
{
"name": "lidar_rear",
"type": "lidar",
"physics": {
"mount_point": [-0.15, 0.0, 0.25],
"rotation": [0.0, std::f64::consts::PI, 0.0] }
},
{
"name": "camera_front",
"type": "camera",
"physics": {
"mount_point": [0.2, 0.0, 0.3]
}
},
{
"name": "camera_rear",
"type": "camera",
"physics": {
"mount_point": [-0.2, 0.0, 0.3],
"rotation": [0.0, std::f64::consts::PI, 0.0]
}
}
]
});
let config_path = project_path.join("mecha10.json");
fs::write(&config_path, serde_json::to_string_pretty(&config)?)?;
let config_content = fs::read_to_string(&config_path)?;
let parsed: serde_json::Value = serde_json::from_str(&config_content)?;
let drivers = parsed["drivers"].as_array().unwrap();
assert_eq!(drivers.len(), 4, "Should have 4 sensors");
let names: Vec<&str> = drivers.iter().filter_map(|d| d["name"].as_str()).collect();
assert_eq!(names.len(), 4);
assert!(names.contains(&"lidar_front"));
assert!(names.contains(&"lidar_rear"));
assert!(names.contains(&"camera_front"));
assert!(names.contains(&"camera_rear"));
for driver in drivers {
if driver["name"].as_str().unwrap().contains("rear") {
assert!(
driver["physics"]["rotation"].is_array(),
"Rear sensor should have rotation"
);
}
}
Ok(())
}
#[test]
fn test_sim_generation_humanoid_template() -> Result<()> {
let temp_dir = TempDir::new()?;
let project_path = temp_dir.path();
let config = json!({
"robot": {
"platform": "humanoid"
},
"drivers": [
{
"name": "camera_head",
"type": "camera",
"physics": {
"mount_point": [0.0, 0.0, 1.6] }
},
{
"name": "imu",
"type": "imu"
}
]
});
let config_path = project_path.join("mecha10.json");
fs::write(&config_path, serde_json::to_string_pretty(&config)?)?;
let config_content = fs::read_to_string(&config_path)?;
let parsed: serde_json::Value = serde_json::from_str(&config_content)?;
assert_eq!(parsed["robot"]["platform"], "humanoid");
let drivers = parsed["drivers"].as_array().unwrap();
let has_camera = drivers.iter().any(|d| d["type"] == "camera");
let has_imu = drivers.iter().any(|d| d["type"] == "imu");
assert!(has_camera, "Humanoid should have camera");
assert!(has_imu, "Humanoid should have IMU");
Ok(())
}
#[test]
fn test_sim_generation_arm_template() -> Result<()> {
let temp_dir = TempDir::new()?;
let project_path = temp_dir.path();
let config = json!({
"robot": {
"platform": "arm"
},
"drivers": [
{
"name": "camera_wrist",
"type": "camera",
"config": {
"depth": true },
"physics": {
"mount_point": [0.0, 0.0, 0.05] }
}
]
});
let config_path = project_path.join("mecha10.json");
fs::write(&config_path, serde_json::to_string_pretty(&config)?)?;
let config_content = fs::read_to_string(&config_path)?;
let parsed: serde_json::Value = serde_json::from_str(&config_content)?;
assert_eq!(parsed["robot"]["platform"], "arm");
let drivers = parsed["drivers"].as_array().unwrap();
let camera = &drivers[0];
assert_eq!(camera["type"], "camera");
assert_eq!(camera["config"]["depth"], true, "Arm camera should support depth");
Ok(())
}
#[test]
fn test_config_validation_missing_required_fields() -> Result<()> {
let temp_dir = TempDir::new()?;
let project_path = temp_dir.path();
let invalid_config = json!({
"robot": {},
"drivers": []
});
let config_path = project_path.join("mecha10.json");
fs::write(&config_path, serde_json::to_string_pretty(&invalid_config)?)?;
let config_content = fs::read_to_string(&config_path)?;
let parsed: serde_json::Value = serde_json::from_str(&config_content)?;
assert!(parsed["robot"]["platform"].is_null(), "Platform should be missing");
Ok(())
}
#[test]
fn test_sensor_mount_point_validation() -> Result<()> {
let temp_dir = TempDir::new()?;
let project_path = temp_dir.path();
let config = json!({
"robot": {
"platform": "rover"
},
"drivers": [
{
"name": "lidar_front",
"type": "lidar",
"physics": {
"mount_point": [0.15, 0.0, 0.25]
}
}
]
});
let config_path = project_path.join("mecha10.json");
fs::write(&config_path, serde_json::to_string_pretty(&config)?)?;
let config_content = fs::read_to_string(&config_path)?;
let parsed: serde_json::Value = serde_json::from_str(&config_content)?;
let mount_point = parsed["drivers"][0]["physics"]["mount_point"].as_array().unwrap();
assert_eq!(mount_point.len(), 3, "Mount point should have 3 coordinates");
for coord in mount_point {
assert!(
coord.is_f64() || coord.is_i64(),
"Mount point coordinates should be numbers"
);
}
Ok(())
}
#[test]
fn test_motors_excluded_from_sensors() -> Result<()> {
let temp_dir = TempDir::new()?;
let project_path = temp_dir.path();
let config = json!({
"robot": {
"platform": "rover"
},
"drivers": [
{
"name": "lidar_front",
"type": "lidar",
"physics": {
"mount_point": [0.15, 0.0, 0.25]
}
},
{
"name": "motor",
"type": "motor" }
]
});
let config_path = project_path.join("mecha10.json");
fs::write(&config_path, serde_json::to_string_pretty(&config)?)?;
let config_content = fs::read_to_string(&config_path)?;
let parsed: serde_json::Value = serde_json::from_str(&config_content)?;
let drivers = parsed["drivers"].as_array().unwrap();
let sensors: Vec<&serde_json::Value> = drivers.iter().filter(|d| d["type"].as_str() != Some("motor")).collect();
assert_eq!(sensors.len(), 1, "Should have 1 sensor (lidar), motor excluded");
assert_eq!(sensors[0]["type"], "lidar");
Ok(())
}
#[test]
fn test_error_recovery_invalid_sensor_config() -> Result<()> {
let temp_dir = TempDir::new()?;
let project_path = temp_dir.path();
let config = json!({
"robot": {
"platform": "rover"
},
"drivers": [
{
"name": "lidar_front",
"type": "lidar",
"physics": {
"mount_point": [0.15, 0.0, 0.25]
}
},
{
"name": "unknown_sensor",
"type": "unknown_type", "physics": {
"mount_point": [0.0, 0.0, 0.3]
}
}
]
});
let config_path = project_path.join("mecha10.json");
fs::write(&config_path, serde_json::to_string_pretty(&config)?)?;
let config_content = fs::read_to_string(&config_path)?;
let parsed: serde_json::Value = serde_json::from_str(&config_content)?;
let drivers = parsed["drivers"].as_array().unwrap();
assert_eq!(drivers.len(), 2);
let sensor_types: Vec<&str> = drivers.iter().filter_map(|d| d["type"].as_str()).collect();
assert!(sensor_types.contains(&"lidar"));
assert!(sensor_types.contains(&"unknown_type"));
Ok(())
}
#[test]
fn test_graceful_degradation_some_sensors_fail() -> Result<()> {
let temp_dir = TempDir::new()?;
let project_path = temp_dir.path();
let config = json!({
"robot": {
"platform": "rover"
},
"drivers": [
{
"name": "lidar_valid",
"type": "lidar",
"physics": {
"mount_point": [0.15, 0.0, 0.25]
}
},
{
"name": "camera_valid",
"type": "camera",
"physics": {
"mount_point": [0.2, 0.0, 0.25]
}
},
{
"name": "invalid_sensor_1",
"type": "fake_sensor",
"physics": {
"mount_point": [0.0, 0.0, 0.3]
}
},
{
"name": "invalid_sensor_2",
"type": "another_fake",
"physics": {
"mount_point": [0.0, 0.0, 0.4]
}
}
]
});
let config_path = project_path.join("mecha10.json");
fs::write(&config_path, serde_json::to_string_pretty(&config)?)?;
let config_content = fs::read_to_string(&config_path)?;
let parsed: serde_json::Value = serde_json::from_str(&config_content)?;
let drivers = parsed["drivers"].as_array().unwrap();
assert_eq!(drivers.len(), 4, "Should have 4 drivers total");
let valid_sensors: Vec<&serde_json::Value> = drivers
.iter()
.filter(|d| {
let sensor_type = d["type"].as_str().unwrap_or("");
sensor_type == "lidar" || sensor_type == "camera" || sensor_type == "imu"
})
.collect();
let invalid_sensors: Vec<&serde_json::Value> = drivers
.iter()
.filter(|d| {
let sensor_type = d["type"].as_str().unwrap_or("");
sensor_type != "lidar" && sensor_type != "camera" && sensor_type != "imu" && sensor_type != "motor"
})
.collect();
assert_eq!(valid_sensors.len(), 2, "Should have 2 valid sensors");
assert_eq!(invalid_sensors.len(), 2, "Should have 2 invalid sensors");
Ok(())
}