use std::sync::Arc;
use crate::brain::agent::service::AgentService;
use crate::brain::agent::service::clear::{
CLEAR_HINT, CLEAR_MARKER_PREFIX, ClearReceipt, clear_marker,
};
use crate::channels::commands::ChannelCommand;
use crate::db::Database;
use crate::services::{MessageService, ServiceContext, SessionService};
use crate::tests::agent_service_mocks::MockProvider;
const LOADER_PREFIX: &str = "[CONTEXT COMPACTION";
#[test]
fn the_marker_is_what_the_loader_and_the_tui_key_on() {
assert!(CLEAR_MARKER_PREFIX.starts_with(LOADER_PREFIX));
let marker = clear_marker(Some("Router audit"), &[]);
assert!(marker.starts_with(CLEAR_MARKER_PREFIX));
const TUI: &str = include_str!("../tui/app/messaging.rs");
assert!(TUI.contains("if msg.content.starts_with(\"[CONTEXT COMPACTION\")"));
}
#[test]
fn the_body_names_the_session_and_the_search_tool() {
let marker = clear_marker(Some(" Router audit "), &[]);
let body = marker.split_once("\n\n").expect("banner then body").1;
assert!(body.contains("session_search"));
assert!(body.contains("'Router audit'"));
assert!(body.contains("'tail'") && body.contains("'search'"));
assert!(
!body.contains("[CONTEXT COMPACTION"),
"the banner is not part of the body"
);
}
#[test]
fn an_untitled_session_still_gets_a_usable_nudge() {
let marker = clear_marker(None, &[]);
assert!(marker.contains("'untitled'"));
assert!(marker.contains("session_search"));
assert_eq!(clear_marker(Some(" "), &[]), clear_marker(None, &[]));
}
#[test]
fn the_banner_is_stripped_and_the_body_kept_when_loaded_as_context() {
let mut content = clear_marker(Some("Router audit"), &[]);
AgentService::strip_compaction_banner(&mut content);
assert!(!content.starts_with("["));
assert!(content.starts_with("The user cleared this session's context"));
assert!(content.contains("session_search"));
}
#[test]
fn the_receipt_line_reports_title_and_an_aborted_summariser() {
let plain = ClearReceipt {
session_title: Some("Router audit".to_string()),
aborted_background_compaction: false,
}
.user_line();
assert!(plain.contains("(title 'Router audit')"));
assert!(plain.contains("No summariser call was made."));
assert!(!plain.contains("cancelled"));
let aborted = ClearReceipt {
session_title: None,
aborted_background_compaction: true,
}
.user_line();
assert!(!aborted.contains("title"));
assert!(aborted.contains("background compaction that was running has been cancelled"));
}
#[tokio::test]
async fn clearing_appends_the_marker_and_the_loader_cuts_there() {
let db = Database::connect_in_memory().await.unwrap();
db.run_migrations().await.unwrap();
let context = ServiceContext::new(db.pool().clone());
let agent = AgentService::new_for_test(Arc::new(MockProvider), context.clone()).await;
let sessions = SessionService::new(context.clone());
let messages = MessageService::new(context);
let session = sessions
.create_session(Some("Router audit".to_string()))
.await
.unwrap();
messages
.create_message(session.id, "user".to_string(), "first question".to_string())
.await
.unwrap();
messages
.create_message(
session.id,
"assistant".to_string(),
"first answer".to_string(),
)
.await
.unwrap();
let receipt = agent
.clear_context(session.id)
.await
.expect("clear succeeds");
assert_eq!(receipt.session_title.as_deref(), Some("Router audit"));
assert!(!receipt.aborted_background_compaction);
let rows = messages
.list_messages_for_session(session.id)
.await
.unwrap();
assert_eq!(rows.len(), 3, "history is kept, one marker row appended");
assert_eq!(rows[0].content, "first question");
let marker = &rows[2];
assert_eq!(marker.role, "user");
assert!(marker.content.starts_with(CLEAR_MARKER_PREFIX));
assert!(marker.content.contains("'Router audit'"));
assert!(
marker.content.contains("user: first question"),
"the marker quotes the exchanges that were cleared away (#1905)"
);
assert!(marker.content.contains("assistant: first answer"));
let loaded = AgentService::messages_from_last_compaction(rows);
assert_eq!(loaded.len(), 1, "the next turn loads only the marker");
assert!(loaded[0].content.starts_with(CLEAR_MARKER_PREFIX));
agent.clear_context(session.id).await.expect("second clear");
let rows = messages
.list_messages_for_session(session.id)
.await
.unwrap();
assert_eq!(rows.len(), 4);
let second = &rows[3];
assert!(
second.content.matches(CLEAR_MARKER_PREFIX).count() == 1,
"an older marker row is never quoted into the new one"
);
assert!(
second.content.contains("first question"),
"the live exchanges still carry into a second clear"
);
}
#[test]
fn clear_aborts_a_background_summariser_before_writing_the_marker() {
const SRC: &str = include_str!("../brain/agent/service/clear.rs");
let abort = SRC
.find("take_pending_compaction(session_id)")
.expect("pending taken");
let write = SRC.find("create_message(").expect("marker written");
assert!(
abort < write,
"the in-flight summary is aborted before the cut"
);
assert!(SRC[abort..write].contains("pending.abort()"));
}
#[test]
fn channels_parse_clear_owner_only() {
const SRC: &str = include_str!("../channels/commands.rs");
let arm = SRC.find("\"/clear\" => {").expect("/clear parsed");
assert!(SRC[arm..arm + 220].contains("Owner-only command"));
assert!(SRC[arm..arm + 220].contains("ChannelCommand::ClearContext"));
assert!(
SRC.contains("(\n \"/clear\","),
"listed in /help"
);
let _ = ChannelCommand::ClearContext;
}
#[test]
fn every_surface_handles_clear_without_an_agent_turn() {
const TELEGRAM: &str = include_str!("../channels/telegram/commands_tg.rs");
const DISCORD: &str = include_str!("../channels/discord/handler.rs");
const SLACK: &str = include_str!("../channels/slack/handler.rs");
const WHATSAPP: &str = include_str!("../channels/whatsapp/handler.rs");
const TUI: &str = include_str!("../tui/app/messaging.rs");
const MENU: &str = include_str!("../channels/telegram/agent.rs");
for (name, src) in [
("telegram", TELEGRAM),
("discord", DISCORD),
("slack", SLACK),
("whatsapp", WHATSAPP),
] {
let arm = src
.find("ChannelCommand::ClearContext => {")
.unwrap_or_else(|| panic!("{name} handles ClearContext"));
assert!(
src[arm..arm + 400].contains("clear_context(session_id)"),
"{name} clears through the agent service"
);
}
let tui = TUI.find("\"/clear\" => {").expect("TUI command");
let window = &TUI[tui..tui + 1200];
assert!(
window.contains("is_processing"),
"refused while a turn runs"
);
assert!(window.contains("clear_context(session_id)"));
assert!(
window.contains("base_context_tokens()"),
"footer reset to baseline"
);
assert!(
MENU.contains("BotCommand::new(\"clear\""),
"in the Telegram menu"
);
}
#[test]
fn a_failed_manual_compaction_points_at_clear() {
const TOOL_LOOP: &str = include_str!("../brain/agent/service/tool_loop.rs");
let failure = TOOL_LOOP
.find("Manual compaction failed")
.expect("manual failure branch");
assert!(TOOL_LOOP[failure..failure + 900].contains("CLEAR_HINT"));
assert!(CLEAR_HINT.contains("/clear"));
const NOTICE: &str = include_str!("../brain/agent/service/compaction_notice.rs");
assert!(NOTICE.contains("self.verbose && matches!(step, CompactionStep::Failed { .. })"));
}
#[test]
fn the_marker_carries_only_the_last_six_pairs() {
let recent: Vec<(String, String)> = (1..=16)
.map(|i| {
let role = if i % 2 == 1 { "user" } else { "assistant" };
(role.to_string(), format!("exchange {i:02}"))
})
.collect();
let marker = clear_marker(Some("Router audit"), &recent);
assert!(marker.contains("The last exchanges before the clear, quoted as context only"));
assert!(marker.contains("user: exchange 05"), "oldest carried pair");
assert!(
marker.contains("assistant: exchange 16"),
"newest carried pair"
);
assert!(
!marker.contains("exchange 04"),
"older than six pairs is dropped"
);
assert!(
!marker.contains("Nothing said or done before it is in your context"),
"the marker must not deny the pairs it quotes"
);
}
#[test]
fn an_empty_history_clears_without_a_pairs_section() {
let marker = clear_marker(Some("Router audit"), &[]);
assert!(!marker.contains("The last exchanges before the clear"));
assert!(marker.contains("Nothing said or done before it is in your context"));
}
#[test]
fn a_long_carried_message_is_capped_on_a_char_boundary() {
let long = "ç".repeat(600);
let marker = clear_marker(Some("Router audit"), &[("user".to_string(), long)]);
let line = marker
.lines()
.find(|l| l.starts_with("user: "))
.expect("the carried message is quoted");
assert!(line.ends_with("[...]"));
let kept = line
.strip_prefix("user: ")
.and_then(|l| l.strip_suffix(" [...]"))
.unwrap();
assert_eq!(kept.chars().count(), 500);
assert!(kept.chars().all(|c| c == 'ç'), "no codepoint is split");
}
#[tokio::test]
async fn a_long_thread_carries_only_the_tail_through_the_real_path() {
let db = Database::connect_in_memory().await.unwrap();
db.run_migrations().await.unwrap();
let context = ServiceContext::new(db.pool().clone());
let agent = AgentService::new_for_test(Arc::new(MockProvider), context.clone()).await;
let sessions = SessionService::new(context.clone());
let messages = MessageService::new(context);
let session = sessions.create_session(None).await.unwrap();
for i in 1..=14 {
let role = if i % 2 == 1 { "user" } else { "assistant" };
messages
.create_message(session.id, role.to_string(), format!("thread {i:02}"))
.await
.unwrap();
}
agent.clear_context(session.id).await.unwrap();
let rows = messages
.list_messages_for_session(session.id)
.await
.unwrap();
assert_eq!(rows.len(), 15, "fourteen messages plus the marker");
let marker = &rows[14];
assert!(marker.content.contains("user: thread 03"), "oldest carried");
assert!(
marker.content.contains("assistant: thread 14"),
"newest carried"
);
assert!(
!marker.content.contains("thread 02"),
"beyond six pairs is dropped"
);
let loaded = AgentService::messages_from_last_compaction(rows);
assert_eq!(
loaded.len(),
1,
"still one context row: the tail rides inside the marker"
);
assert!(loaded[0].content.contains("thread 14"));
}