Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0ee2a69
feat(tests): add comprehensive unit tests for hooks and memory context
senamakel Apr 12, 2026
9a022aa
feat(tests): enhance unit tests for provider alias resolution and pro…
senamakel Apr 12, 2026
faf4c37
feat(tests): enhance error handling and formatting in unit tests
senamakel Apr 12, 2026
27d924a
feat(tests): add unit tests for tool filtering and subagent dump rend…
senamakel Apr 12, 2026
429c913
feat(tests): add comprehensive unit tests for memory loader and multi…
senamakel Apr 12, 2026
13b91de
feat(tests): add unit tests for tool execution and agent behavior
senamakel Apr 12, 2026
aa51c32
feat(tests): enhance tool execution and agent behavior tests
senamakel Apr 12, 2026
a1bd758
refactor(tests): improve test readability and structure
senamakel Apr 12, 2026
27d2d0e
docs(agent): enhance module documentation for agent domain
senamakel Apr 12, 2026
f9696c4
docs(agent): improve documentation and formatting across multiple files
senamakel Apr 12, 2026
62cecfd
feat(tests): add comprehensive tests for memory loader and agent beha…
senamakel Apr 12, 2026
677510c
refactor(tests): improve formatting and readability in memory loader …
senamakel Apr 12, 2026
0e9dc01
feat(tests): add new tests for self-healing interceptor and local AI …
senamakel Apr 12, 2026
251144e
refactor(tests): enhance test structure and external ID handling in e…
senamakel Apr 12, 2026
9f6b7d7
refactor(tests): remove redundant test modules and improve test organ…
senamakel Apr 12, 2026
9c76607
refactor(tests): improve test formatting and readability
senamakel Apr 12, 2026
09179c5
refactor(api): improve string containment check and formatting in tra…
senamakel Apr 12, 2026
a445206
refactor(code): streamline function implementations and improve reada…
senamakel Apr 12, 2026
e03140e
chore(ci): update typecheck workflow and pre-push hooks to include cl…
senamakel Apr 12, 2026
d77e2fc
refactor(api): simplify condition in key_bytes_from_string function
senamakel Apr 12, 2026
15bfe3e
chore(package): update format:check script to remove clippy integration
senamakel Apr 12, 2026
d9a91bc
refactor(core): enhance documentation and structure in core modules
senamakel Apr 12, 2026
073fa80
refactor(core): reorganize imports in engine.rs for clarity
senamakel Apr 12, 2026
d9d9785
refactor(core): enhance WebChannelEvent structure and documentation
senamakel Apr 12, 2026
d0822ab
refactor(core): streamline Socket.IO event handlers for clarity and c…
senamakel Apr 12, 2026
b446636
feat(core): add new structs for Socket RPC and chat events
senamakel Apr 12, 2026
24f76f3
refactor(core): remove unused json_type_name function from socketio.rs
senamakel Apr 12, 2026
3d46483
chore(ci): update clippy command in typecheck workflow
senamakel Apr 12, 2026
2b72130
chore(husky): remove clippy check from pre-push hook
senamakel Apr 12, 2026
dbf12be
refactor(tests): add macOS-specific imports for enhanced test coverage
senamakel Apr 12, 2026
978a0b0
Merge remote-tracking branch 'upstream/main' into tests/improve-coverage
senamakel Apr 12, 2026
af6c5ab
refactor(tests): update tool call execution in test cases
senamakel Apr 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat(tests): add unit tests for tool execution and agent behavior
- Introduced new tests for the `run_tool_call_loop` function, validating the rejection of vision markers for non-vision providers and ensuring correct streaming of final text chunks.
- Added tests to verify that CLI-only tools are blocked in prompt mode and that native tool results are persisted as tool messages.
- Enhanced the `Agent` class with tests for error handling and event publishing during single runs, ensuring robust agent behavior in various scenarios.
- Overall, these additions strengthen the test suite by improving coverage and reliability in tool execution and agent interactions.
  • Loading branch information
senamakel committed Apr 12, 2026
commit 13b91dee5ad1b9a4e4e321d80414a090579dca87
203 changes: 203 additions & 0 deletions src/openhuman/agent/harness/session/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,3 +301,206 @@ impl Agent {
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::core::event_bus::{global, init_global, DomainEvent};
use crate::openhuman::agent::dispatcher::XmlToolDispatcher;
use crate::openhuman::agent::error::AgentError;
use crate::openhuman::memory::Memory;
use crate::openhuman::providers::{ChatMessage, ChatRequest, ChatResponse, UsageInfo};
use anyhow::anyhow;
use async_trait::async_trait;
use parking_lot::Mutex;
use std::sync::Arc;
use tokio::sync::Mutex as AsyncMutex;
use tokio::time::{sleep, Duration};

struct StaticProvider {
response: Mutex<Option<anyhow::Result<ChatResponse>>>,
}

#[async_trait]
impl Provider for StaticProvider {
async fn chat_with_system(
&self,
_system_prompt: Option<&str>,
_message: &str,
_model: &str,
_temperature: f64,
) -> Result<String> {
Ok("unused".into())
}

async fn chat(
&self,
_request: ChatRequest<'_>,
_model: &str,
_temperature: f64,
) -> Result<ChatResponse> {
self.response
.lock()
.take()
.unwrap_or_else(|| Ok(ChatResponse {
text: Some("done".into()),
tool_calls: vec![],
usage: None,
}))
}
}

fn make_agent(provider: Arc<dyn Provider>) -> Agent {
let workspace = tempfile::TempDir::new().expect("temp workspace");
let workspace_path = workspace.into_path();
let memory_cfg = crate::openhuman::config::MemoryConfig {
backend: "none".into(),
..crate::openhuman::config::MemoryConfig::default()
};
let mem: Arc<dyn Memory> = Arc::from(
crate::openhuman::memory::create_memory(&memory_cfg, &workspace_path, None).unwrap(),
);

Agent::builder()
.provider_arc(provider)
.tools(vec![])
.memory(mem)
.tool_dispatcher(Box::new(XmlToolDispatcher))
.workspace_dir(workspace_path)
.event_context("runtime-test-session", "runtime-test-channel")
.build()
.unwrap()
}

#[test]
fn new_entries_for_turn_detects_prefix_overlap_and_fallbacks() {
let history_snapshot = vec![
ConversationMessage::Chat(ChatMessage::user("a")),
ConversationMessage::Chat(ChatMessage::assistant("b")),
];
let current_history = vec![
ConversationMessage::Chat(ChatMessage::user("a")),
ConversationMessage::Chat(ChatMessage::assistant("b")),
ConversationMessage::Chat(ChatMessage::assistant("c")),
];
let appended = Agent::new_entries_for_turn(&history_snapshot, &current_history);
assert_eq!(appended.len(), 1);

let shifted_history = vec![
ConversationMessage::Chat(ChatMessage::assistant("b")),
ConversationMessage::Chat(ChatMessage::assistant("c")),
];
let overlap = Agent::new_entries_for_turn(&history_snapshot, &shifted_history);
assert_eq!(overlap.len(), 1);
assert!(matches!(&overlap[0], ConversationMessage::Chat(msg) if msg.content == "c"));
}

#[test]
fn sanitizers_and_tool_call_helpers_cover_fallback_paths() {
let err = anyhow!(AgentError::PermissionDenied {
tool_name: "shell".into(),
required_level: "Execute".into(),
channel_max_level: "ReadOnly".into(),
});
assert_eq!(Agent::sanitize_event_error_message(&err), "permission_denied");

let generic = anyhow!(
"bad key sk-123456789012345678901234567890\nwith\twhitespace"
);
let sanitized = Agent::sanitize_event_error_message(&generic);
assert!(!sanitized.contains('\n'));
assert!(!sanitized.contains('\t'));

let calls = vec![
crate::openhuman::agent::dispatcher::ParsedToolCall {
name: "a".into(),
arguments: serde_json::json!({}),
tool_call_id: None,
},
crate::openhuman::agent::dispatcher::ParsedToolCall {
name: "b".into(),
arguments: serde_json::json!({"x":1}),
tool_call_id: Some("keep".into()),
},
];
let calls = Agent::with_fallback_tool_call_ids(calls, 2);
assert_eq!(calls[0].tool_call_id.as_deref(), Some("parsed-3-1"));
assert_eq!(calls[1].tool_call_id.as_deref(), Some("keep"));

let response = crate::openhuman::providers::ChatResponse {
text: Some(String::new()),
tool_calls: vec![],
usage: None,
};
let persisted = Agent::persisted_tool_calls_for_history(&response, &calls, 2);
assert_eq!(persisted[0].id, "parsed-3-1");
assert_eq!(persisted[1].id, "keep");
}

#[tokio::test]
async fn run_single_publishes_completed_and_error_events() {
let _ = init_global(64);
let events = Arc::new(AsyncMutex::new(Vec::<DomainEvent>::new()));
let events_handler = Arc::clone(&events);
let _handle = global().unwrap().on("runtime-events-test", move |event| {
let events = Arc::clone(&events_handler);
let cloned = event.clone();
Box::pin(async move {
events.lock().await.push(cloned);
})
});

let ok_provider: Arc<dyn Provider> = Arc::new(StaticProvider {
response: Mutex::new(Some(Ok(ChatResponse {
text: Some("ok".into()),
tool_calls: vec![],
usage: Some(UsageInfo::default()),
}))),
});
let mut ok_agent = make_agent(ok_provider);
let response = ok_agent.run_single("hello").await.expect("run_single ok");
assert_eq!(response, "ok");

let err_provider: Arc<dyn Provider> = Arc::new(StaticProvider {
response: Mutex::new(Some(Err(anyhow!(
AgentError::PermissionDenied {
tool_name: "shell".into(),
required_level: "Execute".into(),
channel_max_level: "ReadOnly".into(),
}
)))),
});
let mut err_agent = make_agent(err_provider);
let err = err_agent
.run_single("hello")
.await
.expect_err("run_single should publish error");
assert!(err.to_string().contains("Permission denied"));

sleep(Duration::from_millis(20)).await;
let captured = events.lock().await;
assert!(captured.iter().any(|event| matches!(
event,
DomainEvent::AgentTurnStarted { session_id, channel }
if session_id == "runtime-test-session" && channel == "runtime-test-channel"
)));
assert!(captured.iter().any(|event| matches!(
event,
DomainEvent::AgentTurnCompleted {
session_id,
text_chars,
iterations,
} if session_id == "runtime-test-session" && *text_chars == 2 && *iterations >= 1
)));
assert!(captured.iter().any(|event| matches!(
event,
DomainEvent::AgentError {
session_id,
message,
recoverable,
} if session_id == "runtime-test-session"
&& message == "permission_denied"
&& !recoverable
)));
}
}
Loading