ssh-mcp-rs 3.0.3

MCP server exposing SSH control for Linux systems via Model Context Protocol
Documentation
//! Integration tests for SSH MCP server
//!
//! These tests require a running SSH server and are marked as `#[ignore]` by default.
//! Run them with: `cargo test -- --ignored`

/// Test that the timeout wrapper correctly formats commands
#[test]
fn test_timeout_wrapper_formatting() {
    use ssh_mcp::ssh::command::wrap_command_with_timeout;

    // Test basic command
    let cmd = wrap_command_with_timeout("echo hello", 10.0);
    assert!(cmd.contains("timeout -k 2s 10s"));
    assert!(cmd.contains("sh -lc")); // Login shell
    assert!(cmd.contains("echo hello"));

    // Test command with special characters
    let cmd = wrap_command_with_timeout("sleep 30", 2.0);
    assert!(cmd.contains("timeout -k 2s 2s"));
    assert!(cmd.contains("sh -lc"));
    assert!(cmd.contains("sleep 30"));
}

/// Test that zero duration is handled by wrapper
#[test]
fn test_wrap_command_with_zero_duration() {
    use ssh_mcp::ssh::command::wrap_command_with_timeout;

    // Edge case: wrapper accepts zero (validation is elsewhere)
    let cmd = wrap_command_with_timeout("echo test", 0.0);
    assert!(cmd.contains("timeout -k 2s 0s"));
    assert!(cmd.contains("sh -lc"));
}

/// Test that fractional seconds are handled correctly
#[test]
fn test_wrap_command_with_fractional_duration() {
    use ssh_mcp::ssh::command::wrap_command_with_timeout;

    // Test sub-second timeout
    let cmd = wrap_command_with_timeout("sleep 1", 0.5);
    assert!(cmd.contains("timeout -k 2s 0.5s"));
    assert!(cmd.contains("sh -lc"));

    // Test millisecond precision
    let cmd = wrap_command_with_timeout("sleep 1", 1.5);
    assert!(cmd.contains("timeout -k 2s 1.5s"));
    assert!(cmd.contains("sh -lc"));
}

/// Test escaping for timeout wrapper
#[test]
fn test_escape_for_timeout_wrapper() {
    use ssh_mcp::ssh::sanitize::escape_for_timeout_wrapper;

    assert_eq!(
        escape_for_timeout_wrapper("echo 'hello'"),
        "echo '\"'\"'hello'\"'\"'"
    );
    assert_eq!(escape_for_timeout_wrapper(r"echo \$HOME"), r"echo \$HOME");
    assert_eq!(escape_for_timeout_wrapper("echo `date`"), "echo `date`");
}

/// Test escaping for shell (pkill patterns)
#[test]
fn test_escape_command_for_shell() {
    use ssh_mcp::ssh::sanitize::escape_command_for_shell;

    assert_eq!(escape_command_for_shell("echo $HOME"), r"echo \$HOME");
    assert_eq!(escape_command_for_shell("echo `date`"), r"echo \`date\`");
    assert_eq!(escape_command_for_shell("echo (test)"), r"echo \(test\)");
}

/// Integration test that requires a real SSH connection
///
/// This test is ignored by default. To run it:
/// 1. Set up a test SSH server (e.g., via testcontainers or local Docker)
/// 2. Configure SSH credentials in environment variables or test config
/// 3. Run with: `cargo test -- --ignored integration_test_ssh_connection`
#[tokio::test]
#[ignore = "requires real SSH server with configured credentials"]
async fn test_ssh_connection_timeout_wrapper() {
    // This test would verify that:
    // 1. SSH connection can be established
    // 2. timeout command is detected on remote host
    // 3. Commands are wrapped with timeout correctly
    // 4. Timed-out commands return timeout error

    // TODO: Implement with testcontainers or mock SSH server
    // Example structure:
    // let config = SshConfig::new("localhost", "testuser")
    //     .with_port(2222)
    //     .with_password("testpass");
    // let manager = SshConnectionManager::new(config).await;
    // manager.connect().await?;
    // assert!(manager.is_connected().await);
    // let timeout_available = manager.check_timeout_availability().await;
    // assert!(timeout_available);

    unreachable!("This test requires SSH server setup");
}

/// Integration test for elevated su shell with timeout
///
/// This test is ignored by default. To run it:
/// 1. Set up a test SSH server with root access configured
/// 2. Configure SSH + SU credentials
/// 3. Run with: `cargo test -- --ignored test_su_shell_timeout`
#[tokio::test]
#[ignore = "requires SSH server with su password configured"]
async fn test_su_shell_timeout_wrapper() {
    // This test would verify that:
    // 1. Elevation to root via su works
    // 2. Commands in elevated shell use timeout wrapper
    // 3. Timed-out commands are properly terminated

    // TODO: Implement with testcontainers or mock SSH server
    // Example structure:
    // let config = SshConfig::new("localhost", "testuser")
    //     .with_port(2222)
    //     .with_password("testpass")
    //     .with_su_password("rootpass");
    // let manager = SshConnectionManager::new(config).await;
    // manager.connect().await?;
    // manager.ensure_elevated().await?;
    // assert!(manager.is_elevated());
    //
    // // Test command that should complete
    // let output = manager.exec_command("echo 'test'", Duration::from_secs(5)).await?;
    // assert!(output.success());
    //
    // // Test command that times out
    // let result = manager.exec_command("sleep 100", Duration::from_secs(2)).await;
    // assert!(matches!(result, Err(SshMcpError::Timeout(_))));

    unreachable!("This test requires SSH server with root access");
}

/// Integration test for timeout wrapper fallback behavior
///
/// This test is ignored by default. To run it:
/// 1. Set up a test SSH server WITHOUT timeout command
/// 2. Run with: `cargo test -- --ignored test_timeout_fallback`
#[tokio::test]
#[ignore = "requires SSH server without timeout command"]
async fn test_timeout_wrapper_fallback_to_pkill() {
    // This test would verify that:
    // 1. If timeout command is not available, fallback is used
    // 2. tokio timeout + pkill mechanism works correctly
    // 3. Commands are terminated on timeout

    // TODO: Implement with testcontainers or mock SSH server
    // Example structure:
    // 1. Create SSH server without /usr/bin/timeout
    // 2. Connect and verify timeout detection fails
    // 3. Execute command with timeout
    // 4. Verify pkill abort works

    unreachable!("This test requires SSH server without timeout command");
}