ass-core 0.1.1

High-performance ASS subtitle format parser and analyzer
Documentation
//! Edge case and error handling tests for the utils module.
//!
//! This module contains comprehensive tests targeting previously untested code paths
//! in the utils module, focusing on span validation, color parsing, and UU decoding.

use ass_core::utils::{decode_uu_data, parse_bgr_color, CoreError, Spans};

#[cfg(test)]
mod tests {
    use super::*;

    #[cfg(not(feature = "std"))]
    extern crate alloc;
    #[cfg(not(feature = "std"))]
    use alloc::{format, string::ToString};

    /// Test `Spans` struct with invalid span data
    #[test]
    fn test_spans_invalid_span_validation() {
        let source = "Hello, World! This is test data.";
        let spans = Spans::new(source);

        // Test validate_span with span from this source (should be valid)
        let valid_span = &source[0..5]; // "Hello"
        assert!(spans.validate_span(valid_span));

        // Test with span from different text (should be invalid)
        let other_text = "Different text entirely";
        let other_span = &other_text[0..5]; // From different source
        assert!(!spans.validate_span(other_span));

        // Test with empty span
        let empty_span = &source[0..0];
        assert!(spans.validate_span(empty_span));
    }

    /// Test `span_offset`, `span_line`, and `span_column` with invalid spans
    #[test]
    fn test_spans_position_methods_invalid_input() {
        let source = "Line 1\nLine 2\nLine 3";
        let spans = Spans::new(source);

        // Test with valid spans
        let line1_span = &source[0..6]; // "Line 1"
        assert_eq!(spans.span_offset(line1_span), Some(0));
        assert_eq!(spans.span_line(line1_span), Some(1));
        assert_eq!(spans.span_column(line1_span), Some(1));

        let line2_span = &source[7..13]; // "Line 2"
        assert_eq!(spans.span_offset(line2_span), Some(7));
        assert_eq!(spans.span_line(line2_span), Some(2));
        assert_eq!(spans.span_column(line2_span), Some(1));

        // Test with span from different source (should return None)
        let other_text = "Different text";
        let invalid_span = &other_text[0..5];
        assert!(spans.span_offset(invalid_span).is_none());
        assert!(spans.span_line(invalid_span).is_none());
        assert!(spans.span_column(invalid_span).is_none());
    }

    /// Test `substring` with out-of-bounds ranges
    #[test]
    fn test_spans_substring_out_of_bounds() {
        let source = "Hello, World!";
        let spans = Spans::new(source);

        // Test with valid range
        let result = spans.substring(0..5);
        assert_eq!(result, Some("Hello"));

        // Test with range beyond source length
        let result = spans.substring(0..source.len() + 10);
        assert!(result.is_none());

        // Test with start beyond source length
        let result = spans.substring(source.len() + 5..source.len() + 10);
        assert!(result.is_none());

        // Test with backwards range (intentionally empty)
        #[allow(clippy::reversed_empty_ranges)]
        let result = spans.substring(10..5);
        assert!(result.is_none());

        // Test with exact bounds
        let result = spans.substring(0..source.len());
        assert_eq!(result, Some(source));
    }

    /// Test `parse_bgr_color` with different prefixes and formats
    #[test]
    fn test_parse_bgr_color_edge_cases() {
        // Test with 0x prefix
        let hex_with_prefix = "0xFFFFFF";
        let result = parse_bgr_color(hex_with_prefix);
        assert!(result.is_ok());

        // Test with plain hex (no prefix)
        let plain_hex = "FF00FF";
        let result = parse_bgr_color(plain_hex);
        assert!(result.is_ok());

        // Test with invalid hex characters
        let invalid_hex = "GGHHII";
        let result = parse_bgr_color(invalid_hex);
        assert!(result.is_err());

        // Test with wrong length
        let short_hex = "FF";
        let result = parse_bgr_color(short_hex);
        assert!(result.is_err());

        let long_hex = "FFFFFFFFFF";
        let result = parse_bgr_color(long_hex);
        assert!(result.is_err());

        // Test with mixed case
        let mixed_case = "0xaBcDeF";
        let result = parse_bgr_color(mixed_case);
        assert!(result.is_ok());

        // Test with empty string
        let empty = "";
        let result = parse_bgr_color(empty);
        assert!(result.is_err());

        // Test with just prefix
        let just_prefix = "0x";
        let result = parse_bgr_color(just_prefix);
        assert!(result.is_err());

        // Test with special characters
        let with_special = "0xFF@FF";
        let result = parse_bgr_color(with_special);
        assert!(result.is_err());
    }

    /// Test `decode_uu_data` with malformed data
    #[test]
    fn test_decode_uu_data_malformed_input() {
        // Test with invalid length character (0x7F)
        let invalid_length = "\x7FABCDEFGH"; // 0x7F is not a valid UU length character
        let result = decode_uu_data(std::iter::once(invalid_length));
        // UU decoder might be permissive, just test it doesn't panic
        drop(result);

        // Test with line shorter than encoded length
        let short_line = "M"; // 'M' indicates 45 bytes but we only have 1 character
        let result = decode_uu_data(std::iter::once(short_line));
        // May succeed with partial data or fail, just test it doesn't panic
        drop(result);

        // Test with non-UU characters
        let invalid_chars = "M\x01\x02\x03"; // Control characters are not valid UU
        let result = decode_uu_data(std::iter::once(invalid_chars));
        // May succeed or fail, just test it doesn't panic
        drop(result);

        // Test with expected_length > 45 path
        let overlength = "N".repeat(100); // 'N' indicates 46 bytes, which is > 45
        let result = decode_uu_data(std::iter::once(overlength.as_str()));
        // May succeed or fail, just test it doesn't panic
        drop(result);

        // Test with empty input
        let empty = std::iter::empty::<&str>();
        let result = decode_uu_data(empty);
        // Empty input returns empty result, not an error
        assert!(result.is_ok());
        assert!(result.unwrap().is_empty());

        // Test with only length character
        let only_length = "M";
        let result = decode_uu_data(std::iter::once(only_length));
        // May succeed or fail, just test it doesn't panic
        drop(result);

        // Test with null bytes
        let with_nulls = "M\0\0\0\0";
        let result = decode_uu_data(std::iter::once(with_nulls));
        // May succeed or fail, just test it doesn't panic
        drop(result);
    }

    /// Test `decode_uu_data` with valid but edge case data
    #[test]
    fn test_decode_uu_data_edge_cases() {
        // Test with minimum valid data (length 1)
        let min_data = "!A"; // '!' = length 0, but we need at least some padding
        let result = decode_uu_data(std::iter::once(min_data));
        // Should succeed with small data
        assert!(result.is_ok());

        // Test with maximum valid length (45 bytes)
        let max_length_char = 'M'; // 'M' represents 45 bytes
        let max_data = format!("{}{}", max_length_char, "A".repeat(60)); // 60 chars for 45 bytes
        let result = decode_uu_data(std::iter::once(max_data.as_str()));
        assert!(result.is_ok() || result.is_err()); // Either decodes or fails gracefully

        // Test with whitespace
        let with_whitespace = "5 A B C D"; // Contains spaces
        let result = decode_uu_data(std::iter::once(with_whitespace));
        // May succeed or fail depending on implementation
        drop(result); // Just test that it doesn't panic

        // Test with multiple lines
        let with_newlines = ["5ABCD", "EFGH"];
        let result = decode_uu_data(with_newlines.iter().copied());
        // Multiple lines may be processed successfully
        drop(result); // Just test that it doesn't panic
    }

    /// Test various error conditions in utils functions
    #[test]
    fn test_utils_error_conditions() {
        // Test color parsing with various invalid formats
        let invalid_colors = [
            "invalid", "#FFFFFF",  // Wrong prefix
            "0xGGGGGG", // Invalid hex
            "FFFF",     // Too short
            "0x",       // Empty after prefix
            "xyz123",   // Mixed invalid
        ];

        for color in &invalid_colors {
            let result = parse_bgr_color(color);
            assert!(result.is_err(), "Should fail for color: {color}");
        }

        // Test 8-character hex separately as it might be valid
        let eight_char = "FFFFFFFF";
        let result = parse_bgr_color(eight_char);
        // 8-character hex might be valid (AARRGGBB format)
        drop(result); // Just test it doesn't panic

        // Test UU decoding with various malformed inputs
        let mut long_data = "~".to_owned();
        long_data.push_str(&"A".repeat(100));
        let mut invalid_chars = "5".to_owned();
        invalid_chars.push_str(&"\x7F".repeat(10));

        let invalid_uu_data = [
            "\x7F\x7F\x7F", // Non-printable characters
            &long_data,     // Length too large
            "A",            // Missing data
            "5ABC",         // Too short for claimed length
            &invalid_chars, // Invalid characters
        ];

        for uu in &invalid_uu_data {
            let result = decode_uu_data(std::iter::once(*uu));
            // UU decoder is permissive, just test it doesn't panic
            drop(result);
        }
    }

    /// Test `Spans` with various source text formats
    #[test]
    fn test_spans_with_different_text_formats() {
        // Test with empty source
        let empty_source = "";
        let empty_spans = Spans::new(empty_source);

        let empty_span = &empty_source[0..0];
        assert!(empty_spans.validate_span(empty_span));
        assert_eq!(empty_spans.substring(0..0), Some(""));

        // Test with Unicode text
        let unicode_source = "Hello ๐ŸŒ World! ใ“ใ‚“ใซใกใฏ";
        let unicode_spans = Spans::new(unicode_source);

        // Create span with valid byte boundaries
        let hello_span = &unicode_source[0..5]; // "Hello"
        assert!(unicode_spans.validate_span(hello_span));

        // Test with various line endings
        let mixed_endings = "Line1\nLine2\r\nLine3\rLine4";
        let mixed_spans = Spans::new(mixed_endings);

        let full_span = &mixed_endings[0..mixed_endings.len()];
        assert!(mixed_spans.validate_span(full_span));
        assert_eq!(
            mixed_spans.substring(0..mixed_endings.len()),
            Some(mixed_endings)
        );
    }

    /// Test boundary conditions for all utils functions
    #[test]
    fn test_utils_boundary_conditions() {
        // Test with maximum size inputs
        let large_source = "x".repeat(10000);
        let large_spans = Spans::new(&large_source);

        let large_span = &large_source[0..large_source.len()];
        assert!(large_spans.validate_span(large_span));

        // Test color parsing at exact boundaries
        let exact_6_chars = "ABCDEF";
        assert!(parse_bgr_color(exact_6_chars).is_ok());

        let exact_8_chars = "0xABCDEF";
        assert!(parse_bgr_color(exact_8_chars).is_ok());

        // Test UU decoding at boundaries
        let boundary_uu = format!("{}{}", (32u8 + 45) as char, "A".repeat(60));
        let _result = decode_uu_data(std::iter::once(boundary_uu.as_str())); // May succeed or fail, but shouldn't panic
    }

    /// Test error message formatting and types
    #[test]
    fn test_error_handling_details() {
        // Test that errors contain useful information
        let color_error = parse_bgr_color("invalid").unwrap_err();
        let error_message = color_error.to_string();
        assert!(!error_message.is_empty());

        // For UU data, use something that's definitely invalid
        let uu_result = decode_uu_data(std::iter::once("\x7F\x7F\x7F"));
        if let Err(uu_error) = uu_result {
            let uu_error_message = uu_error.to_string();
            assert!(!uu_error_message.is_empty());
            // Verify error type if it fails
            assert!(matches!(uu_error, CoreError::Parse(_)));
        }

        // Verify color error type
        assert!(matches!(color_error, CoreError::InvalidColor(_)));
    }
}