//! Decoder behavior on malformed and adversarial input.
//!
//! The format defines a result for every byte sequence, even ones the encoder
//! never emits. A decoder must reproduce those results without panicking. These
//! cases pin the exact output for streams that exercise the awkward corners of
//! the match decoder: the index-47 token, a zero distance, and a surrogate-range
//! literal.

/// Decode `s` and return its UTF-16 code units, so surrogate-range output is
/// visible instead of being flattened by a `String` comparison.
fn decode_units(s: &str) -> Vec<u16> {
    lzbase62::decompress(s).encode_utf16().collect()
}

// Index-47 token (`v`) routes into the match branch with a negative distance.
// The decoder carries `pos` signed so `slice(-pos)` reads from the front.
#[test]
fn index_47_token_decodes_without_panic() {
    let cases: &[(&str, &[u16])] = &[
        ("vA", &[]),
        ("vAB", &[0x20]),
        ("vAC", &[0x20, 0x20]),
        ("vBA", &[]),
    ];
    for (input, expected) in cases {
        assert_eq!(&decode_units(input), expected, "decode {input:?}");
    }
}

// A match token with a zero distance copies the whole output buffer so far, not
// nothing. `slice(-0)` is `slice(0)`, the entire buffer.
#[test]
fn zero_distance_match_copies_window() {
    // `x` is the variable-length leader (index 49), `2` the fixed-length-2
    // leader (index 54). Paired with distance digit `A` (value 0) both select
    // the leading space window.
    let twenty_five_spaces = vec![0x20u16; 25];
    let cases: &[(&str, &[u16])] = &[
        ("xAZ", &twenty_five_spaces), // length byte Z = 25
        ("xAB", &[0x20]),             // length byte B = 1
        ("2A", &[0x20, 0x20]),        // fixed length 2
        ("2B", &[0x20, 0x20]),
    ];
    for (input, expected) in cases {
        assert_eq!(&decode_units(input), expected, "decode {input:?}");
    }
}

// A literal token can decode into the surrogate range. The units API keeps the
// code unit, while the String API flattens it to U+FFFD.
#[test]
fn surrogate_range_literal_is_preserved() {
    assert_eq!(lzbase62::decompress_units("wmle"), vec![0xD83D]);
    assert_eq!(decode_units("wmle"), vec![0xFFFD]);
}

// These cases already pass. They guard the surrounding match and literal
// decoder behavior so a future change cannot regress them unnoticed. Each
// expected value is the canonical result for the byte sequence.
#[test]
fn known_good_malformed_cases_hold() {
    let cases: &[(&str, &[u16])] = &[
        // A bare `v` with no follower: the second read runs past the end before
        // any arithmetic, so the result is empty.
        ("v", &[]),
        // All non-alphabet input decodes to empty.
        ("!!! ??? ...", &[]),
        // `x` (variable-length leader) with a missing or truncated distance
        // decodes to empty.
        ("x", &[]),
        ("xA", &[]),
        ("xY", &[]),
        // `2` (fixed-length-2 leader) with a missing distance still reads the
        // window: NaN distance selects the whole buffer, truncated to length 2.
        ("2", &[0x20, 0x20]),
        // `X` is a standalone literal digit (value 23) with a null index, which
        // arithmetically is 0, so the code unit is 23.
        ("X", &[23]),
        // `xYZ` is a well-formed match: distance 24, length 25, copied from the
        // space window.
        ("xYZ", &[0x20; 25]),
    ];
    for (input, expected) in cases {
        assert_eq!(&decode_units(input), expected, "decode {input:?}");
    }
}
