brlapi 0.4.1

Safe Rust bindings for the BrlAPI library
// SPDX-License-Identifier: LGPL-2.1

//! Integration tests for BrlAPI Rust bindings
//!
//! These tests verify that the BrlAPI bindings work correctly in various
//! scenarios. Some tests may fail if BRLTTY is not running or configured.

use brlapi::{BrlApiError, Connection, ConnectionSettings, TtyMode};

/// Test basic connection functionality
#[test]
fn test_connection_lifecycle() {
    match Connection::open() {
        Ok(connection) => {
            // Test that we can query basic information
            // Valid BrlAPI file descriptors should be > 2 (not standard handles 0,1,2)
            assert!(connection.file_descriptor() > 2);
            println!(
                "Connection established with fd: {}",
                connection.file_descriptor()
            );

            // Connection should be automatically closed when dropped
        }
        Err(BrlApiError::ConnectionTimeout) => {
            println!("BRLTTY not running - connection test skipped (timeout)");
            println!("  Start BRLTTY to run full integration tests: sudo systemctl start brltty");
        }
        Err(BrlApiError::ConnectionRefused) => {
            println!("BRLTTY not running - connection test skipped (refused)");
            println!("  Start BRLTTY to run full integration tests: sudo systemctl start brltty");
        }
        Err(BrlApiError::LibCError) => {
            println!("BRLTTY not running - connection test skipped (system error)");
            println!("  Start BRLTTY to run full integration tests: sudo systemctl start brltty");
        }
        Err(e) => {
            panic!("Unexpected connection error: {e}");
        }
    }
}

/// Test connection with different settings
#[test]
fn test_connection_with_settings() {
    let settings = ConnectionSettings::localhost();

    match Connection::open_with_settings(Some(&settings)) {
        Ok(connection) => {
            // Valid BrlAPI file descriptors should be > 2 (not standard handles 0,1,2)
            assert!(connection.file_descriptor() > 2);
            println!("Connection with localhost settings successful");
        }
        Err(BrlApiError::ConnectionTimeout) => {
            println!("BRLTTY not running - settings test skipped (timeout)");
        }
        Err(BrlApiError::ConnectionRefused) => {
            println!("BRLTTY not running - settings test skipped (refused)");
        }
        Err(BrlApiError::LibCError) => {
            println!("BRLTTY not running - settings test skipped (system error)");
        }
        Err(e) => {
            println!("Connection with settings failed (may be expected): {e}");
        }
    }
}

/// Test display information retrieval
#[test]
fn test_display_information() {
    match Connection::open() {
        Ok(connection) => {
            // Test display size
            match connection.display_size() {
                Ok((width, height)) => {
                    println!("Display size: {width}x{height} cells");
                    // Both width and height are unsigned, so always >= 0
                }
                Err(e) => {
                    println!("Display size query failed: {e}");
                    // Not necessarily a test failure - might be no display
                }
            }

            // Test driver name
            match connection.display_driver() {
                Ok(driver) => {
                    println!("Driver: {driver}");
                    assert!(!driver.is_empty());
                }
                Err(e) => {
                    println!("Driver query failed: {e}");
                }
            }

            // Test model identifier
            match connection.display_model() {
                Ok(model) => {
                    println!("Model: {model}");
                    // Model can be empty for some drivers
                }
                Err(e) => {
                    println!("Model query failed: {e}");
                }
            }
        }
        Err(BrlApiError::ConnectionTimeout) => {
            println!("BRLTTY not running - display info test skipped (timeout)");
        }
        Err(BrlApiError::ConnectionRefused) => {
            println!("BRLTTY not running - display info test skipped (refused)");
        }
        Err(BrlApiError::LibCError) => {
            println!("BRLTTY not running - display info test skipped (system error)");
        }
        Err(e) => {
            panic!("Unexpected connection error: {e}");
        }
    }
}

/// Test TTY mode operations
#[test]
fn test_tty_mode_operations() {
    match Connection::open() {
        Ok(connection) => {
            // Test automatic TTY mode detection
            match TtyMode::enter_auto(&connection, None) {
                Ok((tty_mode, tty_num)) => {
                    println!("TTY mode entered automatically (TTY {tty_num})");

                    // Test text writing in TTY mode (skip for NoBraille driver)
                    match connection.display_driver() {
                        Ok(driver) if driver == "NoBraille" => {
                            println!("Skipping text write for NoBraille driver (not supported)");
                        }
                        _ => match tty_mode.write_text("Test") {
                            Ok(()) => {
                                println!("Text written successfully");
                            }
                            Err(e) => {
                                println!(
                                    "Text write failed (may be expected for some drivers): {e}"
                                );
                            }
                        },
                    }

                    // TTY mode automatically exited when tty_mode drops
                    println!("TTY mode exited successfully");
                }
                Err(BrlApiError::UnknownTTY) => {
                    println!("TTY auto-detection failed (may be expected in test environment)");
                    test_specific_tty_modes(&connection);
                }
                Err(e) => {
                    println!("TTY mode entry failed: {e}");
                }
            }
        }
        Err(BrlApiError::ConnectionTimeout) => {
            println!("BRLTTY not running - TTY mode test skipped (timeout)");
        }
        Err(BrlApiError::ConnectionRefused) => {
            println!("BRLTTY not running - TTY mode test skipped (refused)");
        }
        Err(BrlApiError::LibCError) => {
            println!("BRLTTY not running - TTY mode test skipped (system error)");
        }
        Err(e) => {
            panic!("Unexpected connection error: {e}");
        }
    }
}

/// Test specific TTY mode operations
fn test_specific_tty_modes(connection: &Connection) {
    println!("Testing specific TTY numbers...");

    for tty_num in [2, 3, 1] {
        // Try common TTYs
        match TtyMode::with_tty(connection, Some(tty_num), None) {
            Ok(_tty_mode) => {
                println!("TTY mode entered for TTY {tty_num}");
                // TTY mode automatically cleaned up when _tty_mode drops
                return; // Success, we're done
            }
            Err(e) => {
                println!("TTY {tty_num} failed: {e}");
            }
        }
    }

    println!("All specific TTY attempts failed");
}

/// Test RAII TTY mode wrapper
#[test]
fn test_tty_mode_raii() {
    match Connection::open() {
        Ok(connection) => {
            // Test TryFrom approach
            match TtyMode::try_from(&connection) {
                Ok(tty_mode) => {
                    println!("TtyMode created via TryFrom");

                    // Test that we can use the connection through the wrapper
                    // Skip text operations for NoBraille driver
                    match connection.display_driver() {
                        Ok(driver) if driver == "NoBraille" => {
                            println!("Skipping text write for NoBraille driver");
                        }
                        _ => match tty_mode.write_text("Test") {
                            Ok(()) => {
                                println!("Text written through TtyMode wrapper");
                            }
                            Err(e) => {
                                println!("Text write through wrapper failed: {e}");
                            }
                        },
                    }
                    // TTY mode should be automatically exited when dropped
                }
                Err(BrlApiError::UnknownTTY) => {
                    // Try the auto-detection approach
                    match TtyMode::enter_auto(&connection, None) {
                        Ok((tty_mode, tty_num)) => {
                            println!("TtyMode created via enter_auto (TTY {tty_num})");
                            // Skip text write for NoBraille driver
                            match connection.display_driver() {
                                Ok(driver) if driver == "NoBraille" => {
                                    println!("Skipping text write for NoBraille driver");
                                }
                                _ => {
                                    let _ = tty_mode.write_text("Test");
                                }
                            }
                        }
                        Err(e) => {
                            println!("RAII TTY mode test failed: {e}");
                        }
                    }
                }
                Err(e) => {
                    println!("TtyMode creation failed: {e}");
                }
            }
        }
        Err(BrlApiError::ConnectionTimeout) => {
            println!("BRLTTY not running - RAII test skipped (timeout)");
        }
        Err(BrlApiError::ConnectionRefused) => {
            println!("BRLTTY not running - RAII test skipped (refused)");
        }
        Err(BrlApiError::LibCError) => {
            println!("BRLTTY not running - RAII test skipped (system error)");
        }
        Err(e) => {
            panic!("Unexpected connection error: {e}");
        }
    }
}

/// Test error handling and categorization
#[test]
fn test_error_categorization() {
    // Test error categorization methods
    let connection_error = BrlApiError::ConnectionRefused;
    assert!(connection_error.is_connection_error());
    assert!(!connection_error.is_resource_busy());
    assert!(!connection_error.is_operation_error());

    let resource_error = BrlApiError::TTYBusy;
    assert!(!resource_error.is_connection_error());
    assert!(resource_error.is_resource_busy());
    assert!(!resource_error.is_operation_error());

    let operation_error = BrlApiError::OperationNotSupported;
    assert!(!operation_error.is_connection_error());
    assert!(!operation_error.is_resource_busy());
    assert!(operation_error.is_operation_error());

    println!("Error categorization working correctly");
}

/// Test user-friendly error messages
#[test]
fn test_error_messages() {
    let error = BrlApiError::ConnectionRefused;
    let error_msg = error.to_string();
    let suggestions = error.suggestions();

    assert!(!error_msg.is_empty());
    assert!(!suggestions.is_empty());
    assert!(error_msg.contains("BRLTTY"));

    println!("User-friendly error: {error_msg}");
    println!("Suggestions: {:?}", suggestions);
}

/// Test version information
#[test]
fn test_version_info() {
    use brlapi::version;

    let (major, minor, revision) = version::library_version();
    println!("BrlAPI library version: {major}.{minor}.{revision}");

    let version_string = version::library_version_string();
    assert!(!version_string.is_empty());
    println!("Version string: {version_string}");

    let (exp_major, exp_minor, exp_revision) = version::expected_version();
    println!("Expected version: {exp_major}.{exp_minor}.{exp_revision}");

    // Versions should be reasonable
    assert!(major >= 0);
    assert!(minor >= 0);
    assert!(revision >= 0);
}

/// Test utility functions
#[test]
fn test_utilities() {
    use brlapi::util;

    let handle_size = util::handle_size();
    assert!(handle_size > 0);
    assert!(handle_size < 100000); // Reasonable upper bound
    println!("Handle size: {handle_size} bytes");

    // Test file descriptor validation
    assert!(!util::is_valid_file_descriptor(0)); // stdin is not a valid BrlAPI fd
    assert!(!util::is_valid_file_descriptor(1)); // stdout is not a valid BrlAPI fd
    assert!(!util::is_valid_file_descriptor(2)); // stderr is not a valid BrlAPI fd
    assert!(util::is_valid_file_descriptor(10)); // Regular fd > 2 is valid
    assert!(!util::is_valid_file_descriptor(-1)); // Invalid fd
    println!("File descriptor validation working");
}