use crate::protocol::{self, ServerMsg};
#[test]
fn history_message_round_trip() {
let lines = vec![
b"line one".to_vec(),
b"line two with \x1b[1mbold\x1b[0m".to_vec(),
b"line three".to_vec(),
];
let msg = ServerMsg::History(lines.clone());
let encoded = protocol::encode(&msg).unwrap();
let (data, consumed) = protocol::codec::decode_frame(&encoded).unwrap().unwrap();
assert_eq!(consumed, encoded.len());
let decoded: ServerMsg = protocol::codec::decode(data).unwrap();
match decoded {
ServerMsg::History(decoded_lines) => {
assert_eq!(decoded_lines.len(), 3);
assert_eq!(decoded_lines[0], b"line one");
assert_eq!(decoded_lines[2], b"line three");
}
other => panic!("expected History, got {:?}", other),
}
}
#[test]
fn screen_update_message_round_trip() {
let update_data = b"\x1b[?2026h\x1b[?25l\x1b[2J\x1b[HHello\x1b[?2026l".to_vec();
let msg = ServerMsg::ScreenUpdate(update_data.clone());
let encoded = protocol::encode(&msg).unwrap();
let (data, _) = protocol::codec::decode_frame(&encoded).unwrap().unwrap();
let decoded: ServerMsg = protocol::codec::decode(data).unwrap();
match decoded {
ServerMsg::ScreenUpdate(decoded_data) => {
assert_eq!(decoded_data, update_data);
}
other => panic!("expected ScreenUpdate, got {:?}", other),
}
}
#[test]
fn history_chunking_round_trip() {
let mut all_lines = Vec::new();
for i in 0..500 {
all_lines.push(format!("history line {:04}", i).into_bytes());
}
let size_limit = protocol::codec::MAX_FRAME_SIZE / 2;
let mut chunks: Vec<Vec<Vec<u8>>> = Vec::new();
let mut chunk = Vec::new();
let mut chunk_size = 0;
for line in &all_lines {
let line_size = line.len() + 16;
if chunk_size + line_size > size_limit && !chunk.is_empty() {
chunks.push(std::mem::take(&mut chunk));
chunk_size = 0;
}
chunk_size += line_size;
chunk.push(line.clone());
}
if !chunk.is_empty() {
chunks.push(chunk);
}
let mut reassembled = Vec::new();
for chunk_lines in &chunks {
let msg = ServerMsg::History(chunk_lines.clone());
let encoded = protocol::encode(&msg).unwrap();
let (data, _) = protocol::codec::decode_frame(&encoded).unwrap().unwrap();
let decoded: ServerMsg = protocol::codec::decode(data).unwrap();
match decoded {
ServerMsg::History(lines) => reassembled.extend(lines),
other => panic!("expected History, got {:?}", other),
}
}
assert_eq!(reassembled.len(), all_lines.len());
for (i, line) in reassembled.iter().enumerate() {
assert_eq!(
line, &all_lines[i],
"line {} mismatch after chunked round-trip",
i
);
}
}
#[test]
fn e2e_reattach_protocol_encode_decode_sequence() {
use retach::screen::{AnsiRenderer, Screen};
let mut screen = Screen::new(10, 3, 100);
for i in 1..=6 {
if i < 6 {
screen.process(format!("L{:02}\r\n", i).as_bytes());
} else {
screen.process(format!("L{:02}", i).as_bytes());
}
}
let _ = screen.take_pending_scrollback();
let hist = screen.get_history();
let mut render_data = Vec::new();
if !hist.is_empty() {
render_data.extend_from_slice(b"\x1b[");
render_data.extend_from_slice(screen.rows().to_string().as_bytes());
render_data.extend_from_slice(b";1H");
render_data.extend(std::iter::repeat_n(
b'\n',
screen.rows().saturating_sub(1) as usize,
));
}
let mut renderer = AnsiRenderer::new();
render_data.extend_from_slice(&renderer.render(&screen, true));
let mut wire = Vec::new();
wire.extend(
protocol::encode(&ServerMsg::Connected {
name: "test".into(),
new_session: false,
})
.unwrap(),
);
if !hist.is_empty() {
wire.extend(protocol::encode(&ServerMsg::History(hist)).unwrap());
}
wire.extend(protocol::encode(&ServerMsg::ScreenUpdate(render_data)).unwrap());
let mut offset = 0;
let mut messages = Vec::new();
while offset < wire.len() {
let (data, consumed) = protocol::codec::decode_frame(&wire[offset..])
.unwrap()
.expect("should decode complete frame");
let msg: ServerMsg = protocol::codec::decode(data).unwrap();
messages.push(msg);
offset += consumed;
}
assert!(matches!(messages[0], ServerMsg::Connected { .. }));
assert!(matches!(messages[1], ServerMsg::History(_)));
assert!(matches!(messages[2], ServerMsg::ScreenUpdate(_)));
let mut stdout = Vec::new();
for msg in &messages {
match msg {
ServerMsg::History(lines) => {
for line in lines {
stdout.extend_from_slice(line);
stdout.extend_from_slice(b"\r\n");
}
}
ServerMsg::ScreenUpdate(data) => {
stdout.extend_from_slice(data);
}
_ => {}
}
}
let text = String::from_utf8_lossy(&stdout);
assert!(text.contains("L01"), "L01 should be in output");
assert!(text.contains("L03"), "L03 should be in output");
let pos_clear = text.find("\x1b[2J").expect("screen clear");
let after_clear = &text[pos_clear..];
assert!(after_clear.contains("L04"), "L04 should be on screen");
assert!(after_clear.contains("L06"), "L06 should be on screen");
}