use super::*;
#[test]
fn test_context_compressor_new() {
let compressor = ContextCompressor::new(100000);
assert_eq!(compressor.compression_threshold, 75000); assert_eq!(compressor.min_messages_to_keep, 6);
}
#[test]
fn test_estimate_tokens_simple() {
let compressor = ContextCompressor::new(100000);
let messages = vec![
Message::system("Hello world"), ];
let estimate = compressor.estimate_tokens(&messages);
assert!(estimate > 0, "should produce a non-zero estimate");
assert!(estimate < 100, "single short message shouldn't be huge");
}
#[test]
fn test_estimate_tokens_code_content() {
let compressor = ContextCompressor::new(100000);
let code_messages = vec![Message::user("fn main() { println!(\"hello\"); }")];
let text_messages = vec![Message::user("This is plain text without code")];
let code_estimate = compressor.estimate_tokens(&code_messages);
let text_estimate = compressor.estimate_tokens(&text_messages);
assert!(code_estimate > 0);
assert!(text_estimate > 0);
}
#[test]
fn test_should_compress_small_context() {
let compressor = ContextCompressor::new(100000);
let small: Vec<Message> = vec![Message::system("test")];
assert!(!compressor.should_compress(&small));
}
#[test]
fn test_should_compress_large_context() {
let compressor = ContextCompressor::new(1000); let mut large = vec![Message::system("test".repeat(10000))];
for _ in 0..20 {
large.push(Message::user("more content here".repeat(100)));
}
assert!(compressor.should_compress(&large));
}
#[test]
fn test_hard_compress_preserves_system() {
let compressor = ContextCompressor::new(100000);
let messages = vec![
Message::system("system prompt"),
Message::user("old1"),
Message::assistant("response1"),
Message::user("old2"),
Message::assistant("response2"),
Message::user("recent1"),
Message::assistant("response3"),
Message::user("recent2"),
];
let compressed = compressor.hard_compress(&messages);
assert_eq!(compressed[0].role, "system");
assert_eq!(compressed[0].content, "system prompt");
}
#[test]
fn test_hard_compress_keeps_recent() {
let compressor = ContextCompressor::new(100000);
let messages = vec![
Message::system("system"),
Message::user("old1"),
Message::user("old2"),
Message::user("recent1"),
Message::user("recent2"),
];
let compressed = compressor.hard_compress(&messages);
assert!(compressed.len() >= 4);
assert_eq!(compressed[0].role, "system");
}
#[test]
fn test_hard_compress_ends_with_user() {
let compressor = ContextCompressor::new(100000);
let messages = vec![
Message::system("system"),
Message::user("user msg"),
Message::assistant("assistant msg"),
];
let compressed = compressor.hard_compress(&messages);
let last = compressed.last().unwrap();
assert_eq!(last.role, "user");
}
#[test]
fn test_hard_compress_avoids_consecutive_assistants() {
let compressor = ContextCompressor::new(100000);
let messages = vec![
Message::system("system"),
Message::assistant("response1"),
Message::assistant("response2"), Message::user("user msg"),
];
let compressed = compressor.hard_compress(&messages);
for i in 0..compressed.len() - 1 {
if compressed[i].role == "assistant" {
assert_ne!(compressed[i + 1].role, "assistant");
}
}
}
#[test]
fn test_hard_compress_empty_messages() {
let compressor = ContextCompressor::new(100000);
let messages: Vec<Message> = vec![];
let compressed = compressor.hard_compress(&messages);
assert!(compressed.is_empty() || compressed[0].role == "user");
}
#[test]
fn test_hard_compress_single_message() {
let compressor = ContextCompressor::new(100000);
let messages = vec![Message::system("only system")];
let compressed = compressor.hard_compress(&messages);
assert!(!compressed.is_empty());
}
#[test]
fn test_estimate_tokens_multiple_messages() {
let compressor = ContextCompressor::new(100000);
let messages = vec![
Message::system("System prompt"),
Message::user("User question"),
Message::assistant("Assistant response"),
];
let estimate = compressor.estimate_tokens(&messages);
assert!(estimate > 10); }
#[test]
fn test_compression_threshold_calculation() {
let compressor = ContextCompressor::new(10000);
assert_eq!(compressor.compression_threshold, 7500);
}
#[test]
fn test_min_messages_to_keep() {
let compressor = ContextCompressor::new(100000);
assert_eq!(compressor.min_messages_to_keep, 6);
}
#[test]
fn test_estimate_tokens_empty() {
let compressor = ContextCompressor::new(100000);
let messages: Vec<Message> = vec![];
let estimate = compressor.estimate_tokens(&messages);
assert_eq!(estimate, 0);
}
#[test]
fn test_estimate_tokens_with_semicolons() {
let compressor = ContextCompressor::new(100000);
let messages = vec![Message::user("let x = 1; let y = 2; let z = 3;")];
let estimate = compressor.estimate_tokens(&messages);
assert!(estimate > 0 && estimate < 100);
}
#[test]
fn test_estimate_tokens_with_braces() {
let compressor = ContextCompressor::new(100000);
let messages = vec![Message::user("fn main() { println!(\"hello\"); }")];
let estimate = compressor.estimate_tokens(&messages);
assert!(estimate > 0 && estimate < 100);
}
#[test]
fn test_estimate_tokens_plain_text() {
let compressor = ContextCompressor::new(100000);
let messages = vec![Message::user("This is plain text without any code")];
let estimate = compressor.estimate_tokens(&messages);
assert!(estimate > 0);
}
#[test]
fn test_should_compress_exact_threshold() {
let compressor = ContextCompressor::new(1000);
let messages = vec![
Message::user("a".repeat(3200)), ];
let estimate = compressor.estimate_tokens(&messages);
let should = compressor.should_compress(&messages);
if estimate > 850 {
assert!(should);
}
}
#[test]
fn test_hard_compress_only_assistants() {
let compressor = ContextCompressor::new(100000);
let messages = vec![
Message::system("system"),
Message::assistant("response1"),
Message::assistant("response2"),
Message::assistant("response3"),
];
let compressed = compressor.hard_compress(&messages);
let last = compressed.last().unwrap();
assert_eq!(last.role, "user");
}
#[test]
fn test_hard_compress_alternating() {
let compressor = ContextCompressor::new(100000);
let messages = vec![
Message::system("system"),
Message::user("u1"),
Message::assistant("a1"),
Message::user("u2"),
Message::assistant("a2"),
Message::user("u3"),
];
let compressed = compressor.hard_compress(&messages);
assert!(!compressed.is_empty());
assert_eq!(compressed[0].role, "system");
}
#[test]
fn test_hard_compress_two_messages() {
let compressor = ContextCompressor::new(100000);
let messages = vec![Message::system("system"), Message::user("question")];
let compressed = compressor.hard_compress(&messages);
assert!(compressed.len() >= 2);
}
#[test]
fn test_hard_compress_user_only() {
let compressor = ContextCompressor::new(100000);
let messages = vec![
Message::system("system"),
Message::user("q1"),
Message::user("q2"),
Message::user("q3"),
];
let compressed = compressor.hard_compress(&messages);
assert!(!compressed.is_empty());
}
#[test]
fn test_hard_compress_long_conversation() {
let compressor = ContextCompressor::new(100000);
let mut messages = vec![Message::system("system")];
for i in 0..20 {
messages.push(Message::user(format!("Question {}", i)));
messages.push(Message::assistant(format!("Answer {}", i)));
}
let compressed = compressor.hard_compress(&messages);
assert!(compressed.len() < messages.len());
assert_eq!(compressed[0].role, "system");
assert_eq!(compressed.last().unwrap().role, "user");
}
#[test]
fn safe_tail_start_skips_leading_tool_results() {
let messages = vec![
Message::system("system"),
Message::user("question"),
Message::assistant("let me call a tool"),
Message::tool("tool result 1", "call_1"),
Message::tool("tool result 2", "call_2"),
Message::user("follow-up"),
Message::assistant("done"),
];
assert_eq!(safe_tail_start(&messages, 3), 5);
assert_eq!(safe_tail_start(&messages, 1), 1);
}
#[test]
fn hard_compress_does_not_start_tail_on_tool_result() {
let messages = vec![
Message::system("system"),
Message::user("please run a tool"),
Message::assistant("calling tool now"),
Message::tool("tool result", "call_1"),
Message::assistant("the result was good"),
Message::user("thanks"),
];
let compressed = ContextCompressor::new(100000).hard_compress(&messages);
let note_idx = compressed
.iter()
.position(|m| m.content.text().contains("[Earlier context was compressed"));
assert!(
note_idx.is_some(),
"compression note not found in: {:?}",
compressed
.iter()
.map(|m| m.role.clone())
.collect::<Vec<_>>()
);
let note_idx = note_idx.unwrap();
assert!(
note_idx + 1 < compressed.len(),
"no messages after compression note"
);
assert_ne!(
compressed[note_idx + 1].role,
"tool",
"hard_compress kept an orphan tool result as the first tail message: {:?}",
compressed
.iter()
.map(|m| m.role.clone())
.collect::<Vec<_>>()
);
}
#[test]
fn test_estimate_tokens_large_message() {
let compressor = ContextCompressor::new(100000);
let large_content = "a".repeat(10000);
let small_content = "a".repeat(100);
let messages = vec![Message::user(large_content)];
let small_messages = vec![Message::user(small_content)];
let estimate = compressor.estimate_tokens(&messages);
let small_estimate = compressor.estimate_tokens(&small_messages);
assert!(estimate > small_estimate);
assert!(estimate > 0);
}
#[test]
fn test_estimate_tokens_unicode() {
let compressor = ContextCompressor::new(100000);
let messages = vec![Message::user("日本語テスト 🦀 Rust")];
let estimate = compressor.estimate_tokens(&messages);
assert!(estimate > 0);
}
#[test]
fn test_hard_compress_preserves_task_objective() {
let compressor = ContextCompressor::new(100000);
let messages = vec![
Message::system("sys"),
Message::user("THE ORIGINAL TASK: fix the bug"),
Message::assistant("Let me start by reading the file."),
Message::user("Here is the file."),
Message::assistant("I see the issue."),
Message::user("Can you fix it?"),
Message::assistant("Working on it now."),
Message::user("Is it done?"),
Message::assistant("Almost there."),
Message::user("Please finish up."),
];
let compressed = compressor.hard_compress(&messages);
assert!(
compressed
.iter()
.any(|m| m.content.text().contains("THE ORIGINAL TASK")),
"hard_compress dropped the original task objective: {:?}",
compressed
.iter()
.map(|m| m.content.text().to_string())
.collect::<Vec<_>>()
);
}