mecha10-cli 0.1.47

Mecha10 CLI tool
Documentation
// Tests for mecha10_cli::services::process

use mecha10_cli::services::process::*;

#[test]
fn test_new_service() {
    let service = ProcessService::new();
    assert_eq!(service.count(), 0);
    assert!(service.is_empty());
}

#[test]
fn test_spawn_node() -> Result<()> {
    let mut service = ProcessService::new();

    // Spawn a simple process (sleep)
    let pid = service.spawn_node("test_process", "sleep", &["1"])?;

    assert!(pid > 0);
    assert_eq!(service.count(), 1);
    assert!(!service.is_empty());

    // Cleanup
    service.cleanup();

    Ok(())
}

#[test]
fn test_is_running() -> Result<()> {
    let mut service = ProcessService::new();

    // Spawn a process
    service.spawn_node("test_process", "sleep", &["2"])?;

    // Should be running
    assert!(service.is_running("test_process"));

    // Stop it
    service.stop("test_process")?;

    // Should not be running
    assert!(!service.is_running("test_process"));

    Ok(())
}

#[test]
fn test_stop_process() -> Result<()> {
    let mut service = ProcessService::new();

    service.spawn_node("test_process", "sleep", &["10"])?;
    assert_eq!(service.count(), 1);

    service.stop("test_process")?;
    // Note: count may still be 1 immediately after stop,
    // but the process is being terminated

    Ok(())
}

#[test]
fn test_cleanup() -> Result<()> {
    let mut service = ProcessService::new();

    service.spawn_node("test1", "sleep", &["5"])?;
    service.spawn_node("test2", "sleep", &["5"])?;

    assert_eq!(service.count(), 2);

    service.cleanup();

    // After cleanup, processes should be gone
    // (count will be 0 after cleanup drains the tracker)

    Ok(())
}

// === New Comprehensive Tests ===

#[test]
fn test_default() {
    let service = ProcessService::default();
    assert_eq!(service.count(), 0);
    assert!(service.is_empty());
}

#[test]
fn test_spawn_with_output() -> Result<()> {
    let mut service = ProcessService::new();

    let pid = service.spawn_with_output("test_output", "sleep", &["1"])?;
    assert!(pid > 0);
    assert_eq!(service.count(), 1);

    service.cleanup();
    Ok(())
}

#[test]
fn test_spawn_with_env() -> Result<()> {
    let mut service = ProcessService::new();

    let mut env = HashMap::new();
    env.insert("TEST_VAR".to_string(), "test_value".to_string());

    let pid = service.spawn_with_env("test_env", "sleep", &["1"], env)?;
    assert!(pid > 0);
    assert_eq!(service.count(), 1);

    service.cleanup();
    Ok(())
}

#[test]
fn test_spawn_in_dir() -> Result<()> {
    let mut service = ProcessService::new();

    let pid = service.spawn_in_dir("test_dir", "sleep", &["1"], "/tmp")?;
    assert!(pid > 0);
    assert_eq!(service.count(), 1);

    service.cleanup();
    Ok(())
}

#[test]
fn test_spawn_nodes_multiple() -> Result<()> {
    let mut service = ProcessService::new();

    let nodes = vec![
        ("node1", "sleep", vec!["1"]),
        ("node2", "sleep", vec!["1"]),
        ("node3", "sleep", vec!["1"]),
    ];

    let pids = service.spawn_nodes(nodes)?;

    assert_eq!(pids.len(), 3);
    assert!(pids.contains_key("node1"));
    assert!(pids.contains_key("node2"));
    assert!(pids.contains_key("node3"));
    assert_eq!(service.count(), 3);

    service.cleanup();
    Ok(())
}

#[test]
fn test_spawn_nodes_with_failures() -> Result<()> {
    let mut service = ProcessService::new();

    let nodes = vec![
        ("node1", "sleep", vec!["1"]),
        ("node2", "nonexistent_binary", vec![]), // This will fail
        ("node3", "sleep", vec!["1"]),
    ];

    let pids = service.spawn_nodes(nodes)?;

    // Should have 2 successful spawns
    assert_eq!(pids.len(), 2);
    assert!(pids.contains_key("node1"));
    assert!(pids.contains_key("node3"));
    assert!(!pids.contains_key("node2"));

    service.cleanup();
    Ok(())
}

#[test]
fn test_get_status() -> Result<()> {
    let mut service = ProcessService::new();

    service.spawn_node("test1", "sleep", &["2"])?;
    service.spawn_node("test2", "sleep", &["2"])?;

    let status = service.get_status();
    assert_eq!(status.len(), 2);
    assert!(status.contains_key("test1"));
    assert!(status.contains_key("test2"));

    service.cleanup();
    Ok(())
}

#[test]
fn test_stop_nonexistent_process() {
    let mut service = ProcessService::new();

    let result = service.stop("nonexistent");
    assert!(result.is_err());
}

#[test]
fn test_spawn_invalid_binary() {
    let mut service = ProcessService::new();

    let result = service.spawn_node("test", "nonexistent_binary_12345", &[]);
    assert!(result.is_err());
}

#[test]
fn test_tracker_access() -> Result<()> {
    let mut service = ProcessService::new();

    service.spawn_node("test", "sleep", &["1"])?;

    let tracker = service.tracker();
    assert!(!tracker.is_empty());

    service.cleanup();
    Ok(())
}

#[test]
fn test_restart_process() -> Result<()> {
    let mut service = ProcessService::new();

    // Spawn initial process
    let pid1 = service.spawn_node("test", "sleep", &["5"])?;

    // Restart it
    let pid2 = service.restart("test", "sleep", &["5"])?;

    // PIDs should be different
    assert_ne!(pid1, pid2);

    service.cleanup();
    Ok(())
}

#[test]
fn test_restart_all() -> Result<()> {
    let mut service = ProcessService::new();

    // Spawn initial processes
    service.spawn_node("node1", "sleep", &["5"])?;
    service.spawn_node("node2", "sleep", &["5"])?;

    let nodes = vec![("node1", "sleep", vec!["5"]), ("node2", "sleep", vec!["5"])];

    let pids = service.restart_all(nodes)?;

    assert_eq!(pids.len(), 2);
    assert!(pids.contains_key("node1"));
    assert!(pids.contains_key("node2"));

    service.cleanup();
    Ok(())
}

#[test]
fn test_count_accuracy() -> Result<()> {
    let mut service = ProcessService::new();

    assert_eq!(service.count(), 0);

    service.spawn_node("test1", "sleep", &["1"])?;
    assert_eq!(service.count(), 1);

    service.spawn_node("test2", "sleep", &["1"])?;
    assert_eq!(service.count(), 2);

    service.stop("test1")?;
    // Count might still include stopped process until cleaned

    service.cleanup();
    Ok(())
}

#[test]
fn test_is_empty_states() -> Result<()> {
    let mut service = ProcessService::new();

    assert!(service.is_empty());

    service.spawn_node("test", "sleep", &["1"])?;
    assert!(!service.is_empty());

    service.cleanup();
    Ok(())
}

#[test]
fn test_spawn_same_name_twice() -> Result<()> {
    let mut service = ProcessService::new();

    service.spawn_node("duplicate", "sleep", &["1"])?;
    // Spawning with same name should still work
    // (ProcessTracker will handle the duplicate)
    service.spawn_node("duplicate", "sleep", &["1"])?;

    service.cleanup();
    Ok(())
}

#[test]
fn test_spawn_with_args() -> Result<()> {
    let mut service = ProcessService::new();

    // Use echo with multiple args
    let pid = service.spawn_node("test_args", "echo", &["hello", "world"])?;
    assert!(pid > 0);

    service.cleanup();
    Ok(())
}

#[test]
fn test_spawn_with_empty_args() -> Result<()> {
    let mut service = ProcessService::new();

    let pid = service.spawn_node("test_no_args", "sleep", &["1"])?;
    assert!(pid > 0);

    service.cleanup();
    Ok(())
}

#[test]
fn test_is_running_after_process_exits() -> Result<()> {
    let mut service = ProcessService::new();

    // Spawn a process that exits quickly
    service.spawn_node("quick_exit", "true", &[])?;

    // Give it time to exit
    std::thread::sleep(std::time::Duration::from_millis(100));

    // Should no longer be running
    let running = service.is_running("quick_exit");
    assert!(!running);

    service.cleanup();
    Ok(())
}

#[test]
fn test_spawn_nodes_empty_list() -> Result<()> {
    let mut service = ProcessService::new();

    let nodes = vec![];
    let pids = service.spawn_nodes(nodes)?;

    assert_eq!(pids.len(), 0);
    assert_eq!(service.count(), 0);

    Ok(())
}

#[test]
fn test_multiple_cleanup_calls() -> Result<()> {
    let mut service = ProcessService::new();

    service.spawn_node("test", "sleep", &["1"])?;

    service.cleanup();
    // Second cleanup should not panic
    service.cleanup();

    Ok(())
}

// Note: build_node and build_all tests are commented out because
// they would require a valid Cargo workspace setup during tests

// #[test]
// fn test_build_node() -> Result<()> {
//     let service = ProcessService::new();
//     service.build_node("test_node", false)?;
//     Ok(())
// }

// #[test]
// fn test_build_all() -> Result<()> {
//     let service = ProcessService::new();
//     service.build_all(false)?;
//     Ok(())
// }