use std::sync::Arc;
use std::thread;
use foundation_connectrpc::shared::compression::GzipStreamCompressor;
use foundation_connectrpc::{
negotiate_compression, BufferPool, CompressionError, CompressionRegistry, Compressor,
GzipCompressor, SizeLimits,
};
#[test]
fn gzip_roundtrip() {
let gz = GzipCompressor::default();
let data = b"the quick brown fox jumps over the lazy dog".repeat(20);
let compressed = gz.compress(&data).unwrap();
assert!(compressed.len() < data.len(), "should shrink repetitive data");
let back = gz.decompress(&compressed, 0).unwrap();
assert_eq!(back, data);
assert_eq!(gz.name(), "gzip");
}
#[test]
fn gzip_decompress_bomb_is_bounded_by_read_max() {
let gz = GzipCompressor::default();
let bomb = gz.compress(&vec![0u8; 1_000_000]).unwrap();
let err = gz.decompress(&bomb, 4096).unwrap_err();
assert!(matches!(err, CompressionError::TooLarge { limit: 4096, .. }));
assert_eq!(gz.decompress(&bomb, 0).unwrap().len(), 1_000_000);
}
#[test]
fn gzip_stream_compressor_reused_across_messages() {
let gz = GzipCompressor::default();
let mut stream = GzipStreamCompressor::default();
stream.write(b"first message").unwrap();
let a = stream.finish_message().unwrap();
stream.write(b"second message").unwrap();
let b = stream.finish_message().unwrap();
assert_eq!(gz.decompress(&a, 0).unwrap(), b"first message");
assert_eq!(gz.decompress(&b, 0).unwrap(), b"second message");
}
#[test]
fn size_limits_compress_min_and_send_max() {
let limits = SizeLimits {
read_max_bytes: 0,
send_max_bytes: 100,
compress_min_bytes: 50,
};
assert!(!limits.should_compress(10), "below compress_min: skip");
assert!(limits.should_compress(50), "at compress_min: compress");
assert!(limits.check_send(100).is_ok());
let err = limits.check_send(101).err().expect("over send_max");
assert_eq!(
err.current_context().code(),
foundation_connectrpc::Code::ResourceExhausted
);
let unlimited = SizeLimits::default();
assert!(unlimited.should_compress(0));
assert!(unlimited.check_send(usize::MAX).is_ok());
}
#[test]
fn registry_defaults_to_gzip() {
let reg = CompressionRegistry::new();
assert!(reg.get("gzip").is_some());
assert!(reg.supports("identity"));
assert!(reg.supports("gzip"));
assert!(!reg.supports("br"));
assert_eq!(reg.accept_encoding_header(), "gzip");
}
#[test]
fn registry_register_and_remove() {
let mut reg = CompressionRegistry::empty();
assert_eq!(reg.accept_encoding_header(), "");
reg.register(Arc::new(GzipCompressor::default()));
assert_eq!(reg.accept_encoding_header(), "gzip");
reg.remove("gzip");
assert!(reg.get("gzip").is_none());
}
#[test]
fn negotiate_request_and_response() {
let reg = CompressionRegistry::new();
let n = negotiate_compression(®, Some("gzip"), Some("gzip, identity")).unwrap();
assert!(n.request_decompressor.is_some());
assert!(n.response_compressor.is_some());
let n = negotiate_compression(®, None, None).unwrap();
assert!(n.request_decompressor.is_none());
assert!(n.response_compressor.is_none());
let n = negotiate_compression(®, Some("gzip"), Some("br")).unwrap();
assert!(n.response_compressor.is_some());
let err = negotiate_compression(®, Some("br"), None).err().expect("unsupported");
assert_eq!(
err.current_context().code(),
foundation_connectrpc::Code::Unimplemented
);
}
#[test]
fn buffer_pool_recycles_locally() {
let mut pool = BufferPool::new();
let mut buf = pool.get();
buf.extend_from_slice(&[1, 2, 3]);
pool.put(buf);
assert_eq!(pool.free_list_len(), 1);
let reused = pool.get();
assert!(reused.is_empty());
assert_eq!(pool.free_list_len(), 0);
}
#[test]
fn frozen_frame_final_drop_returns_to_origin_pool_across_threads() {
let mut pool = BufferPool::new();
let mut buf = pool.get();
buf.extend_from_slice(&[7u8; 4096]);
let frozen = pool.freeze(buf);
assert_eq!(&frozen[..8], &[7u8; 8]);
assert_eq!(pool.pending_returns(), 0, "still alive, not yet returned");
let handle = thread::spawn(move || {
let _f = frozen; });
handle.join().unwrap();
assert_eq!(pool.pending_returns(), 1, "final drop returned to origin lane");
let recycled = pool.get();
assert!(
recycled.capacity() >= 4096,
"recycled the original allocation (cap {}), not a fresh 512-byte buffer",
recycled.capacity()
);
assert_eq!(pool.pending_returns(), 0);
}