test(agent): Add unit tests for agent-core and fix clippy warnings

This commit is contained in:
2025-12-26 18:19:58 +01:00
parent fbb6681cd2
commit f5a5724823
14 changed files with 322 additions and 120 deletions

View File

@@ -159,10 +159,10 @@ impl Compactor {
let mut summary = String::new();
use futures_util::StreamExt;
while let Some(chunk_result) = stream.next().await {
if let Ok(chunk) = chunk_result {
if let Some(content) = &chunk.content {
summary.push_str(content);
}
if let Ok(chunk) = chunk_result
&& let Some(content) = &chunk.content
{
summary.push_str(content);
}
}
@@ -195,14 +195,14 @@ mod tests {
// Small message list shouldn't compact
let small_messages: Vec<ChatMessage> = (0..10)
.map(|i| ChatMessage::user(&format!("Message {}", i)))
.map(|i| ChatMessage::user(format!("Message {}", i)))
.collect();
assert!(!counter.should_compact(&small_messages));
// Large message list should compact
// Need ~162,000 tokens = ~648,000 chars (at 4 chars per token)
let large_content = "x".repeat(700_000);
let large_messages = vec![ChatMessage::user(&large_content)];
let large_messages = vec![ChatMessage::user(large_content)];
assert!(counter.should_compact(&large_messages));
}
@@ -211,7 +211,7 @@ mod tests {
let compactor = Compactor::new();
let small: Vec<ChatMessage> = (0..5)
.map(|i| ChatMessage::user(&format!("Short message {}", i)))
.map(|i| ChatMessage::user(format!("Short message {}", i)))
.collect();
assert!(!compactor.needs_compaction(&small));
}

View File

@@ -928,7 +928,7 @@ pub async fn run_agent_loop<P: LlmProvider>(
let new_args = current_args + &args_delta;
// Try to parse as JSON, but keep as string if incomplete
tool_call.function.arguments = serde_json::from_str(&new_args)
.unwrap_or_else(|_| Value::String(new_args));
.unwrap_or(Value::String(new_args));
}
}
}
@@ -1128,3 +1128,65 @@ pub async fn run_agent_loop_streaming<P: LlmProvider>(
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use llm_core::ToolCallDelta;
#[test]
fn test_tool_calls_builder() {
let mut builder = ToolCallsBuilder::new();
// Add first tool call deltas
builder.add_deltas(&[
ToolCallDelta {
index: 0,
id: Some("call_1".to_string()),
function_name: Some("read".to_string()),
arguments_delta: Some("{\"path\":".to_string()),
}
]);
// Add second tool call deltas
builder.add_deltas(&[
ToolCallDelta {
index: 1,
id: Some("call_2".to_string()),
function_name: Some("write".to_string()),
arguments_delta: Some("{\"path\":\"test.txt\"".to_string()),
}
]);
// Add more deltas for first tool call
builder.add_deltas(&[
ToolCallDelta {
index: 0,
id: None,
function_name: None,
arguments_delta: Some("\"lib.rs\"}".to_string()),
}
]);
// Add more deltas for second tool call
builder.add_deltas(&[
ToolCallDelta {
index: 1,
id: None,
function_name: None,
arguments_delta: Some(",\"content\":\"hello\"}".to_string()),
}
]);
let calls = builder.build();
assert_eq!(calls.len(), 2);
assert_eq!(calls[0].id, "call_1");
assert_eq!(calls[0].function.name, "read");
assert_eq!(calls[0].function.arguments, json!({"path": "lib.rs"}));
assert_eq!(calls[1].id, "call_2");
assert_eq!(calls[1].function.name, "write");
assert_eq!(calls[1].function.arguments, json!({"path": "test.txt", "content": "hello"}));
}
}

View File

@@ -183,10 +183,10 @@ impl Checkpoint {
for entry in fs::read_dir(checkpoint_dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("json") {
if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
checkpoints.push(stem.to_string());
}
if path.extension().and_then(|s| s.to_str()) == Some("json")
&& let Some(stem) = path.file_stem().and_then(|s| s.to_str())
{
checkpoints.push(stem.to_string());
}
}

View File

@@ -47,27 +47,27 @@ impl SystemPromptBuilder {
pub fn with_project_instructions(mut self, project_root: &Path) -> Self {
// Try CLAUDE.md first (Claude Code compatibility)
let claude_md = project_root.join("CLAUDE.md");
if claude_md.exists() {
if let Ok(content) = std::fs::read_to_string(&claude_md) {
self.sections.push(PromptSection {
name: "project".to_string(),
content: format!("# Project Instructions\n\n{}", content),
priority: 20,
});
return self;
}
if claude_md.exists()
&& let Ok(content) = std::fs::read_to_string(&claude_md)
{
self.sections.push(PromptSection {
name: "project".to_string(),
content: format!("# Project Instructions\n\n{}", content),
priority: 20,
});
return self;
}
// Fallback to .owlen.md
let owlen_md = project_root.join(".owlen.md");
if owlen_md.exists() {
if let Ok(content) = std::fs::read_to_string(&owlen_md) {
self.sections.push(PromptSection {
name: "project".to_string(),
content: format!("# Project Instructions\n\n{}", content),
priority: 20,
});
}
if owlen_md.exists()
&& let Ok(content) = std::fs::read_to_string(&owlen_md)
{
self.sections.push(PromptSection {
name: "project".to_string(),
content: format!("# Project Instructions\n\n{}", content),
priority: 20,
});
}
self