use serializer::llm::parser::{LlmParser, ParseError};
#[cfg(test)]
mod error_position_tests {
use super::*;
#[test]
fn test_unclosed_bracket_has_position() {
let input = "table:2(id name)[1 Alice";
let result = LlmParser::parse(input);
assert!(result.is_err(), "Should fail on unclosed bracket");
if let Err(ParseError::UnclosedBracket { pos }) = result {
assert!(pos <= input.len(), "Position should be within input bounds");
assert!(pos > 0, "Position should be non-zero");
}
}
#[test]
fn test_unclosed_paren_has_position() {
let input = "table:2(id name[data]";
let result = LlmParser::parse(input);
assert!(result.is_err(), "Should fail on unclosed parenthesis");
if let Err(ParseError::UnclosedParen { pos }) = result {
assert!(pos <= input.len(), "Position should be within input bounds");
assert!(pos > 0, "Position should be non-zero");
}
}
#[test]
fn test_unexpected_char_has_position() {
let input = "key\x00:value";
let result = LlmParser::parse(input);
if let Err(e) = result {
match e {
ParseError::UnexpectedChar { pos, ch } => {
assert!(pos <= input.len(), "Position should be within input bounds");
assert_eq!(ch, '\x00', "Should report the unexpected character");
}
_ => {
}
}
}
}
#[test]
fn test_unexpected_eof_has_position() {
let input = "key=";
let result = LlmParser::parse(input);
if let Err(e) = result {
match e {
ParseError::UnexpectedEof => {
}
ParseError::MissingValue { pos } => {
assert!(pos <= input.len(), "Position should be within input bounds");
}
_ => {
}
}
}
}
#[test]
fn test_invalid_table_format_has_message() {
let input = "table:abc(id name)[data]";
let result = LlmParser::parse(input);
if let Err(e) = result {
match e {
ParseError::InvalidTable { msg } => {
assert!(!msg.is_empty(), "Error message should not be empty");
}
_ => {
}
}
}
}
#[test]
fn test_multiline_error_position() {
let input = "line1=value1\nline2=value2\nline3[unclosed";
let result = LlmParser::parse(input);
if let Err(e) = result {
match e {
ParseError::UnclosedBracket { pos } => {
let line3_start = "line1=value1\nline2=value2\n".len();
assert!(pos >= line3_start, "Position should be in line 3");
assert!(pos <= input.len(), "Position should be within input");
}
_ => {
}
}
}
}
#[test]
fn test_all_parse_errors_have_position_or_context() {
let test_cases = vec![
("table:2(id)[", "unclosed bracket"),
("table:2(id[data]", "unclosed paren"),
("table:2(id name)[1", "incomplete row"),
];
for (input, description) in test_cases {
let result = LlmParser::parse(input);
assert!(
result.is_err(),
"Test case '{}' should produce an error",
description
);
if let Err(e) = result {
let has_useful_info = match &e {
ParseError::UnexpectedChar { pos, .. } => *pos <= input.len(),
ParseError::UnclosedBracket { pos } => *pos <= input.len(),
ParseError::UnclosedParen { pos } => *pos <= input.len(),
ParseError::MissingValue { pos } => *pos <= input.len(),
ParseError::InvalidTable { msg } => !msg.is_empty(),
ParseError::SchemaMismatch { .. } => true,
ParseError::Utf8Error { offset } => *offset <= input.len(),
ParseError::InputTooLarge { .. } => true,
ParseError::UnexpectedEof => true,
ParseError::InvalidValue { value } => !value.is_empty(),
};
assert!(
has_useful_info,
"Error for '{}' should provide useful information: {:?}",
description, e
);
}
}
}
}
#[cfg(test)]
mod input_validation_tests {
use super::*;
#[test]
#[ignore] fn test_maximum_input_size_rejection() {
let large_input = "x".repeat(100_000_001);
let result = LlmParser::parse(&large_input);
assert!(
result.is_err(),
"Should reject input exceeding maximum size"
);
if let Err(ParseError::InputTooLarge { size, max }) = result {
assert_eq!(size, large_input.len(), "Should report actual size");
assert_eq!(max, 100_000_000, "Should report maximum size");
}
}
#[test]
#[ignore] fn test_maximum_input_size_bytes() {
let large_input = vec![b'x'; 100_000_001];
let result = LlmParser::parse_bytes(&large_input);
assert!(
result.is_err(),
"Should reject byte input exceeding maximum size"
);
if let Err(ParseError::InputTooLarge { size, max }) = result {
assert_eq!(size, large_input.len(), "Should report actual size");
assert_eq!(max, 100_000_000, "Should report maximum size");
}
}
#[test]
fn test_invalid_utf8_with_byte_offset() {
let mut bytes = b"key=value\n".to_vec();
let invalid_pos = bytes.len();
bytes.push(0xFF); bytes.extend_from_slice(b"\nmore=data");
let result = LlmParser::parse_bytes(&bytes);
assert!(result.is_err(), "Should fail on invalid UTF-8");
if let Err(ParseError::Utf8Error { offset }) = result {
assert_eq!(
offset, invalid_pos,
"Should report correct byte offset of invalid UTF-8"
);
}
}
#[test]
fn test_invalid_utf8_at_start() {
let bytes = vec![0xFF, 0xFE, b'k', b'e', b'y'];
let result = LlmParser::parse_bytes(&bytes);
assert!(result.is_err(), "Should fail on invalid UTF-8 at start");
if let Err(ParseError::Utf8Error { offset }) = result {
assert_eq!(
offset, 0,
"Should report offset 0 for invalid UTF-8 at start"
);
}
}
#[test]
fn test_invalid_utf8_in_middle() {
let mut bytes = b"first=ok\nsecond=".to_vec();
let invalid_pos = bytes.len();
bytes.push(0x80);
let result = LlmParser::parse_bytes(&bytes);
assert!(result.is_err(), "Should fail on invalid UTF-8 in middle");
if let Err(ParseError::Utf8Error { offset }) = result {
assert!(
offset >= invalid_pos.saturating_sub(1),
"Should report offset near invalid byte"
);
}
}
#[test]
fn test_empty_input() {
let result = LlmParser::parse("");
assert!(result.is_ok(), "Empty input should parse successfully");
let doc = result.unwrap();
assert!(doc.is_empty(), "Empty input should produce empty document");
}
#[test]
fn test_whitespace_only_input() {
let result = LlmParser::parse(" \n\t\n ");
assert!(
result.is_ok(),
"Whitespace-only input should parse successfully"
);
let doc = result.unwrap();
assert!(
doc.is_empty(),
"Whitespace-only input should produce empty document"
);
}
#[test]
fn test_input_at_size_limit() {
let input = "x".repeat(1_000_000); let result = LlmParser::parse(&input);
assert!(result.is_ok(), "Should accept input under size limit");
}
#[test]
#[ignore] fn test_input_just_over_size_limit() {
let input = "x".repeat(100_000_001);
let result = LlmParser::parse(&input);
assert!(result.is_err(), "Should reject input just over size limit");
if let Err(ParseError::InputTooLarge { size, max }) = result {
assert_eq!(size, 100_000_001);
assert_eq!(max, 100_000_000);
}
}
#[test]
fn test_deeply_nested_structures() {
let mut input = String::from("obj:1[nested=");
for _ in 0..100 {
input.push_str("value_");
}
input.push(']');
let result = LlmParser::parse(&input);
let _ = result;
}
#[test]
fn test_very_long_line() {
let long_value = "x".repeat(10_000);
let input = format!("key={}", long_value);
let result = LlmParser::parse(&input);
assert!(result.is_ok(), "Should handle very long lines");
}
#[test]
fn test_many_small_entries() {
let mut input = String::new();
for i in 0..1000 {
input.push_str(&format!("key{}=value{}\n", i, i));
}
let result = LlmParser::parse(&input);
assert!(result.is_ok(), "Should handle many small entries");
}
}
#[cfg(test)]
mod schema_mismatch_tests {
use super::*;
#[test]
fn test_schema_mismatch_too_few_columns() {
let input = "table:2(id name email)[1 Alice]";
let result = LlmParser::parse(input);
assert!(result.is_err(), "Should fail when row has too few columns");
if let Err(ParseError::SchemaMismatch { expected, got }) = result {
assert_eq!(expected, 3, "Should expect 3 columns from schema");
assert_eq!(got, 2, "Should report 2 columns in row");
}
}
#[test]
fn test_schema_mismatch_too_many_columns() {
let input = "table:2(id name)[1 Alice Bob Carol]";
let result = LlmParser::parse(input);
assert!(result.is_err(), "Should fail when row has too many columns");
if let Err(ParseError::SchemaMismatch { expected, got }) = result {
assert_eq!(expected, 2, "Should expect 2 columns from schema");
assert!(got > 2, "Should report more than 2 columns in row");
}
}
#[test]
fn test_schema_mismatch_multiline_format() {
let input = "table:2(id name email)[\n1 Alice\n]";
let result = LlmParser::parse(input);
assert!(
result.is_err(),
"Should fail on schema mismatch in multiline format"
);
if let Err(ParseError::SchemaMismatch { expected, got }) = result {
assert_eq!(expected, 3, "Should expect 3 columns");
assert_eq!(got, 2, "Should report 2 columns");
}
}
#[test]
fn test_schema_mismatch_comma_separated() {
let input = "table:2(id name email)[1 Alice, 2 Bob]";
let result = LlmParser::parse(input);
if let Err(ParseError::SchemaMismatch { expected, got }) = result {
assert_eq!(expected, 3, "Should expect 3 columns from schema");
assert!(got < 3, "Should report fewer columns in row");
}
}
#[test]
fn test_schema_mismatch_semicolon_separated() {
let input = "table:2(id name email)[1 Alice; 2 Bob Carol]";
let result = LlmParser::parse(input);
if let Err(ParseError::SchemaMismatch { expected, got }) = result {
assert_eq!(expected, 3, "Should expect 3 columns");
assert_eq!(got, 2, "Should report 2 columns in first row");
}
}
#[test]
fn test_schema_mismatch_colon_separated() {
let input = "table:2(timestamp level message)[2025-01-15 INFO: 2025-01-15 ERROR]";
let result = LlmParser::parse(input);
if let Err(ParseError::SchemaMismatch { expected, got }) = result {
assert_eq!(expected, 3, "Should expect 3 columns");
assert!(got != 3, "Should report incorrect column count");
}
}
#[test]
fn test_empty_schema() {
let input = "table:1()[data]";
let result = LlmParser::parse(input);
assert!(result.is_err(), "Should fail on empty schema");
if let Err(e) = result {
match e {
ParseError::InvalidTable { msg } => {
assert!(
msg.to_lowercase().contains("empty")
|| msg.to_lowercase().contains("schema"),
"Error message should mention empty schema"
);
}
_ => {
}
}
}
}
#[test]
fn test_schema_mismatch_with_nested_arrays() {
let input = "table:1(id items)[1 [a b c]]";
let result = LlmParser::parse(input);
if let Err(e) = result {
match e {
ParseError::SchemaMismatch { expected, got: _ } => {
assert_eq!(expected, 2, "Should expect 2 columns");
}
_ => {
}
}
}
}
#[test]
fn test_schema_mismatch_error_message_clarity() {
let input = "users:3(id name email age)[1 Alice alice@example.com]";
let result = LlmParser::parse(input);
assert!(result.is_err(), "Should fail on schema mismatch");
if let Err(ParseError::SchemaMismatch { expected, got }) = result {
assert_eq!(expected, 4, "Should expect 4 columns");
assert_eq!(got, 3, "Should report 3 columns");
let error_msg = format!(
"Schema mismatch: expected {} columns, got {}",
expected, got
);
assert!(
error_msg.contains("4"),
"Error message should contain expected count"
);
assert!(
error_msg.contains("3"),
"Error message should contain actual count"
);
}
}
#[test]
fn test_multiple_rows_first_row_mismatch() {
let input = "table:3(id name)[1 Alice, 2 Bob Carol, 3 Dave]";
let result = LlmParser::parse(input);
if let Err(ParseError::SchemaMismatch { expected, got }) = result {
assert_eq!(expected, 2, "Should expect 2 columns");
}
}
#[test]
fn test_multiple_rows_middle_row_mismatch() {
let input = "table:3(id name)[1 Alice, 2 Bob Carol Dave, 3 Eve]";
let result = LlmParser::parse(input);
if let Err(ParseError::SchemaMismatch { expected, got }) = result {
assert_eq!(expected, 2, "Should expect 2 columns");
assert!(got != 2, "Should report incorrect column count");
}
}
#[test]
fn test_schema_mismatch_preserves_context() {
let input = "config=test\ntable:2(id name email)[1 Alice]\nother=value";
let result = LlmParser::parse(input);
if let Err(e) = result {
match e {
ParseError::SchemaMismatch { expected, got } => {
assert_eq!(expected, 3);
assert_eq!(got, 2);
}
_ => {
}
}
}
}
}
#[cfg(test)]
mod integration_tests {
use super::*;
#[test]
fn test_error_handling_comprehensive() {
let error_cases = vec![
("table:2(id)[", "UnclosedBracket"),
("table:2(id[data]", "UnclosedParen"),
("table:2(id name)[1]", "SchemaMismatch"),
];
for (input, expected_error_type) in error_cases {
let result = LlmParser::parse(input);
assert!(
result.is_err(),
"Input '{}' should produce {} error",
input,
expected_error_type
);
if let Err(e) = result {
let error_string = format!("{}", e);
assert!(
!error_string.is_empty(),
"Error should have non-empty display string"
);
}
}
}
#[test]
fn test_error_recovery_not_required() {
let input = "bad[syntax\ngood=value";
let result = LlmParser::parse(input);
assert!(result.is_err(), "Should fail on first error");
}
}