mecha10-cli 0.8.0

Mecha10 CLI tool
Documentation
use mecha10_cli::*;
use serial_test::serial;
use std::env;
use std::path::PathBuf;

fn clear_test_env() {
    env::remove_var("REDIS_URL");
    env::remove_var("MECHA10_REDIS_URL");
    env::remove_var("DATABASE_URL");
    env::remove_var("POSTGRES_URL");
}

#[test]
#[serial]
fn test_builder_defaults() {
    clear_test_env();
    let ctx = CliContextBuilder::new().build().unwrap();
    assert_eq!(ctx.redis_url(), "redis://localhost:6379");
    assert_eq!(ctx.postgres_url(), None);
    assert!(!ctx.is_verbose());
    assert_eq!(ctx.log_level, Level::INFO);
}

#[test]
fn test_builder_explicit_values() {
    let ctx = CliContextBuilder::new()
        .redis_url("redis://custom:1234".to_string())
        .postgres_url(Some("postgres://test".to_string()))
        .verbose(true)
        .log_level(Level::DEBUG)
        .build()
        .unwrap();

    assert_eq!(ctx.redis_url(), "redis://custom:1234");
    assert_eq!(ctx.postgres_url(), Some("postgres://test"));
    assert!(ctx.is_verbose());
    assert_eq!(ctx.log_level, Level::DEBUG);
}

#[test]
#[serial]
fn test_builder_env_precedence() {
    clear_test_env();
    env::set_var("REDIS_URL", "redis://from-env:6379");
    env::set_var("DATABASE_URL", "postgres://from-env");

    let ctx = CliContextBuilder::new().build().unwrap();

    assert_eq!(ctx.redis_url(), "redis://from-env:6379");
    assert_eq!(ctx.postgres_url(), Some("postgres://from-env"));

    clear_test_env();
}

#[test]
#[serial]
fn test_builder_explicit_overrides_env() {
    env::set_var("REDIS_URL", "redis://from-env:6379");

    let ctx = CliContextBuilder::new()
        .redis_url("redis://explicit:1234".to_string())
        .build()
        .unwrap();

    assert_eq!(ctx.redis_url(), "redis://explicit:1234");

    env::remove_var("REDIS_URL");
}

#[test]
#[serial]
fn test_builder_mecha10_env_priority() {
    env::set_var("MECHA10_REDIS_URL", "redis://mecha10:6379");
    env::set_var("REDIS_URL", "redis://generic:6379");

    let ctx = CliContextBuilder::new().build().unwrap();

    assert_eq!(ctx.redis_url(), "redis://mecha10:6379");

    env::remove_var("MECHA10_REDIS_URL");
    env::remove_var("REDIS_URL");
}

#[test]
#[serial]
fn test_convenience_methods() {
    clear_test_env();
    let ctx = CliContextBuilder::new()
        .verbose(true)
        .redis_url("redis://test:6379".to_string())
        .build()
        .unwrap();

    assert!(ctx.is_verbose());
    assert_eq!(ctx.redis_url(), "redis://test:6379");
    assert_eq!(ctx.postgres_url(), None);
}

#[test]
fn test_project_path() {
    let working_dir = PathBuf::from("/test/project");
    let ctx = CliContextBuilder::new()
        .working_dir(working_dir.clone())
        .build()
        .unwrap();

    assert_eq!(ctx.project_path("config"), PathBuf::from("/test/project/config"));
    assert_eq!(
        ctx.project_path("nodes/camera"),
        PathBuf::from("/test/project/nodes/camera")
    );
}

#[test]
fn test_config_path_default() {
    let working_dir = PathBuf::from("/test/project");
    let ctx = CliContextBuilder::new()
        .working_dir(working_dir.clone())
        .build()
        .unwrap();

    assert_eq!(ctx.config_path, PathBuf::from("/test/project/mecha10.json"));
}

#[test]
fn test_config_path_explicit() {
    let custom_path = PathBuf::from("/custom/config.json");
    let ctx = CliContextBuilder::new()
        .config_path(Some(custom_path.clone()))
        .build()
        .unwrap();

    assert_eq!(ctx.config_path, custom_path);
}

#[test]
#[serial]
fn test_default_context() {
    clear_test_env();
    let ctx = CliContext::default();
    assert_eq!(ctx.log_level, Level::INFO);
    assert_eq!(ctx.redis_url(), "redis://localhost:6379");
    assert!(!ctx.is_verbose());
}

#[test]
fn test_simulation_service_lazy_init() {
    let mut ctx = CliContextBuilder::new().build().unwrap();
    let _sim1 = ctx.simulation();
    let _sim2 = ctx.simulation();
}

#[test]
fn test_process_service_lazy_init() {
    let mut ctx = CliContextBuilder::new().build().unwrap();
    let _proc1 = ctx.process();
    let _proc2 = ctx.process();
}

#[test]
fn test_docker_service_lazy_init() {
    let mut ctx = CliContextBuilder::new().build().unwrap();
    let _docker1 = ctx.docker();
    let _docker2 = ctx.docker();
}

#[test]
fn test_deployment_service_lazy_init() {
    let mut ctx = CliContextBuilder::new().build().unwrap();
    let _deploy1 = ctx.deployment();
    let _deploy2 = ctx.deployment();
}

#[test]
fn test_component_catalog_service_lazy_init() {
    let mut ctx = CliContextBuilder::new().build().unwrap();
    let _catalog1 = ctx.component_catalog();
    let _catalog2 = ctx.component_catalog();
}

#[test]
fn test_discovery_service_lazy_init() {
    let mut ctx = CliContextBuilder::new().build().unwrap();
    let _discovery1 = ctx.discovery();
    let _discovery2 = ctx.discovery();
}

#[test]
fn test_is_ci() {
    let ctx = CliContextBuilder::new().build().unwrap();

    let ci_was_set = env::var("CI").is_ok();

    env::set_var("CI", "true");
    assert!(ctx.is_ci());

    env::remove_var("CI");
    env::set_var("GITHUB_ACTIONS", "true");
    assert!(ctx.is_ci());

    env::remove_var("GITHUB_ACTIONS");
    env::set_var("GITLAB_CI", "true");
    assert!(ctx.is_ci());

    env::remove_var("GITLAB_CI");
    if !ci_was_set {
        env::remove_var("CI");
    }
}

#[test]
fn test_path_helpers() {
    let working_dir = PathBuf::from("/test/project");
    let ctx = CliContextBuilder::new()
        .working_dir(working_dir.clone())
        .build()
        .unwrap();

    assert_eq!(ctx.logs_dir(), PathBuf::from("/test/project/logs"));
    assert_eq!(ctx.data_dir(), PathBuf::from("/test/project/data"));
    assert_eq!(ctx.recordings_dir(), PathBuf::from("/test/project/data/recordings"));
    assert_eq!(ctx.maps_dir(), PathBuf::from("/test/project/data/maps"));
    assert_eq!(ctx.telemetry_dir(), PathBuf::from("/test/project/data/telemetry"));
    assert_eq!(ctx.simulation_dir(), PathBuf::from("/test/project/configs/simulation"));
    assert_eq!(ctx.target_debug_dir(), PathBuf::from("/test/project/target/debug"));
    assert_eq!(ctx.target_release_dir(), PathBuf::from("/test/project/target/release"));
    assert_eq!(ctx.packages_dir(), PathBuf::from("/test/project/target/packages"));
}

#[test]
fn test_ensure_dir() {
    use tempfile::TempDir;

    let temp = TempDir::new().unwrap();
    let ctx = CliContextBuilder::new()
        .working_dir(temp.path().to_path_buf())
        .build()
        .unwrap();

    let test_dir = temp.path().join("test_dir");
    assert!(!test_dir.exists());

    ctx.ensure_dir(&test_dir).unwrap();
    assert!(test_dir.exists());

    ctx.ensure_dir(&test_dir).unwrap();
    assert!(test_dir.exists());
}

#[test]
fn test_validate_project_structure_not_initialized() {
    use tempfile::TempDir;

    let temp = TempDir::new().unwrap();
    let ctx = CliContextBuilder::new()
        .working_dir(temp.path().to_path_buf())
        .build()
        .unwrap();

    let result = ctx.validate_project_structure();
    assert!(result.is_err());
    assert!(result.unwrap_err().to_string().contains("not initialized"));
}

#[test]
fn test_validate_project_structure_missing_dirs() {
    use tempfile::TempDir;

    let temp = TempDir::new().unwrap();
    let ctx = CliContextBuilder::new()
        .working_dir(temp.path().to_path_buf())
        .build()
        .unwrap();

    std::fs::write(temp.path().join("mecha10.json"), "{}").unwrap();

    let result = ctx.validate_project_structure();
    assert!(result.is_err());
    assert!(result.unwrap_err().to_string().contains("nodes"));
}

#[test]
fn test_validate_project_structure_success() {
    use tempfile::TempDir;

    let temp = TempDir::new().unwrap();
    let ctx = CliContextBuilder::new()
        .working_dir(temp.path().to_path_buf())
        .build()
        .unwrap();

    std::fs::write(temp.path().join("mecha10.json"), "{}").unwrap();
    std::fs::create_dir(temp.path().join("nodes")).unwrap();
    std::fs::create_dir(temp.path().join("drivers")).unwrap();

    let result = ctx.validate_project_structure();
    assert!(result.is_ok());
}