use super::*;
use crate::command::chat::storage::{ChatMessage, MessageRole, ToolCallItem};
fn user_msg(content: &str) -> ChatMessage {
ChatMessage::text(MessageRole::User, content)
}
fn assistant_msg(content: &str) -> ChatMessage {
ChatMessage::text(MessageRole::Assistant, content)
}
fn tool_call_msg(names: &[&str]) -> ChatMessage {
ChatMessage {
role: MessageRole::Assistant,
content: String::new(),
tool_calls: Some(
names
.iter()
.enumerate()
.map(|(i, name)| ToolCallItem {
id: format!("call_{}", i),
name: name.to_string(),
arguments: "{}".to_string(),
})
.collect(),
),
tool_call_id: None,
images: None,
reasoning_content: None,
sender_name: None,
}
}
fn tool_result_msg(call_id: &str, content: &str) -> ChatMessage {
ChatMessage {
role: MessageRole::Tool,
content: content.to_string(),
tool_calls: None,
tool_call_id: Some(call_id.to_string()),
images: None,
reasoning_content: None,
sender_name: None,
}
}
#[test]
fn test_no_truncation_needed() {
let msgs = vec![user_msg("hello"), assistant_msg("hi")];
let result = select_messages(&msgs, 100, 0, 10, &[]); assert_eq!(result.len(), 2);
assert_eq!(result[0].role, MessageRole::User);
assert_eq!(result[1].role, MessageRole::Assistant);
}
#[test]
fn test_tool_group_dropped_first() {
use crate::command::chat::tools::tool_names::BASH;
let msgs = vec![
user_msg("do something"),
assistant_msg("let me check"),
tool_call_msg(&[BASH]),
tool_result_msg("call_0", &"huge output ".repeat(1000)),
user_msg("what about this"),
assistant_msg("here's the answer"),
];
let result = select_messages(&msgs, 100, 1, 0, &[]);
assert!(result.iter().any(|m| m.role == MessageRole::User));
assert!(!result.iter().any(|m| m.role == MessageRole::Tool)); assert!(result.iter().any(|m| m.content.contains("Previous: used"))); }
#[test]
fn test_time_order_preserved() {
use crate::command::chat::tools::tool_names::BASH;
let msgs = vec![
user_msg("first"),
assistant_msg("ok1"),
tool_call_msg(&[BASH]),
tool_result_msg("call_0", "output"),
user_msg("second"),
assistant_msg("ok2"),
];
let result = select_messages(&msgs, 100, 0, 10, &[]);
let user_positions: Vec<usize> = result
.iter()
.enumerate()
.filter(|(_, m)| m.role == MessageRole::User)
.map(|(i, _)| i)
.collect();
assert!(user_positions[0] < user_positions[1]);
}
#[test]
fn test_placeholder_format() {
use crate::command::chat::tools::tool_names::{BASH, READ};
let msgs = vec![
user_msg("run"),
tool_call_msg(&[BASH, READ]),
tool_result_msg("call_0", &"x".repeat(2000)),
tool_result_msg("call_1", &"y".repeat(2000)),
];
let result = select_messages(&msgs, 100, 1, 0, &[]);
let placeholder = result.iter().find(|m| m.content.contains("Previous: used"));
assert!(placeholder.is_some());
let p = placeholder.unwrap();
assert!(p.content.contains(BASH));
assert!(p.content.contains(READ));
assert!(p.tool_calls.is_none());
}
#[test]
fn test_exempt_tool_group_protected() {
use crate::command::chat::tools::tool_names::LOAD_SKILL;
let msgs = vec![
user_msg("load a skill"),
tool_call_msg(&[LOAD_SKILL]),
tool_result_msg("call_0", &"skill content ".repeat(500)),
user_msg("q1"),
assistant_msg("a1"),
user_msg("q2"),
assistant_msg("a2"),
user_msg("q3"),
assistant_msg("a3"),
];
let result = select_messages(&msgs, 100, 5, 0, &[]);
assert!(
result.iter().any(|m| m.role == MessageRole::Tool),
"exempt tool result 应该被保留"
);
}
#[test]
fn test_stage1_time_fallback_keeps_recent_tool_group() {
use crate::command::chat::tools::tool_names::BASH;
let mut msgs = Vec::new();
for i in 0..20 {
msgs.push(user_msg(&format!("old user {}", i).repeat(50)));
}
msgs.push(tool_call_msg(&[BASH]));
msgs.push(tool_result_msg("call_0", "recent shell output"));
msgs.push(user_msg("latest"));
let result = select_messages(&msgs, 100, 2, 2, &[]);
assert!(
result.iter().any(|m| m.role == MessageRole::Tool),
"最近的 tool result 应该被时间保底保留"
);
assert!(
result
.iter()
.any(|m| m.role == MessageRole::User && m.content == "latest"),
"最新 User 必须保留"
);
}