use anyhow::Result;
use mecha10_cli::services::project::*;
use std::fs;
use std::path::Path;
use tempfile::TempDir;
fn create_test_project(dir: &Path, with_mecha10_json: bool) -> Result<()> {
fs::create_dir_all(dir.join("nodes"))?;
fs::create_dir_all(dir.join("drivers"))?;
fs::create_dir_all(dir.join("types"))?;
if with_mecha10_json {
fs::write(
dir.join("mecha10.json"),
r#"{
"name": "test-project",
"version": "0.1.0",
"robot": {
"id": "test-robot"
}
}"#,
)?;
}
Ok(())
}
#[test]
fn test_detect_project_in_current_dir() -> Result<()> {
let temp = TempDir::new()?;
create_test_project(temp.path(), true)?;
let project = ProjectService::detect(temp.path())?;
let expected = temp.path().canonicalize()?;
let actual = project.root().canonicalize()?;
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn test_detect_project_in_parent_dir() -> Result<()> {
let temp = TempDir::new()?;
create_test_project(temp.path(), true)?;
let subdir = temp.path().join("nodes").join("my_node");
fs::create_dir_all(&subdir)?;
let project = ProjectService::detect(&subdir)?;
let expected = temp.path().canonicalize()?;
let actual = project.root().canonicalize()?;
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn test_detect_project_fails_when_not_found() {
let temp = TempDir::new().unwrap();
create_test_project(temp.path(), false).unwrap();
let result = ProjectService::detect(temp.path());
assert!(result.is_err());
}
#[test]
fn test_project_name_from_mecha10_json() -> Result<()> {
let temp = TempDir::new()?;
create_test_project(temp.path(), true)?;
let project = ProjectService::new(temp.path().to_path_buf());
assert_eq!(project.name()?, "test-project");
Ok(())
}
#[test]
fn test_project_version_from_mecha10_json() -> Result<()> {
let temp = TempDir::new()?;
create_test_project(temp.path(), true)?;
let project = ProjectService::new(temp.path().to_path_buf());
assert_eq!(project.version()?, "0.1.0");
Ok(())
}
#[test]
fn test_validate_succeeds_for_valid_project() -> Result<()> {
let temp = TempDir::new()?;
create_test_project(temp.path(), true)?;
let project = ProjectService::new(temp.path().to_path_buf());
project.validate()?;
Ok(())
}
#[test]
fn test_validate_fails_without_mecha10_json() {
let temp = TempDir::new().unwrap();
create_test_project(temp.path(), false).unwrap();
let project = ProjectService::new(temp.path().to_path_buf());
let result = project.validate();
assert!(result.is_err());
}
#[test]
fn test_list_nodes() -> Result<()> {
let temp = TempDir::new()?;
create_test_project(temp.path(), true)?;
fs::create_dir_all(temp.path().join("nodes").join("node_a"))?;
fs::create_dir_all(temp.path().join("nodes").join("node_b"))?;
let project = ProjectService::new(temp.path().to_path_buf());
let nodes = project.list_nodes()?;
assert_eq!(nodes, vec!["node_a", "node_b"]);
Ok(())
}
#[test]
fn test_list_drivers() -> Result<()> {
let temp = TempDir::new()?;
create_test_project(temp.path(), true)?;
fs::create_dir_all(temp.path().join("drivers").join("camera"))?;
fs::create_dir_all(temp.path().join("drivers").join("motor"))?;
let project = ProjectService::new(temp.path().to_path_buf());
let drivers = project.list_drivers()?;
assert_eq!(drivers, vec!["camera", "motor"]);
Ok(())
}
#[test]
fn test_list_types() -> Result<()> {
let temp = TempDir::new()?;
create_test_project(temp.path(), true)?;
fs::create_dir_all(temp.path().join("types").join("sensor_data"))?;
fs::create_dir_all(temp.path().join("types").join("control_msg"))?;
let project = ProjectService::new(temp.path().to_path_buf());
let types = project.list_types()?;
assert_eq!(types, vec!["control_msg", "sensor_data"]);
Ok(())
}
#[test]
fn test_list_nodes_empty_directory() -> Result<()> {
let temp = TempDir::new()?;
create_test_project(temp.path(), true)?;
let project = ProjectService::new(temp.path().to_path_buf());
let nodes = project.list_nodes()?;
assert_eq!(nodes, Vec::<String>::new());
Ok(())
}
#[test]
fn test_list_nodes_skips_files() -> Result<()> {
let temp = TempDir::new()?;
create_test_project(temp.path(), true)?;
fs::create_dir_all(temp.path().join("nodes").join("node_a"))?;
fs::write(temp.path().join("nodes").join("readme.txt"), "readme")?;
let project = ProjectService::new(temp.path().to_path_buf());
let nodes = project.list_nodes()?;
assert_eq!(nodes, vec!["node_a"]);
Ok(())
}
#[test]
fn test_list_nodes_sorted() -> Result<()> {
let temp = TempDir::new()?;
create_test_project(temp.path(), true)?;
fs::create_dir_all(temp.path().join("nodes").join("zulu"))?;
fs::create_dir_all(temp.path().join("nodes").join("alpha"))?;
fs::create_dir_all(temp.path().join("nodes").join("bravo"))?;
let project = ProjectService::new(temp.path().to_path_buf());
let nodes = project.list_nodes()?;
assert_eq!(nodes, vec!["alpha", "bravo", "zulu"]);
Ok(())
}
#[test]
fn test_project_metadata_from_cargo_toml() -> Result<()> {
let temp = TempDir::new()?;
create_test_project(temp.path(), false)?;
fs::write(
temp.path().join("Cargo.toml"),
r#"[package]
name = "my-robot"
version = "1.2.3"
edition = "2021"
"#,
)?;
let project = ProjectService::new(temp.path().to_path_buf());
let name = project.name()?;
let version = project.version()?;
assert_eq!(name, "my-robot");
assert_eq!(version, "1.2.3");
Ok(())
}
#[test]
fn test_project_metadata_prefers_mecha10_json() -> Result<()> {
let temp = TempDir::new()?;
create_test_project(temp.path(), true)?;
fs::write(
temp.path().join("Cargo.toml"),
r#"[package]
name = "different-name"
version = "9.9.9"
"#,
)?;
let project = ProjectService::new(temp.path().to_path_buf());
let name = project.name()?;
let version = project.version()?;
assert_eq!(name, "test-project");
assert_eq!(version, "0.1.0");
Ok(())
}
#[test]
fn test_project_metadata_fails_without_config() {
let temp = TempDir::new().unwrap();
create_test_project(temp.path(), false).unwrap();
let project = ProjectService::new(temp.path().to_path_buf());
let result = project.name();
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("No mecha10.json or Cargo.toml"));
}
#[test]
fn test_project_metadata_invalid_json() {
let temp = TempDir::new().unwrap();
create_test_project(temp.path(), false).unwrap();
fs::write(temp.path().join("mecha10.json"), "not valid json {{{").unwrap();
let project = ProjectService::new(temp.path().to_path_buf());
let result = project.name();
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("parse"));
}
#[test]
fn test_project_metadata_missing_name_field() {
let temp = TempDir::new().unwrap();
create_test_project(temp.path(), false).unwrap();
fs::write(temp.path().join("mecha10.json"), r#"{"version": "1.0.0"}"#).unwrap();
let project = ProjectService::new(temp.path().to_path_buf());
let result = project.name();
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Missing 'name' field"));
}
#[test]
fn test_project_metadata_missing_version_field() {
let temp = TempDir::new().unwrap();
create_test_project(temp.path(), false).unwrap();
fs::write(temp.path().join("mecha10.json"), r#"{"name": "test"}"#).unwrap();
let project = ProjectService::new(temp.path().to_path_buf());
let result = project.version();
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Missing 'version' field"));
}
#[test]
fn test_project_path_method() -> Result<()> {
let temp = TempDir::new()?;
create_test_project(temp.path(), true)?;
let project = ProjectService::new(temp.path().to_path_buf());
assert_eq!(project.path("nodes/my_node"), temp.path().join("nodes/my_node"));
assert_eq!(project.path("config/robot.json"), temp.path().join("config/robot.json"));
Ok(())
}
#[test]
fn test_project_root_getter() -> Result<()> {
let temp = TempDir::new()?;
create_test_project(temp.path(), true)?;
let project = ProjectService::new(temp.path().to_path_buf());
assert_eq!(project.root(), temp.path());
Ok(())
}
#[test]
fn test_project_config_path() -> Result<()> {
let temp = TempDir::new()?;
create_test_project(temp.path(), true)?;
let project = ProjectService::new(temp.path().to_path_buf());
assert_eq!(project.config_path(), temp.path().join("mecha10.json"));
Ok(())
}
#[test]
fn test_is_initialized_true() -> Result<()> {
let temp = TempDir::new()?;
create_test_project(temp.path(), true)?;
let project = ProjectService::new(temp.path().to_path_buf());
assert!(project.is_initialized());
Ok(())
}
#[test]
fn test_is_initialized_false() -> Result<()> {
let temp = TempDir::new()?;
create_test_project(temp.path(), false)?;
let project = ProjectService::new(temp.path().to_path_buf());
assert!(!project.is_initialized());
Ok(())
}
#[test]
fn test_validate_fails_missing_nodes_dir() {
let temp = TempDir::new().unwrap();
fs::create_dir_all(temp.path()).unwrap();
fs::write(temp.path().join("mecha10.json"), "{}").unwrap();
let project = ProjectService::new(temp.path().to_path_buf());
let result = project.validate();
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("missing 'nodes' directory"));
}
#[test]
fn test_validate_fails_missing_drivers_dir() {
let temp = TempDir::new().unwrap();
fs::create_dir_all(temp.path().join("nodes")).unwrap();
fs::write(temp.path().join("mecha10.json"), "{}").unwrap();
let project = ProjectService::new(temp.path().to_path_buf());
let result = project.validate();
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("missing 'drivers' directory"));
}
#[test]
fn test_validate_fails_missing_types_dir() {
let temp = TempDir::new().unwrap();
fs::create_dir_all(temp.path().join("nodes")).unwrap();
fs::create_dir_all(temp.path().join("drivers")).unwrap();
fs::write(temp.path().join("mecha10.json"), "{}").unwrap();
let project = ProjectService::new(temp.path().to_path_buf());
let result = project.validate();
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("missing 'types' directory"));
}