#![cfg(feature = "docker")]
use foundation_deployment_docker::streaming::decoder::{JsonLineDecoder, LogFrameDecoder, LogOutput};
#[test]
fn log_decoder_single_stdout_frame() {
let mut dec = LogFrameDecoder::new();
let mut frame = vec![0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05];
frame.extend_from_slice(b"hello");
dec.feed(&frame);
let out = dec.decode().expect("should decode complete frame");
assert_eq!(out, LogOutput::StdOut { message: bytes::Bytes::from("hello") });
assert!(dec.decode().is_none(), "no more frames");
assert_eq!(dec.buffered_len(), 0);
}
#[test]
fn log_decoder_single_stderr_frame() {
let mut dec = LogFrameDecoder::new();
let mut frame = vec![0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04];
frame.extend_from_slice(b"oops");
dec.feed(&frame);
let out = dec.decode().expect("should decode");
assert_eq!(out, LogOutput::StdErr { message: bytes::Bytes::from("oops") });
}
#[test]
fn log_decoder_stdin_frame() {
let mut dec = LogFrameDecoder::new();
let mut frame = vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03];
frame.extend_from_slice(b"foo");
dec.feed(&frame);
let out = dec.decode().expect("should decode");
assert_eq!(out, LogOutput::StdIn { message: bytes::Bytes::from("foo") });
}
#[test]
fn log_decoder_console_frame() {
let mut dec = LogFrameDecoder::new();
let mut frame = vec![0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02];
frame.extend_from_slice(b"hi");
dec.feed(&frame);
let out = dec.decode().expect("should decode");
assert_eq!(out, LogOutput::Console { message: bytes::Bytes::from("hi") });
}
#[test]
fn log_decoder_partial_header_returns_none() {
let mut dec = LogFrameDecoder::new();
dec.feed(&[0x01, 0x00, 0x00, 0x00]);
assert!(dec.decode().is_none(), "partial header should return None");
assert_eq!(dec.buffered_len(), 4);
}
#[test]
fn log_decoder_partial_payload_returns_none() {
let mut dec = LogFrameDecoder::new();
let mut chunk = vec![0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14];
chunk.extend_from_slice(b"short");
dec.feed(&chunk);
assert!(dec.decode().is_none(), "partial payload should return None");
assert_ne!(dec.buffered_len(), 0);
}
#[test]
fn log_decoder_multiple_frames_single_chunk() {
let mut dec = LogFrameDecoder::new();
let mut chunk = vec![0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02];
chunk.extend_from_slice(b"ab");
chunk.extend_from_slice(&[0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02]);
chunk.extend_from_slice(b"cd");
dec.feed(&chunk);
let f1 = dec.decode().expect("frame 1");
assert_eq!(f1, LogOutput::StdOut { message: bytes::Bytes::from("ab") });
let f2 = dec.decode().expect("frame 2");
assert_eq!(f2, LogOutput::StdErr { message: bytes::Bytes::from("cd") });
assert!(dec.decode().is_none(), "no more frames");
}
#[test]
fn log_decoder_incremental_feeds() {
let mut dec = LogFrameDecoder::new();
dec.feed(&[0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03]);
assert!(dec.decode().is_none());
dec.feed(b"xyz");
let out = dec.decode().expect("frame after incremental feed");
assert_eq!(out, LogOutput::StdOut { message: bytes::Bytes::from("xyz") });
}
#[test]
fn log_decoder_compact_frees_memory() {
let mut dec = LogFrameDecoder::new();
let mut frame = vec![0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03];
frame.extend_from_slice(b"foo");
dec.feed(&frame);
let _ = dec.decode();
assert_eq!(dec.buffered_len(), 0, "all bytes decoded");
let mut frame2 = vec![0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03];
frame2.extend_from_slice(b"bar");
dec.feed(&frame2);
let _ = dec.decode();
assert_eq!(dec.buffered_len(), 0);
}
#[test]
fn log_decoder_large_payload() {
let mut dec = LogFrameDecoder::new();
let payload = vec![b'x'; 65536];
let mut frame = vec![0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00]; frame.extend_from_slice(&payload);
dec.feed(&frame);
let out = dec.decode().expect("large frame");
assert_eq!(out, LogOutput::StdOut { message: bytes::Bytes::from(payload) });
}
#[test]
fn json_line_decoder_single_line() {
let mut dec = JsonLineDecoder::new();
dec.feed(b"{\"status\":\"ok\"}\n");
let line = dec.decode().expect("should decode complete line");
assert_eq!(line, bytes::Bytes::from("{\"status\":\"ok\"}"));
assert!(dec.decode().is_none());
}
#[test]
fn json_line_decoder_multiple_lines_single_chunk() {
let mut dec = JsonLineDecoder::new();
dec.feed(b"{\"a\":1}\n{\"b\":2}\n");
let l1 = dec.decode().expect("line 1");
assert_eq!(l1, bytes::Bytes::from("{\"a\":1}"));
let l2 = dec.decode().expect("line 2");
assert_eq!(l2, bytes::Bytes::from("{\"b\":2}"));
assert!(dec.decode().is_none());
}
#[test]
fn json_line_decoder_partial_line_returns_none() {
let mut dec = JsonLineDecoder::new();
dec.feed(b"{\"partial\":");
assert!(dec.decode().is_none(), "no newline yet");
assert_eq!(dec.buffered_len(), 11);
}
#[test]
fn json_line_decoder_incremental_feeds() {
let mut dec = JsonLineDecoder::new();
dec.feed(b"{\"status\":"); assert!(dec.decode().is_none());
dec.feed(b"\"running\"}\n"); let line = dec.decode().expect("complete after feed");
assert_eq!(line, bytes::Bytes::from("{\"status\":\"running\"}"));
}
#[test]
fn json_line_decoder_empty_lines_skipped() {
let mut dec = JsonLineDecoder::new();
dec.feed(b"\n\n{\"key\":\"val\"}\n\n");
let line = dec.decode().expect("line after empty");
assert_eq!(line, bytes::Bytes::from("{\"key\":\"val\"}"));
assert!(dec.decode().is_none());
}
#[test]
fn json_line_decoder_compact_frees_memory() {
let mut dec = JsonLineDecoder::new();
dec.feed(b"line1\nline2\n");
let _l1 = dec.decode().expect("line1");
let _l2 = dec.decode().expect("line2");
dec.compact();
assert_eq!(dec.buffered_len(), 0);
dec.feed(b"line3\n");
let l3 = dec.decode().expect("line3 after compact");
assert_eq!(l3, bytes::Bytes::from("line3"));
}