use super::*;
#[tokio::test]
async fn summarizing_compresses_when_over_budget() {
let mut s = Summarizing::new(512) .with_summarizer(stub_summarizer("SUMMARY"));
let big = "x".repeat(200);
for i in 0..5u32 {
s.sync_turn(&big, &big, &metrics_with_input(10 + i)).await;
}
assert_eq!(s.compress_count, 0);
s.sync_turn(&big, &big, &metrics_with_input(600)).await;
assert!(s.compress_count >= 1, "compress_count={}", s.compress_count);
assert!(
s.prev_summary.starts_with(crate::agentic::SUMMARY_PREFIX),
"prev_summary must be the marked compaction message"
);
assert!(s.prev_summary.contains("SUMMARY"));
assert!(s.prev_summary.contains(crate::agentic::SUMMARY_END_MARKER));
assert!(
s.history
.iter()
.any(|t| t.user.starts_with(crate::agentic::SUMMARY_PREFIX) && t.assistant.is_empty()),
"the compaction message must live in history as a lone user entry"
);
}
#[tokio::test]
async fn summarizing_append_only_declines_to_rewrite_history() {
let big = "x".repeat(200);
let drive = |rewrites_history: bool| async move {
let big = "x".repeat(200);
let mut s = Summarizing::new(512)
.with_summarizer(stub_summarizer("SUMMARY"))
.with_rewrites_history(rewrites_history);
for i in 0..5u32 {
s.sync_turn(&big, &big, &metrics_with_input(10 + i)).await;
}
let before = s.history.clone();
s.sync_turn(&big, &big, &metrics_with_input(600)).await;
(s, before)
};
let (standard, _) = drive(true).await;
assert!(
standard.compress_count >= 1,
"the standard preset still compacts"
);
let (append, before) = drive(false).await;
assert_eq!(
append.compress_count, 0,
"append-only must mint no compaction"
);
assert_eq!(
append.prev_summary, "",
"append-only must not start a summary chain"
);
assert!(
append.pending_record.is_none(),
"append-only must not stage a compaction record"
);
assert_eq!(append.history.len(), before.len() + 1);
for (kept, original) in append.history.iter().zip(before.iter()) {
assert_eq!(kept.user, original.user);
assert_eq!(kept.assistant, original.assistant);
}
let _ = big;
}
#[tokio::test]
async fn summarizing_compresses_repeatedly() {
let mut s = Summarizing::new(512).with_summarizer(stub_summarizer("SUMMARY"));
let text = "x".repeat(120);
for i in 0..20u32 {
s.sync_turn(&text, &text, &metrics_with_input(50 * (i + 1)))
.await;
}
assert!(s.compress_count >= 1);
}
#[tokio::test]
async fn a_session_inheriting_summarizer_rebinds_to_the_new_backend() {
let mut s = Summarizing::new(512).with_summarizer(stub_summarizer("A-SUMMARY"));
let big = "x".repeat(200);
for i in 0..5u32 {
s.sync_turn(&big, &big, &metrics_with_input(10 + i)).await;
}
s.sync_turn(&big, &big, &metrics_with_input(600)).await; assert!(
s.prev_summary.contains("A-SUMMARY"),
"backend A summarizer must run first: {}",
s.prev_summary
);
MemoryProvider::set_summarizer(&mut s, Box::new(stub_summarizer("B-SUMMARY")));
for i in 0..5u32 {
s.sync_turn(&big, &big, &metrics_with_input(10 + i)).await;
}
s.sync_turn(&big, &big, &metrics_with_input(600)).await; assert!(
s.prev_summary.contains("B-SUMMARY"),
"the rebound backend-B summarizer must be used: {}",
s.prev_summary
);
assert!(
!s.prev_summary.contains("A-SUMMARY"),
"the stale backend-A summarizer must be gone"
);
}
#[tokio::test]
async fn summarizing_fallback_placeholder_when_no_summarizer() {
let policy_tokens = crate::agentic::response_repository_policy_tokens() as u32;
let mut s = Summarizing::new(256 + policy_tokens * 5 / 4);
for i in 0..6u32 {
s.sync_turn(
&format!("question {i}"),
&format!("answer {i}"),
&metrics_with_input(if i == 5 { 300 + policy_tokens } else { 10 }),
)
.await;
}
let compaction = s
.history
.iter()
.find(|t| t.user.starts_with(crate::agentic::SUMMARY_PREFIX))
.expect("static fallback marker should be inserted");
assert!(
compaction
.user
.contains("Summary generation was unavailable"),
"got: {}",
compaction.user
);
}