brlapi 0.4.1

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

//! Error handling and validation tests

use brlapi::BrlApiError;
use std::ffi::CString;

#[test]
fn test_error_enum_completeness() {
    // Test that our error enum covers all BrlAPI error codes by testing from_c_error conversion
    use brlapi_sys::brlapi_error;

    // Test error code conversion from C constants
    assert!(matches!(
        BrlApiError::from_c_error(brlapi_error::BRLAPI_ERROR_SUCCESS as i32),
        BrlApiError::Success
    ));
    assert!(matches!(
        BrlApiError::from_c_error(brlapi_error::BRLAPI_ERROR_NOMEM as i32),
        BrlApiError::NoMem
    ));
    assert!(matches!(
        BrlApiError::from_c_error(brlapi_error::BRLAPI_ERROR_TTYBUSY as i32),
        BrlApiError::TTYBusy
    ));
    assert!(matches!(
        BrlApiError::from_c_error(brlapi_error::BRLAPI_ERROR_DEVICEBUSY as i32),
        BrlApiError::DeviceBusy
    ));
    assert!(matches!(
        BrlApiError::from_c_error(brlapi_error::BRLAPI_ERROR_UNKNOWN_INSTRUCTION as i32),
        BrlApiError::UnknownInstruction
    ));
    assert!(matches!(
        BrlApiError::from_c_error(brlapi_error::BRLAPI_ERROR_ILLEGAL_INSTRUCTION as i32),
        BrlApiError::IllegalInstruction
    ));
    assert!(matches!(
        BrlApiError::from_c_error(brlapi_error::BRLAPI_ERROR_INVALID_PARAMETER as i32),
        BrlApiError::InvalidParameter
    ));
    assert!(matches!(
        BrlApiError::from_c_error(brlapi_error::BRLAPI_ERROR_INVALID_PACKET as i32),
        BrlApiError::InvalidPacket
    ));
    assert!(matches!(
        BrlApiError::from_c_error(brlapi_error::BRLAPI_ERROR_CONNREFUSED as i32),
        BrlApiError::ConnectionRefused
    ));
    assert!(matches!(
        BrlApiError::from_c_error(brlapi_error::BRLAPI_ERROR_OPNOTSUPP as i32),
        BrlApiError::OperationNotSupported
    ));
    assert!(matches!(
        BrlApiError::from_c_error(brlapi_error::BRLAPI_ERROR_AUTHENTICATION as i32),
        BrlApiError::AuthenticationFailed
    ));
    assert!(matches!(
        BrlApiError::from_c_error(brlapi_error::BRLAPI_ERROR_READONLY_PARAMETER as i32),
        BrlApiError::ParameterCannotBeChanged
    ));

    // Test error message formatting
    let error = BrlApiError::ConnectionRefused;
    let message = format!("{error}");
    assert_eq!(
        message,
        "Failed to connect to BrlAPI server. Is BRLTTY running? Try 'sudo systemctl start brltty'"
    );

    let debug_output = format!("{error:?}");
    assert!(debug_output.contains("ConnectionRefused"));
}

#[test]
fn test_error_display_and_debug() {
    // Test error formatting and debug output
    let errors = [
        BrlApiError::Success,
        BrlApiError::NoMem,
        BrlApiError::ConnectionRefused,
        BrlApiError::AuthenticationFailed,
        BrlApiError::OperationNotSupported,
    ];

    for error in &errors {
        // Test Display trait
        let display_msg = format!("{error}");
        assert!(!display_msg.is_empty(), "Error message should not be empty");
        println!("Display: {display_msg}");

        // Test Debug trait
        let debug_msg = format!("{error:?}");
        assert!(!debug_msg.is_empty(), "Debug output should not be empty");
        println!("Debug: {debug_msg}");

        // Test Error trait (inherited from std::error::Error via thiserror)
        let error_msg = error.to_string();
        assert_eq!(display_msg, error_msg, "Display and to_string should match");
    }
}

#[test]
fn test_error_categorization() {
    // Test that errors are properly categorized for handling
    let connection_errors = [
        BrlApiError::ConnectionRefused,
        BrlApiError::AuthenticationFailed,
        BrlApiError::GetAddrInfoError,
        BrlApiError::ConnectionTimeout,
    ];

    let resource_busy_errors = [
        BrlApiError::TTYBusy,
        BrlApiError::DeviceBusy,
        BrlApiError::NoMem,
    ];

    let operation_errors = [
        BrlApiError::OperationNotSupported,
        BrlApiError::InvalidParameter,
        BrlApiError::InvalidPacket,
        BrlApiError::ParameterCannotBeChanged,
    ];

    // Test error categorization methods
    for error in &connection_errors {
        assert!(
            error.is_connection_error(),
            "Should be a connection error: {:?}",
            error
        );
        let msg = format!("{error}");
        println!("Connection error: {msg}");
        assert!(!msg.is_empty());
    }

    for error in &resource_busy_errors {
        assert!(
            error.is_resource_busy(),
            "Should be a resource busy error: {:?}",
            error
        );
        let msg = format!("{error}");
        println!("Resource busy error: {msg}");
        assert!(!msg.is_empty());
    }

    for error in &operation_errors {
        assert!(
            error.is_operation_error(),
            "Should be an operation error: {:?}",
            error
        );
        let msg = format!("{error}");
        println!("Operation error: {msg}");
        assert!(!msg.is_empty());
    }
}

#[test]
fn test_conversion_errors() {
    // Test Rust-specific conversion errors
    use std::ffi::CString;

    // Test NullByteInString conversion
    let null_byte_result = CString::new("hello\0world");
    assert!(null_byte_result.is_err());

    let brlapi_error: BrlApiError = null_byte_result.unwrap_err().into();
    assert!(brlapi_error.is_conversion_error());
    assert!(matches!(brlapi_error, BrlApiError::NullByteInString(_)));

    // Test UTF-8 conversion errors
    let invalid_utf8 = vec![0xFF, 0xFE, 0xFD];
    let utf8_result = String::from_utf8(invalid_utf8);
    assert!(utf8_result.is_err());

    let brlapi_error: BrlApiError = utf8_result.unwrap_err().into();
    assert!(brlapi_error.is_conversion_error());
    assert!(matches!(brlapi_error, BrlApiError::StringConversion(_)));
}

#[test]
fn test_custom_errors() {
    // Test custom error creation
    let custom_error = BrlApiError::custom("This is a test error");
    assert!(matches!(custom_error, BrlApiError::Custom { .. }));

    let message = format!("{custom_error}");
    assert_eq!(message, "This is a test error");

    // Test unexpected return value error
    let unexpected_error = BrlApiError::unexpected_return_value(42);
    assert!(matches!(
        unexpected_error,
        BrlApiError::UnexpectedReturnValue { value: 42 }
    ));
    assert!(unexpected_error.is_operation_error());
}

#[test]
fn test_error_suggestions() {
    // Test that error suggestions are provided for common issues
    let connection_error = BrlApiError::ConnectionRefused;
    let suggestions = connection_error.suggestions();
    assert!(!suggestions.is_empty());
    assert!(suggestions.iter().any(|s| s.contains("brltty")));

    let auth_error = BrlApiError::AuthenticationFailed;
    let auth_suggestions = auth_error.suggestions();
    assert!(!auth_suggestions.is_empty());
    assert!(auth_suggestions.iter().any(|s| s.contains("brlapi")));

    let conversion_error = BrlApiError::NullByteInString(CString::new("test\0").unwrap_err());
    let conversion_suggestions = conversion_error.suggestions();
    assert!(!conversion_suggestions.is_empty());
    assert!(conversion_suggestions.iter().any(|s| s.contains("null")));
}