mecha10-cli 0.1.47

Mecha10 CLI tool
Documentation
//! Tests for package utils functions

use mecha10_cli::services::package_service::utils::*;
use std::fs;
use tempfile::TempDir;

#[test]
fn test_is_executable_on_unix() {
    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;

        let temp = TempDir::new().unwrap();
        let file = temp.path().join("test_binary");
        fs::write(&file, "binary content").unwrap();

        // Initially not executable
        assert!(!is_executable(&file).unwrap());

        // Make executable
        let mut perms = fs::metadata(&file).unwrap().permissions();
        perms.set_mode(0o755);
        fs::set_permissions(&file, perms).unwrap();

        assert!(is_executable(&file).unwrap());
    }
}

#[test]
fn test_is_executable_on_windows() {
    #[cfg(not(unix))]
    {
        let temp = TempDir::new().unwrap();

        // File with .exe extension
        let exe_file = temp.path().join("test.exe");
        fs::write(&exe_file, "binary").unwrap();
        assert!(is_executable(&exe_file).unwrap());

        // File without extension
        let no_ext = temp.path().join("binary");
        fs::write(&no_ext, "binary").unwrap();
        assert!(is_executable(&no_ext).unwrap());

        // File with non-exe extension
        let txt_file = temp.path().join("readme.txt");
        fs::write(&txt_file, "text").unwrap();
        assert!(!is_executable(&txt_file).unwrap());
    }
}

#[test]
fn test_calculate_checksum_consistent() {
    let temp = TempDir::new().unwrap();
    let file = temp.path().join("test.txt");
    fs::write(&file, "test content").unwrap();

    let checksum1 = calculate_checksum(&file).unwrap();
    let checksum2 = calculate_checksum(&file).unwrap();

    assert_eq!(checksum1, checksum2);
    assert_eq!(checksum1.len(), 64); // BLAKE3 produces 32-byte hash = 64 hex chars
}

#[test]
fn test_calculate_checksum_different_content() {
    let temp = TempDir::new().unwrap();

    let file1 = temp.path().join("file1.txt");
    let file2 = temp.path().join("file2.txt");

    fs::write(&file1, "content A").unwrap();
    fs::write(&file2, "content B").unwrap();

    let checksum1 = calculate_checksum(&file1).unwrap();
    let checksum2 = calculate_checksum(&file2).unwrap();

    assert_ne!(checksum1, checksum2);
}

#[test]
fn test_calculate_checksum_large_file() {
    let temp = TempDir::new().unwrap();
    let file = temp.path().join("large.dat");

    // Create file larger than buffer size (8192 bytes)
    let content = vec![0u8; 16384];
    fs::write(&file, content).unwrap();

    let result = calculate_checksum(&file);
    assert!(result.is_ok());
    assert_eq!(result.unwrap().len(), 64);
}

#[test]
fn test_calculate_checksum_missing_file() {
    let temp = TempDir::new().unwrap();
    let file = temp.path().join("nonexistent.txt");

    let result = calculate_checksum(&file);
    assert!(result.is_err());
}

#[test]
fn test_determine_config_type_infrastructure() {
    let path = std::path::Path::new("/project/config/infrastructure.yaml");
    assert_eq!(determine_config_type(path), "infrastructure");
}

#[test]
fn test_determine_config_type_node() {
    let path = std::path::Path::new("/project/config/node_config.json");
    assert_eq!(determine_config_type(path), "node");
}

#[test]
fn test_determine_config_type_behavior() {
    let path = std::path::Path::new("/project/config/behavior_tree.yaml");
    assert_eq!(determine_config_type(path), "behavior");
}

#[test]
fn test_determine_config_type_unknown() {
    let path = std::path::Path::new("/project/config/database.toml");
    assert_eq!(determine_config_type(path), "unknown");
}

#[test]
fn test_determine_config_type_case_insensitive() {
    let path = std::path::Path::new("/project/config/INFRASTRUCTURE.yaml");
    assert_eq!(determine_config_type(path), "infrastructure");
}

#[test]
fn test_determine_asset_type_model_by_path() {
    let path = std::path::Path::new("/project/models/robot.urdf");
    assert_eq!(determine_asset_type(path), "model");
}

#[test]
fn test_determine_asset_type_model_by_extension() {
    let path = std::path::Path::new("/project/data/network.onnx");
    assert_eq!(determine_asset_type(path), "model");

    let path2 = std::path::Path::new("/project/data/weights.pt");
    assert_eq!(determine_asset_type(path2), "model");
}

#[test]
fn test_determine_asset_type_calibration() {
    let path = std::path::Path::new("/project/calibration/camera.yaml");
    assert_eq!(determine_asset_type(path), "calibration");
}

#[test]
fn test_determine_asset_type_data() {
    let path = std::path::Path::new("/project/data/training.csv");
    assert_eq!(determine_asset_type(path), "data");
}

#[test]
fn test_determine_asset_type_case_insensitive() {
    let path = std::path::Path::new("/project/MODELS/robot.urdf");
    assert_eq!(determine_asset_type(path), "model");
}

#[test]
fn test_collect_dependencies_with_cargo_lock() {
    let temp = TempDir::new().unwrap();
    let project_root = temp.path();

    let cargo_lock_content = r#"
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3

[[package]]
name = "anyhow"
version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "serde"
version = "1.0.193"
source = "registry+https://github.com/rust-lang/crates.io-index"
"#;

    fs::write(project_root.join("Cargo.lock"), cargo_lock_content).unwrap();

    let result = collect_dependencies(project_root);
    assert!(result.is_ok());

    let deps = result.unwrap();
    assert!(deps.contains_key("anyhow"));
    assert!(deps.contains_key("serde"));
    assert_eq!(deps.len(), 2);
}

#[test]
fn test_collect_dependencies_without_cargo_lock() {
    let temp = TempDir::new().unwrap();
    let project_root = temp.path();

    // No Cargo.lock file

    let result = collect_dependencies(project_root);
    assert!(result.is_ok());

    let deps = result.unwrap();
    assert_eq!(deps.len(), 0);
}

#[test]
fn test_collect_dependencies_empty_cargo_lock() {
    let temp = TempDir::new().unwrap();
    let project_root = temp.path();

    fs::write(project_root.join("Cargo.lock"), "").unwrap();

    let result = collect_dependencies(project_root);
    assert!(result.is_ok());

    let deps = result.unwrap();
    assert_eq!(deps.len(), 0);
}

#[test]
fn test_get_git_commit_in_git_repo() {
    // Only test if we're actually in a git repo
    let temp = TempDir::new().unwrap();
    let project_root = temp.path();

    // Initialize git repo
    std::process::Command::new("git")
        .args(["init"])
        .current_dir(project_root)
        .output()
        .ok();

    // Create initial commit
    fs::write(project_root.join("test.txt"), "test").ok();
    std::process::Command::new("git")
        .args(["add", "."])
        .current_dir(project_root)
        .output()
        .ok();
    std::process::Command::new("git")
        .args(["commit", "-m", "Initial commit"])
        .current_dir(project_root)
        .output()
        .ok();

    let result = get_git_commit(project_root);

    // Commit might be None if git operations failed
    if let Some(commit) = result {
        assert_eq!(commit.len(), 40); // Git SHA-1 hash is 40 hex chars
        assert!(commit.chars().all(|c| c.is_ascii_hexdigit()));
    }
}

#[test]
fn test_get_git_commit_not_git_repo() {
    let temp = TempDir::new().unwrap();
    let project_root = temp.path();

    let result = get_git_commit(project_root);
    assert!(result.is_none());
}

#[test]
fn test_copy_dir_recursive_simple() {
    let temp = TempDir::new().unwrap();
    let src = temp.path().join("source");
    let dst = temp.path().join("dest");

    fs::create_dir_all(&src).unwrap();
    fs::write(src.join("file1.txt"), "content1").unwrap();
    fs::write(src.join("file2.txt"), "content2").unwrap();

    let result = copy_dir_recursive(&src, &dst);
    assert!(result.is_ok());

    assert!(dst.join("file1.txt").exists());
    assert!(dst.join("file2.txt").exists());
    assert_eq!(fs::read_to_string(dst.join("file1.txt")).unwrap(), "content1");
    assert_eq!(fs::read_to_string(dst.join("file2.txt")).unwrap(), "content2");
}

#[test]
fn test_copy_dir_recursive_nested() {
    let temp = TempDir::new().unwrap();
    let src = temp.path().join("source");
    let dst = temp.path().join("dest");

    let nested = src.join("subdir/deeper");
    fs::create_dir_all(&nested).unwrap();
    fs::write(src.join("root.txt"), "root content").unwrap();
    fs::write(src.join("subdir/sub.txt"), "sub content").unwrap();
    fs::write(nested.join("deep.txt"), "deep content").unwrap();

    let result = copy_dir_recursive(&src, &dst);
    assert!(result.is_ok());

    assert!(dst.join("root.txt").exists());
    assert!(dst.join("subdir/sub.txt").exists());
    assert!(dst.join("subdir/deeper/deep.txt").exists());
}

#[test]
fn test_copy_dir_recursive_creates_dest() {
    let temp = TempDir::new().unwrap();
    let src = temp.path().join("source");
    let dst = temp.path().join("nonexistent/dest");

    fs::create_dir_all(&src).unwrap();
    fs::write(src.join("file.txt"), "content").unwrap();

    assert!(!dst.exists());

    let result = copy_dir_recursive(&src, &dst);
    assert!(result.is_ok());
    assert!(dst.exists());
    assert!(dst.join("file.txt").exists());
}

#[test]
fn test_copy_dir_recursive_empty_dir() {
    let temp = TempDir::new().unwrap();
    let src = temp.path().join("source");
    let dst = temp.path().join("dest");

    fs::create_dir_all(&src).unwrap();

    let result = copy_dir_recursive(&src, &dst);
    assert!(result.is_ok());
    assert!(dst.exists());
}

#[test]
fn test_copy_dir_recursive_missing_source() {
    let temp = TempDir::new().unwrap();
    let src = temp.path().join("nonexistent");
    let dst = temp.path().join("dest");

    let result = copy_dir_recursive(&src, &dst);
    assert!(result.is_err());
}