qssh 0.0.2-alpha

Experimental quantum-safe SSH using post-quantum crypto. Research project - NOT for production. See LIMITATIONS.md
Documentation
//! Integration tests for shell and PTY functionality

// These modules don't exist in current implementation
// use qssh::shell_handler::{ShellHandler, ShellType};
use qssh::pty::Pty;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use std::process::Command;
use std::time::Duration;
use tokio::time::timeout;

#[cfg(unix)]
#[tokio::test]
async fn test_pty_creation() {
    // Test that we can create a PTY
    let pty_result = Pty::new();
    assert!(pty_result.is_ok());
    
    let mut pty = pty_result.unwrap();
    
    // Test that we can spawn a shell in the PTY
    let child = Command::new("/bin/sh")
        .arg("-c")
        .arg("echo 'PTY test'")
        .spawn();
    
    assert!(child.is_ok());
}

#[tokio::test]
async fn test_shell_command_execution() {
    // Test executing commands through the shell handler
    let commands = vec![
        ("echo 'hello world'", "hello world"),
        ("expr 2 + 2", "4"),
        ("printf 'test'", "test"),
    ];
    
    for (cmd, expected) in commands {
        let output = Command::new("/bin/sh")
            .arg("-c")
            .arg(cmd)
            .output()
            .expect("Failed to execute command");
        
        let stdout = String::from_utf8_lossy(&output.stdout);
        assert!(stdout.contains(expected), "Command {} failed, got: {}", cmd, stdout);
    }
}

#[tokio::test]
async fn test_shell_io_redirection() {
    // Test input/output redirection
    let test_input = "test input data\n";
    let echo_cmd = format!("echo '{}'", test_input.trim());
    
    let output = Command::new("/bin/sh")
        .arg("-c")
        .arg(&echo_cmd)
        .output()
        .expect("Failed to execute command");
    
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert_eq!(stdout.trim(), test_input.trim());
}

#[tokio::test]
async fn test_shell_environment_variables() {
    // Test that environment variables are properly set
    use std::env;
    
    // Set a test environment variable
    env::set_var("QSSH_TEST_VAR", "test_value");
    
    let output = Command::new("/bin/sh")
        .arg("-c")
        .arg("echo $QSSH_TEST_VAR")
        .env("QSSH_TEST_VAR", "test_value")
        .output()
        .expect("Failed to execute command");
    
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert_eq!(stdout.trim(), "test_value");
    
    // Clean up
    env::remove_var("QSSH_TEST_VAR");
}

#[tokio::test]
async fn test_shell_exit_codes() {
    // Test that exit codes are properly returned
    let test_cases = vec![
        ("exit 0", 0),
        ("exit 1", 1),
        ("exit 42", 42),
        ("false", 1),
        ("true", 0),
    ];
    
    for (cmd, expected_code) in test_cases {
        let output = Command::new("/bin/sh")
            .arg("-c")
            .arg(cmd)
            .output()
            .expect("Failed to execute command");
        
        assert_eq!(
            output.status.code(),
            Some(expected_code),
            "Command {} returned wrong exit code", 
            cmd
        );
    }
}

#[tokio::test]
async fn test_shell_signal_handling() {
    // Test that signals are properly handled
    use tokio::process::Command as TokioCommand;
    use tokio::signal;
    
    // Start a long-running process
    let mut child = TokioCommand::new("/bin/sh")
        .arg("-c")
        .arg("sleep 10")
        .spawn()
        .expect("Failed to spawn process");
    
    // Give it time to start
    tokio::time::sleep(Duration::from_millis(100)).await;
    
    // Kill the process
    child.kill().await.expect("Failed to kill process");
    
    // Verify it was killed
    let status = child.wait().await.expect("Failed to wait for process");
    assert!(!status.success());
}

#[cfg(unix)]
#[tokio::test]
async fn test_pty_window_size() {
    // Test PTY window size handling
    let pty = Pty::new().unwrap();
    
    // Default size should be reasonable
    // In a real implementation, we'd use ioctl to get/set window size
    // For now, we just verify PTY creation succeeds
    assert!(pty.master_fd() > 0);
}

#[tokio::test]
async fn test_shell_pipeline() {
    // Test shell pipeline execution
    let pipeline = "echo 'hello world' | grep 'world' | wc -l";
    
    let output = Command::new("/bin/sh")
        .arg("-c")
        .arg(pipeline)
        .output()
        .expect("Failed to execute pipeline");
    
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert_eq!(stdout.trim(), "1");
}

#[tokio::test]
async fn test_shell_error_handling() {
    // Test error handling for invalid commands
    let invalid_commands = vec![
        "nonexistentcommand123",
        "/invalid/path/to/command",
        "$()",
    ];
    
    for cmd in invalid_commands {
        let output = Command::new("/bin/sh")
            .arg("-c")
            .arg(cmd)
            .output()
            .expect("Failed to execute command");
        
        // Invalid commands should have non-zero exit code
        assert_ne!(output.status.code(), Some(0), "Command {} should have failed", cmd);
    }
}

#[tokio::test]
async fn test_shell_working_directory() {
    // Test changing working directory
    let output = Command::new("/bin/sh")
        .arg("-c")
        .arg("cd /tmp && pwd")
        .output()
        .expect("Failed to execute command");
    
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert_eq!(stdout.trim(), "/tmp");
}

#[tokio::test]
async fn test_shell_user_context() {
    // Test that shell runs in correct user context
    let output = Command::new("/bin/sh")
        .arg("-c")
        .arg("whoami")
        .output()
        .expect("Failed to execute command");
    
    let stdout = String::from_utf8_lossy(&output.stdout);
    // Should return current user
    assert!(!stdout.trim().is_empty());
}

#[tokio::test]
async fn test_concurrent_shell_sessions() {
    // Test multiple concurrent shell sessions
    let mut handles = vec![];
    
    for i in 0..5 {
        let handle = tokio::spawn(async move {
            let output = Command::new("/bin/sh")
                .arg("-c")
                .arg(format!("echo 'Session {}'", i))
                .output()
                .expect("Failed to execute command");
            
            String::from_utf8_lossy(&output.stdout).trim().to_string()
        });
        handles.push(handle);
    }
    
    let mut results = Vec::new();
    for handle in handles {
        results.push(handle.await);
    }
    
    for (i, result) in results.iter().enumerate() {
        let output = result.as_ref().unwrap();
        assert_eq!(output, &format!("Session {}", i));
    }
}

#[tokio::test]
async fn test_shell_timeout() {
    // Test command timeout handling
    let long_command = tokio::process::Command::new("/bin/sh")
        .arg("-c")
        .arg("sleep 10")
        .spawn();
    
    assert!(long_command.is_ok());
    let mut child = long_command.unwrap();
    
    // Try to wait with timeout
    let wait_result = timeout(Duration::from_secs(1), child.wait()).await;
    
    // Should timeout
    assert!(wait_result.is_err());
    
    // Clean up
    child.kill().await.ok();
}

#[tokio::test]
async fn test_shell_special_characters() {
    // Test handling of special characters in shell commands
    let test_cases = vec![
        ("echo 'test$variable'", "test$variable"),
        ("echo \"test'with'quotes\"", "test'with'quotes"),
        ("echo 'test\"with\"doublequotes'", "test\"with\"doublequotes"),
        ("echo 'test\\with\\backslashes'", "test\\with\\backslashes"),
    ];
    
    for (cmd, expected) in test_cases {
        let output = Command::new("/bin/sh")
            .arg("-c")
            .arg(cmd)
            .output()
            .expect("Failed to execute command");
        
        let stdout = String::from_utf8_lossy(&output.stdout);
        assert_eq!(stdout.trim(), expected, "Failed for command: {}", cmd);
    }
}