ssh-commander-core 0.1.0

Async Rust domain layer for SSH, SFTP, FTP/FTPS, PostgreSQL, and connection management — the engine behind midnight-ssh.
Documentation
#![cfg(test)]

use crate::ssh::{AuthMethod, HostKeyStore, SshClient, SshConfig};
use std::sync::Arc;
use tokio::sync::RwLock;

// Test credentials - Replace with your own test server credentials
const TEST_HOST: &str = "localhost"; // Replace with your test SSH server
const TEST_USERNAME: &str = "testuser"; // Replace with your test username
const TEST_PASSWORD: &str = "testpass"; // Replace with your test password
const TEST_PORT: u16 = 22;

/// A scratch host-key store backed by a temp file.
fn test_host_keys() -> Arc<HostKeyStore> {
    let tmp = std::env::temp_dir().join(format!("r-shell-test-known_hosts-{}", std::process::id()));
    Arc::new(HostKeyStore::new(tmp))
}

fn create_test_config() -> SshConfig {
    SshConfig {
        host: TEST_HOST.to_string(),
        port: TEST_PORT,
        username: TEST_USERNAME.to_string(),
        auth_method: AuthMethod::Password {
            password: TEST_PASSWORD.to_string(),
        },
    }
}

// Unit test - doesn't require external SSH server
#[test]
fn test_ssh_config_creation() {
    let config = create_test_config();
    assert_eq!(config.host, "localhost");
    assert_eq!(config.port, 22);
    assert_eq!(config.username, "testuser");
}

// Note: The following tests are integration tests that require a running SSH server.
// They are marked as ignored to prevent CI failures.
// To run these tests locally, start an SSH server and run: cargo test -- --ignored --nocapture

#[tokio::test]
#[ignore]
async fn test_ssh_connection() {
    let client = Arc::new(RwLock::new(SshClient::new(test_host_keys())));
    let mut client_write = client.write().await;
    let config = create_test_config();

    let result = client_write.connect(&config).await;

    assert!(
        result.is_ok(),
        "SSH connection should succeed: {:?}",
        result.err()
    );

    // Disconnect
    let disconnect_result = client_write.disconnect().await;
    assert!(disconnect_result.is_ok(), "Disconnect should succeed");
}

#[tokio::test]
#[ignore]
async fn test_execute_command() {
    let client = Arc::new(RwLock::new(SshClient::new(test_host_keys())));
    let mut client_write = client.write().await;
    let config = create_test_config();

    // Connect
    client_write
        .connect(&config)
        .await
        .expect("Failed to connect");

    // Execute command
    let output = client_write
        .execute_command("echo 'test'")
        .await
        .expect("Failed to execute command");

    assert!(
        output.contains("test"),
        "Command output should contain 'test'"
    );

    // Disconnect
    client_write.disconnect().await.ok();
}

#[tokio::test]
#[ignore]
async fn test_invalid_credentials() {
    let client = Arc::new(RwLock::new(SshClient::new(test_host_keys())));
    let mut client_write = client.write().await;

    let config = SshConfig {
        host: TEST_HOST.to_string(),
        port: TEST_PORT,
        username: TEST_USERNAME.to_string(),
        auth_method: AuthMethod::Password {
            password: "wrongpassword".to_string(),
        },
    };

    let result = client_write.connect(&config).await;

    assert!(
        result.is_err(),
        "Connection with invalid password should fail"
    );
}

#[tokio::test]
#[ignore]
async fn test_get_system_stats() {
    let client = Arc::new(RwLock::new(SshClient::new(test_host_keys())));
    let mut client_write = client.write().await;
    let config = create_test_config();

    // Connect
    client_write
        .connect(&config)
        .await
        .expect("Failed to connect");

    // Get CPU usage
    let cpu_output = client_write
        .execute_command("top -bn1 | grep 'Cpu(s)' | awk '{print $2}' | cut -d'%' -f1")
        .await;
    assert!(cpu_output.is_ok(), "Should get CPU stats");

    // Get memory usage
    let mem_output = client_write
        .execute_command("free | grep Mem | awk '{print ($3/$2) * 100.0}'")
        .await;
    assert!(mem_output.is_ok(), "Should get memory stats");

    // Disconnect
    client_write.disconnect().await.ok();
}

#[tokio::test]
#[ignore]
async fn test_process_list() {
    let client = Arc::new(RwLock::new(SshClient::new(test_host_keys())));
    let mut client_write = client.write().await;
    let config = create_test_config();

    // Connect
    client_write
        .connect(&config)
        .await
        .expect("Failed to connect");

    // Get process list
    let output = client_write
        .execute_command("ps aux | sort -k3nr | head -10")
        .await
        .expect("Failed to get process list");

    assert!(!output.is_empty(), "Process list should not be empty");
    assert!(
        output.contains("PID") || output.contains("USER"),
        "Output should contain process info"
    );

    // Disconnect
    client_write.disconnect().await.ok();
}