plano/crates/brightstaff/src/handlers/integration_tests.rs

173 lines
6.2 KiB
Rust
Raw Normal View History

2025-10-14 14:01:11 -07:00
use std::sync::Arc;
use hermesllm::apis::openai::{ChatCompletionsRequest, Message, MessageContent, Role};
use hyper::header::HeaderMap;
2026-03-19 17:58:33 -07:00
use crate::handlers::agents::pipeline::PipelineProcessor;
use crate::handlers::agents::selector::{AgentSelectionError, AgentSelector};
use crate::handlers::response::ResponseHandler;
use crate::router::orchestrator::OrchestratorService;
2025-10-14 14:01:11 -07:00
/// Integration test that demonstrates the modular agent chat flow
/// This test shows how the three main components work together:
/// 1. AgentSelector - selects the appropriate agents based on orchestration
2025-10-14 14:01:11 -07:00
/// 2. PipelineProcessor - executes the agent pipeline
/// 3. ResponseHandler - handles response streaming
#[cfg(test)]
2025-12-25 21:08:37 -08:00
mod tests {
2025-10-14 14:01:11 -07:00
use super::*;
2026-03-18 17:58:20 -07:00
use common::configuration::{Agent, AgentFilterChain, Listener, ListenerType};
2025-10-14 14:01:11 -07:00
fn create_test_orchestrator_service() -> Arc<OrchestratorService> {
Arc::new(OrchestratorService::new(
2025-10-14 14:01:11 -07:00
"http://localhost:8080".to_string(),
"test-model".to_string(),
"plano-orchestrator".to_string(),
crate::router::orchestrator_model_v1::MAX_TOKEN_LEN,
2025-10-14 14:01:11 -07:00
))
}
fn create_test_message(role: Role, content: &str) -> Message {
Message {
role,
content: Some(MessageContent::Text(content.to_string())),
2025-10-14 14:01:11 -07:00
name: None,
tool_calls: None,
tool_call_id: None,
}
}
#[tokio::test]
async fn test_modular_agent_chat_flow() {
// Setup services
let orchestrator_service = create_test_orchestrator_service();
let agent_selector = AgentSelector::new(orchestrator_service);
let mut pipeline_processor = PipelineProcessor::default();
2025-10-14 14:01:11 -07:00
// Create test data
let agents = vec![
Agent {
id: "filter-agent".to_string(),
agent_type: Some("filter".to_string()),
2025-10-14 14:01:11 -07:00
url: "http://localhost:8081".to_string(),
tool: None,
transport: None,
2025-10-14 14:01:11 -07:00
},
Agent {
id: "terminal-agent".to_string(),
agent_type: Some("terminal".to_string()),
2025-10-14 14:01:11 -07:00
url: "http://localhost:8082".to_string(),
tool: None,
transport: None,
2025-10-14 14:01:11 -07:00
},
];
let agent_pipeline = AgentFilterChain {
id: "terminal-agent".to_string(),
2026-03-18 17:58:20 -07:00
input_filters: Some(vec![
2025-12-25 21:08:37 -08:00
"filter-agent".to_string(),
"terminal-agent".to_string(),
]),
2025-10-14 14:01:11 -07:00
description: Some("Test pipeline".to_string()),
default: Some(true),
};
let listener = Listener {
2026-03-18 17:58:20 -07:00
listener_type: ListenerType::Agent,
2025-10-14 14:01:11 -07:00
name: "test-listener".to_string(),
agents: Some(vec![agent_pipeline.clone()]),
2026-03-18 17:58:20 -07:00
input_filters: None,
output_filters: None,
2025-10-14 14:01:11 -07:00
port: 8080,
router: None,
};
let listeners = vec![listener];
let messages = vec![create_test_message(Role::User, "Hello world!")];
// Test 1: Agent Selection
2026-03-19 17:58:33 -07:00
let selected_listener = agent_selector.find_listener(Some("test-listener"), &listeners);
2025-10-14 14:01:11 -07:00
assert!(selected_listener.is_ok());
let listener = selected_listener.unwrap();
assert_eq!(listener.name, "test-listener");
// Test 2: Agent Map Creation
let agent_map = agent_selector.create_agent_map(&agents);
assert_eq!(agent_map.len(), 2);
assert!(agent_map.contains_key("filter-agent"));
assert!(agent_map.contains_key("terminal-agent"));
// Test 3: Pipeline Processing (empty filter chain for testing)
let request = ChatCompletionsRequest {
messages: messages.clone(),
model: "test-model".to_string(),
..Default::default()
};
// Create a pipeline with empty filter chain to avoid network calls
let test_pipeline = AgentFilterChain {
id: "terminal-agent".to_string(),
2026-03-18 17:58:20 -07:00
input_filters: Some(vec![]), // Empty filter chain - no network calls needed
2025-10-14 14:01:11 -07:00
description: None,
default: None,
};
let headers = HeaderMap::new();
2026-03-18 17:58:20 -07:00
let request_bytes = serde_json::to_vec(&request).expect("failed to serialize request");
2025-10-14 14:01:11 -07:00
let result = pipeline_processor
2026-03-18 17:58:20 -07:00
.process_raw_filter_chain(
&request_bytes,
&test_pipeline,
&agent_map,
&headers,
"/v1/chat/completions",
)
2025-10-14 14:01:11 -07:00
.await;
println!("Pipeline processing result: {:?}", result);
assert!(result.is_ok());
2026-03-18 17:58:20 -07:00
let processed_bytes = result.unwrap();
// With empty filter chain, should return the original bytes unchanged
let processed_request: ChatCompletionsRequest =
serde_json::from_slice(&processed_bytes).expect("failed to deserialize response");
assert_eq!(processed_request.messages.len(), 1);
if let Some(MessageContent::Text(content)) = &processed_request.messages[0].content {
2025-10-14 14:01:11 -07:00
assert_eq!(content, "Hello world!");
} else {
panic!("Expected text content");
}
// Test 4: Error Response Creation
2026-03-19 17:58:33 -07:00
let error_response = ResponseHandler::create_bad_request("Test error");
assert_eq!(error_response.status(), hyper::StatusCode::BAD_REQUEST);
2025-10-14 14:01:11 -07:00
println!("✅ All modular components working correctly!");
}
#[tokio::test]
async fn test_error_handling_flow() {
let orchestrator_service = create_test_orchestrator_service();
let agent_selector = AgentSelector::new(orchestrator_service);
2025-10-14 14:01:11 -07:00
// Test listener not found
2026-03-19 17:58:33 -07:00
let result = agent_selector.find_listener(Some("nonexistent"), &[]);
2025-10-14 14:01:11 -07:00
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
AgentSelectionError::ListenerNotFound(_)
));
2026-03-19 17:58:33 -07:00
// Test error response creation
let error_response = ResponseHandler::create_internal_error("Pipeline failed");
assert_eq!(
error_response.status(),
hyper::StatusCode::INTERNAL_SERVER_ERROR
);
2025-10-14 14:01:11 -07:00
println!("✅ Error handling working correctly!");
}
}