use a2a_protocol_client::streaming::{SseParseError, SseParser};
fn parse_all(input: &[u8]) -> Vec<a2a_protocol_client::streaming::SseFrame> {
let mut p = SseParser::new();
p.feed(input);
let mut frames = Vec::new();
while let Some(f) = p.next_frame() {
frames.push(f.expect("unexpected parse error"));
}
frames
}
#[test]
fn utf8_bom_at_start_is_handled() {
let mut input = vec![0xEF, 0xBB, 0xBF];
input.extend_from_slice(b"data: hello\n\n");
let frames = parse_all(&input);
assert_eq!(frames.len(), 1);
}
#[test]
fn data_with_embedded_json_newlines() {
let input = b"data: {\"key\":\n\
data: \"value\"}\n\n";
let frames = parse_all(input);
assert_eq!(frames.len(), 1);
assert!(frames[0].data.contains("key"));
assert!(frames[0].data.contains("value"));
}
#[test]
fn numeric_id_zero_is_valid() {
let frames = parse_all(b"id: 0\ndata: test\n\n");
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].id.as_deref(), Some("0"));
}
#[test]
fn very_long_data_field() {
let data = "a".repeat(100_000);
let input = format!("data: {data}\n\n");
let frames = parse_all(input.as_bytes());
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].data.len(), 100_000);
}
#[test]
fn empty_data_field() {
let frames = parse_all(b"data:\n\n");
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].data, "");
}
#[test]
fn data_with_no_space_after_colon() {
let frames = parse_all(b"data:nospace\n\n");
assert_eq!(frames.len(), 1);
assert!(frames[0].data == "nospace" || frames[0].data == " nospace");
}
#[test]
fn crlf_line_endings() {
let frames = parse_all(b"data: hello\r\n\r\n");
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].data, "hello");
}
#[test]
fn id_persists_across_events() {
let input = b"id: abc\ndata: first\n\ndata: second\n\n";
let frames = parse_all(input);
assert_eq!(frames.len(), 2);
assert_eq!(frames[0].id.as_deref(), Some("abc"));
assert_eq!(frames[1].id.as_deref(), Some("abc"));
}
#[test]
fn event_type_does_not_persist() {
let input = b"event: special\ndata: first\n\ndata: second\n\n";
let frames = parse_all(input);
assert_eq!(frames.len(), 2);
assert_eq!(frames[0].event_type.as_deref(), Some("special"));
assert!(frames[1].event_type.is_none());
}
#[test]
fn unknown_field_is_ignored() {
let frames = parse_all(b"custom: ignored\ndata: valid\n\n");
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].data, "valid");
}
#[test]
fn multiple_colons_in_value() {
let frames = parse_all(b"data: http://example.com:8080/path\n\n");
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].data, "http://example.com:8080/path");
}
#[test]
fn retry_with_non_numeric_value_is_ignored() {
let frames = parse_all(b"retry: abc\ndata: test\n\n");
assert_eq!(frames.len(), 1);
assert!(frames[0].retry.is_none());
}
#[test]
fn consecutive_blank_lines_produce_no_extra_events() {
let frames = parse_all(b"data: first\n\n\n\ndata: second\n\n");
assert_eq!(frames.len(), 2);
}
#[test]
fn field_name_only_no_colon() {
let frames = parse_all(b"data\ndata: real\n\n");
assert_eq!(frames.len(), 1);
assert!(frames[0].data.contains("real"));
}
#[test]
fn field_name_only_data_dispatches() {
let frames = parse_all(b"data\n\n");
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].data, "");
}
#[test]
fn field_name_only_unknown_is_ignored() {
let frames = parse_all(b"foobar\ndata: valid\n\n");
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].data, "valid");
}
#[test]
fn extremely_long_field_name_is_ignored() {
let long_field = "x".repeat(10_000);
let input = format!("{long_field}: value\ndata: valid\n\n");
let frames = parse_all(input.as_bytes());
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].data, "valid");
}
#[test]
fn extremely_long_field_name_no_colon() {
let long_field = "x".repeat(10_000);
let input = format!("{long_field}\ndata: valid\n\n");
let frames = parse_all(input.as_bytes());
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].data, "valid");
}
#[test]
fn with_max_event_size_rejects_oversized_event() {
let mut p = SseParser::with_max_event_size(64);
let big = format!("data: {}\n\n", "z".repeat(100));
p.feed(big.as_bytes());
let result = p.next_frame().expect("should have a result");
assert!(result.is_err());
match result.unwrap_err() {
SseParseError::EventTooLarge { limit, actual } => {
assert_eq!(limit, 64);
assert!(actual > 64);
}
}
}
#[test]
fn with_max_event_size_allows_small_events() {
let mut p = SseParser::with_max_event_size(1024);
p.feed(b"data: small\n\n");
let result = p.next_frame().expect("should have a result");
let frame = result.expect("should not error");
assert_eq!(frame.data, "small");
}
#[test]
fn with_max_event_size_recovery_after_oversized() {
let mut p = SseParser::with_max_event_size(32);
let big = format!("data: {}\n\n", "a".repeat(64));
let small = "data: ok\n\n";
p.feed(big.as_bytes());
p.feed(small.as_bytes());
let first = p.next_frame().expect("first result");
assert!(first.is_err(), "oversized event should error");
let second = p.next_frame().expect("second result");
assert_eq!(second.unwrap().data, "ok");
}
#[test]
fn fragmented_arbitrary_chunk_sizes() {
let input = b"data: fragmented across chunks\n\n";
let mut p = SseParser::new();
for chunk in input.chunks(3) {
p.feed(chunk);
}
let frame = p.next_frame().expect("expected frame").expect("no error");
assert_eq!(frame.data, "fragmented across chunks");
}
#[test]
fn fragmented_split_between_field_and_value() {
let mut p = SseParser::new();
p.feed(b"data:");
p.feed(b" split\n");
p.feed(b"\n");
let frame = p.next_frame().expect("expected frame").expect("no error");
assert_eq!(frame.data, "split");
}
#[test]
fn multiple_frames_in_one_buffer() {
let input = b"data: one\n\ndata: two\n\ndata: three\n\n";
let frames = parse_all(input);
assert_eq!(frames.len(), 3);
assert_eq!(frames[0].data, "one");
assert_eq!(frames[1].data, "two");
assert_eq!(frames[2].data, "three");
}
#[test]
fn incomplete_frame_at_end_of_buffer() {
let mut p = SseParser::new();
p.feed(b"data: incomplete");
assert!(p.next_frame().is_none(), "incomplete frame should not emit");
p.feed(b"\n\n");
let frame = p.next_frame().expect("expected frame").expect("no error");
assert_eq!(frame.data, "incomplete");
}
#[test]
fn mixed_line_endings_cr_lf_crlf() {
let mut p = SseParser::new();
p.feed(b"data: mixed\r\n\r\n");
let frame = p.next_frame().expect("expected frame").expect("no error");
assert_eq!(frame.data, "mixed");
}
#[test]
fn connection_drop_mid_frame_no_panic() {
let mut p = SseParser::new();
p.feed(b"event: partial\ndata: incomp");
assert!(p.next_frame().is_none());
assert_eq!(p.pending_count(), 0);
}
#[test]
fn id_with_null_byte_clears_last_id() {
let mut p = SseParser::new();
p.feed(b"id: abc\ndata: first\n\n");
let f1 = p.next_frame().unwrap().unwrap();
assert_eq!(f1.id.as_deref(), Some("abc"));
p.feed(b"id: \0\ndata: second\n\n");
let f2 = p.next_frame().unwrap().unwrap();
assert!(f2.id.is_none(), "id with null byte should clear last id");
}
#[test]
fn single_byte_at_a_time_delivery() {
let input = b"event: test\ndata: byte-by-byte\n\n";
let mut p = SseParser::new();
for &byte in input.iter() {
p.feed(std::slice::from_ref(&byte));
}
let frame = p.next_frame().expect("expected frame").expect("no error");
assert_eq!(frame.data, "byte-by-byte");
assert_eq!(frame.event_type.as_deref(), Some("test"));
}