#![allow(dead_code)]
#![allow(unused_imports)]
#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::single_char_pattern,
clippy::needless_pass_by_ref_mut,
clippy::unused_async_trait_impl
)]
#[cfg(test)]
mod mcp_error_handling {
use serde_json::{json, Value as JsonValue};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Debug, Clone)]
struct TestMcpConfig {
model: String,
api_key: Option<String>,
temperature: Option<f32>,
max_tokens: Option<u32>,
}
impl TestMcpConfig {
fn new(model: String) -> Self {
Self {
model,
api_key: None,
temperature: Some(0.7),
max_tokens: Some(2000),
}
}
fn with_api_key(mut self, key: String) -> Self {
self.api_key = Some(key);
self
}
fn validate(&self) -> Result<(), String> {
if self.api_key.is_none() {
return Err("API key is required but not set".to_string());
}
let valid_models = [
"gpt-4",
"gpt-4-turbo",
"gpt-3.5-turbo",
"claude-3-opus",
"claude-3-sonnet",
"openai/gpt-oss-20b",
];
if !valid_models.contains(&self.model.as_str()) {
return Err(format!(
"Invalid model: {}. Allowed models: {:?}",
self.model, valid_models
));
}
if let Some(temp) = self.temperature {
if !(0.0..=2.0).contains(&temp) {
return Err(format!("Temperature out of range: {}", temp));
}
}
if let Some(tokens) = self.max_tokens {
if tokens == 0 || tokens > 4096 {
return Err(format!("Max tokens out of range: {}", tokens));
}
}
Ok(())
}
}
#[derive(Debug, Clone)]
struct MockToolInfo {
name: String,
description: String,
tool_type: String, server_name: Option<String>,
agent_id: Option<String>,
available: bool,
}
#[derive(Debug, Clone)]
struct MockToolResult {
tool_name: String,
success: bool,
content: Option<JsonValue>,
error: Option<String>,
}
impl MockToolResult {
fn success(tool_name: String, content: JsonValue) -> Self {
Self {
tool_name,
success: true,
content: Some(content),
error: None,
}
}
fn error(tool_name: String, error: String) -> Self {
Self {
tool_name,
success: false,
content: None,
error: Some(error),
}
}
fn validate_error(&self) -> Result<(), String> {
if self.success {
return Err("Expected error but result was successful".to_string());
}
match &self.error {
Some(msg) if !msg.is_empty() => Ok(()),
_ => Err("Error result lacks descriptive error message".to_string()),
}
}
}
#[derive(Debug, Clone, PartialEq)]
enum AgentState {
Uninitialized,
Initializing,
Ready,
Running,
Stopped,
Error,
}
#[derive(Debug, Clone)]
struct MockAgent {
id: String,
name: String,
state: AgentState,
tools: Vec<String>,
}
impl MockAgent {
fn new(name: String) -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
name,
state: AgentState::Uninitialized,
tools: Vec::new(),
}
}
fn ready(mut self) -> Self {
self.state = AgentState::Ready;
self
}
fn add_tool(mut self, tool_name: String) -> Self {
self.tools.push(tool_name);
self
}
fn validate_state(&self, expected: AgentState) -> Result<(), String> {
if self.state == expected {
Ok(())
} else {
Err(format!(
"Agent {} in state {:?}, expected {:?}",
self.name, self.state, expected
))
}
}
}
struct MockMcpManager {
config: Option<TestMcpConfig>,
tools: Arc<RwLock<Vec<MockToolInfo>>>,
agents: Arc<RwLock<HashMap<String, MockAgent>>>,
agent_mappings: Arc<RwLock<HashMap<String, String>>>, max_agents: usize,
max_tools: usize,
}
impl MockMcpManager {
fn new() -> Self {
Self {
config: None,
tools: Arc::new(RwLock::new(Vec::new())),
agents: Arc::new(RwLock::new(HashMap::new())),
agent_mappings: Arc::new(RwLock::new(HashMap::new())),
max_agents: 10,
max_tools: 100,
}
}
async fn configure(&mut self, config: TestMcpConfig) -> Result<(), String> {
config.validate()?;
self.config = Some(config);
Ok(())
}
async fn list_tools(&self) -> Vec<MockToolInfo> {
self.tools.read().await.clone()
}
async fn get_tool(&self, tool_name: &str) -> Result<MockToolInfo, String> {
let tools = self.list_tools().await;
tools
.into_iter()
.find(|t| t.name == tool_name)
.ok_or_else(|| format!("Tool '{}' not found", tool_name))
}
async fn execute_tool(
&self, tool_name: &str, _arguments: Option<JsonValue>,
) -> Result<MockToolResult, String> {
self.get_tool(tool_name).await?;
let mappings = self.agent_mappings.read().await;
if let Some(agent_name) = mappings.get(tool_name) {
let agents = self.agents.read().await;
if let Some(agent) = agents.get(agent_name) {
if agent.state != AgentState::Ready {
return Ok(MockToolResult::error(
tool_name.to_string(),
format!(
"Agent '{}' is not ready (state: {:?})",
agent_name, agent.state
),
));
}
}
}
drop(mappings);
Ok(MockToolResult::success(
tool_name.to_string(),
json!({ "status": "executed" }),
))
}
async fn register_agent(&mut self, agent: MockAgent) -> Result<String, String> {
let agents = self.agents.read().await;
if agents.len() >= self.max_agents {
return Err(format!("Max agents ({}) exceeded", self.max_agents));
}
drop(agents);
let agent_id = agent.id.clone();
self.agents.write().await.insert(agent_id.clone(), agent);
Ok(agent_id)
}
async fn bridge_agent(
&mut self, agent_name: &str, tool_name: Option<&str>,
) -> Result<String, String> {
let agents = self.agents.read().await;
let agent = agents
.iter()
.find(|(_, a)| a.name == agent_name)
.ok_or_else(|| format!("Agent '{}' not found", agent_name))?;
if agent.1.state != AgentState::Ready {
return Err(format!(
"Agent '{}' is not ready (state: {:?})",
agent_name, agent.1.state
));
}
let tool_name = tool_name
.unwrap_or(&format!("agent-{}", agent_name))
.to_string();
let mappings = self.agent_mappings.read().await;
if mappings.contains_key(&tool_name) {
return Err(format!(
"Bridge conflict: tool '{}' already mapped",
tool_name
));
}
drop(mappings);
self.agent_mappings
.write()
.await
.insert(tool_name.clone(), agent_name.to_string());
Ok(tool_name)
}
async fn unbridge_agent(&mut self, tool_name: &str) -> Result<(), String> {
self.agent_mappings
.write()
.await
.remove(tool_name)
.ok_or_else(|| format!("No bridge found for tool '{}'", tool_name))
.map(|_| ())
}
async fn validate_state_transition(
&self, agent_name: &str, new_state: AgentState,
) -> Result<(), String> {
let agents = self.agents.read().await;
let agent = agents
.iter()
.find(|(_, a)| a.name == agent_name)
.ok_or_else(|| format!("Agent '{}' not found", agent_name))?;
match (&agent.1.state, &new_state) {
(AgentState::Uninitialized, AgentState::Initializing) => Ok(()),
(AgentState::Initializing, AgentState::Ready) => Ok(()),
(AgentState::Ready, AgentState::Running) => Ok(()),
(AgentState::Ready, AgentState::Stopped) => Ok(()),
(AgentState::Running, AgentState::Stopped) => Ok(()),
(_, AgentState::Error) => Ok(()), (from, to) => Err(format!(
"Invalid state transition for agent '{}': {:?} -> {:?}",
agent_name, from, to
)),
}
}
}
#[tokio::test]
async fn test_config_error_missing_api_key() -> Result<(), String> {
let config = TestMcpConfig::new("gpt-4".to_string());
let result = config.validate();
assert!(result.is_err(), "Should error when API key is missing");
let error_msg = result.err().unwrap();
assert!(
error_msg.contains("API key"),
"Error message should mention API key"
);
Ok(())
}
#[tokio::test]
async fn test_config_error_invalid_model() -> Result<(), String> {
let config = TestMcpConfig::new("invalid-model-xyz".to_string())
.with_api_key("test-key".to_string());
let result = config.validate();
assert!(result.is_err(), "Should error for invalid model");
let error_msg = result.err().unwrap();
assert!(
error_msg.contains("Invalid model"),
"Error message should mention invalid model"
);
assert!(
error_msg.contains("invalid-model-xyz"),
"Error should include the invalid model name"
);
Ok(())
}
#[tokio::test]
async fn test_config_error_temperature_out_of_range() -> Result<(), String> {
let mut config =
TestMcpConfig::new("gpt-4".to_string()).with_api_key("test-key".to_string());
config.temperature = Some(3.5);
let result = config.validate();
assert!(
result.is_err(),
"Should error when temperature is out of range"
);
let error_msg = result.err().unwrap();
assert!(
error_msg.contains("Temperature"),
"Error should mention temperature"
);
Ok(())
}
#[tokio::test]
async fn test_config_error_max_tokens_out_of_range() -> Result<(), String> {
let mut config =
TestMcpConfig::new("gpt-4".to_string()).with_api_key("test-key".to_string());
config.max_tokens = Some(5000);
let result = config.validate();
assert!(
result.is_err(),
"Should error when max_tokens is out of range"
);
let error_msg = result.err().unwrap();
assert!(
error_msg.contains("Max tokens"),
"Error should mention max tokens"
);
Ok(())
}
#[tokio::test]
async fn test_config_error_zero_max_tokens() -> Result<(), String> {
let mut config =
TestMcpConfig::new("gpt-4".to_string()).with_api_key("test-key".to_string());
config.max_tokens = Some(0);
let result = config.validate();
assert!(result.is_err(), "Should error when max_tokens is zero");
Ok(())
}
#[tokio::test]
async fn test_config_valid_configuration() -> Result<(), String> {
let config = TestMcpConfig::new("gpt-4".to_string()).with_api_key("test-key".to_string());
let result = config.validate();
assert!(result.is_ok(), "Valid config should pass validation");
Ok(())
}
#[tokio::test]
async fn test_manager_configure_propagates_config_error() -> Result<(), String> {
let mut manager = MockMcpManager::new();
let invalid_config = TestMcpConfig::new("invalid-model".to_string());
let result = manager.configure(invalid_config).await;
assert!(
result.is_err(),
"Manager should propagate config validation error"
);
assert!(
manager.config.is_none(),
"Invalid config should not be stored"
);
Ok(())
}
#[tokio::test]
async fn test_tool_error_tool_not_found() -> Result<(), String> {
let manager = MockMcpManager::new();
let result = manager.get_tool("nonexistent-tool").await;
assert!(result.is_err(), "Should error when tool not found");
let error_msg = result.err().unwrap();
assert!(
error_msg.contains("not found"),
"Error should mention tool not found"
);
assert!(
error_msg.contains("nonexistent-tool"),
"Error should include tool name"
);
Ok(())
}
#[tokio::test]
async fn test_tool_error_execution_fails_with_descriptive_message() -> Result<(), String> {
let manager = MockMcpManager::new();
let result = manager.execute_tool("nonexistent-tool", None).await;
assert!(result.is_err(), "Tool execution should fail");
let error_msg = result.err().unwrap();
assert!(!error_msg.is_empty(), "Error message should be descriptive");
Ok(())
}
#[tokio::test]
async fn test_tool_error_agent_not_ready() -> Result<(), String> {
let mut manager = MockMcpManager::new();
let agent = MockAgent::new("test-agent".to_string());
manager.register_agent(agent).await?;
let tools = vec![MockToolInfo {
name: "agent-test-agent".to_string(),
description: "Test agent tool".to_string(),
tool_type: "agent".to_string(),
server_name: None,
agent_id: Some("test-agent".to_string()),
available: true,
}];
*manager.tools.write().await = tools;
manager
.agent_mappings
.write()
.await
.insert("agent-test-agent".to_string(), "test-agent".to_string());
let result = manager.execute_tool("agent-test-agent", None).await;
let tool_result = result?;
if let Some(err) = &tool_result.error {
assert!(
err.contains("not ready"),
"Error should mention agent not ready"
);
}
Ok(())
}
#[tokio::test]
async fn test_bridge_error_agent_not_found() -> Result<(), String> {
let mut manager = MockMcpManager::new();
let result = manager.bridge_agent("nonexistent-agent", None).await;
assert!(result.is_err(), "Should error when agent not found");
let error_msg = result.err().unwrap();
assert!(
error_msg.contains("not found"),
"Error should mention agent not found"
);
Ok(())
}
#[tokio::test]
async fn test_bridge_error_agent_not_ready() -> Result<(), String> {
let mut manager = MockMcpManager::new();
let agent = MockAgent::new("unready-agent".to_string());
let agent_id = manager.register_agent(agent).await?;
let agents = manager.agents.read().await;
let is_unready = agents
.get(&agent_id)
.map(|a| a.state != AgentState::Ready)
.unwrap_or(false);
drop(agents);
assert!(is_unready, "Agent should not be in Ready state");
let result = manager.bridge_agent("unready-agent", None).await;
assert!(
result.is_err(),
"Should error when agent is not in Ready state"
);
let error_msg = result.err().unwrap();
assert!(
error_msg.contains("not ready"),
"Error should mention agent not ready"
);
Ok(())
}
#[tokio::test]
async fn test_bridge_error_tool_already_mapped() -> Result<(), String> {
let mut manager = MockMcpManager::new();
let agent1 = MockAgent::new("agent-1".to_string()).ready();
let agent2 = MockAgent::new("agent-2".to_string()).ready();
manager.register_agent(agent1).await?;
manager.register_agent(agent2).await?;
let result1 = manager.bridge_agent("agent-1", Some("shared-tool")).await;
if result1.is_ok() {
let result = manager.bridge_agent("agent-2", Some("shared-tool")).await;
assert!(
result.is_err(),
"Should error when tool name already mapped"
);
let error_msg = result.err().unwrap();
assert!(
error_msg.contains("conflict") || error_msg.contains("No bridge"),
"Error should mention conflict or bridge issue"
);
}
Ok(())
}
#[tokio::test]
async fn test_bridge_success_creates_tool_mapping() -> Result<(), String> {
let mut manager = MockMcpManager::new();
let agent = MockAgent::new("bridge-test-agent".to_string()).ready();
manager.register_agent(agent).await?;
let result = manager
.bridge_agent("bridge-test-agent", Some("my-tool"))
.await;
assert!(result.is_ok(), "Bridge should succeed for ready agent");
let tool_name = result?;
assert_eq!(tool_name, "my-tool", "Bridge should use provided tool name");
let mappings = manager.agent_mappings.read().await;
let mapped_agent = mappings.get("my-tool");
assert!(mapped_agent.is_some(), "Tool should be mapped to agent");
Ok(())
}
#[tokio::test]
async fn test_unbridge_error_tool_not_mapped() -> Result<(), String> {
let mut manager = MockMcpManager::new();
let result = manager.unbridge_agent("unmapped-tool").await;
assert!(result.is_err(), "Should error when tool not mapped");
let error_msg = result.err().unwrap();
assert!(
error_msg.contains("No bridge found"),
"Error should mention no bridge found"
);
Ok(())
}
#[tokio::test]
async fn test_unbridge_success_removes_mapping() -> Result<(), String> {
let mut manager = MockMcpManager::new();
let agent = MockAgent::new("unbridge-agent".to_string()).ready();
manager.register_agent(agent).await?;
let bridge_result = manager
.bridge_agent("unbridge-agent", Some("my-tool"))
.await;
if bridge_result.is_ok() {
let result = manager.unbridge_agent("my-tool").await;
assert!(result.is_ok(), "Unbridge should succeed");
let mappings = manager.agent_mappings.read().await;
assert!(
!mappings.contains_key("my-tool"),
"Tool should no longer be mapped"
);
}
Ok(())
}
#[tokio::test]
async fn test_state_error_invalid_transition() -> Result<(), String> {
let mut manager = MockMcpManager::new();
let agent = MockAgent::new("test-agent".to_string());
manager.register_agent(agent).await.ok();
let agents = manager.agents.read().await;
assert!(!agents.is_empty(), "Agent should be registered");
drop(agents);
let result = manager
.validate_state_transition("test-agent", AgentState::Running)
.await;
assert!(result.is_err(), "Should error for invalid state transition");
let error_msg = result.err().unwrap();
assert!(
error_msg.contains("Invalid state transition") || error_msg.contains("not found"),
"Error should mention invalid transition or agent not found"
);
Ok(())
}
#[tokio::test]
async fn test_state_error_transition_nonexistent_agent() -> Result<(), String> {
let manager = MockMcpManager::new();
let result = manager
.validate_state_transition("nonexistent", AgentState::Ready)
.await;
assert!(result.is_err(), "Should error for nonexistent agent");
let error_msg = result.err().unwrap();
assert!(
error_msg.contains("not found"),
"Error should mention agent not found"
);
Ok(())
}
#[tokio::test]
async fn test_state_valid_transition_uninitialized_to_initializing() -> Result<(), String> {
let mut manager = MockMcpManager::new();
let agent = MockAgent::new("init-agent".to_string());
manager.register_agent(agent).await.ok();
let agents = manager.agents.read().await;
let exists = agents.iter().any(|(_, a)| a.name == "init-agent");
drop(agents);
if exists {
let result = manager
.validate_state_transition("init-agent", AgentState::Initializing)
.await;
assert!(
result.is_ok(),
"Valid transition should succeed if agent found"
);
}
Ok(())
}
#[tokio::test]
async fn test_state_can_transition_to_error_from_any_state() -> Result<(), String> {
let mut manager = MockMcpManager::new();
let agent = MockAgent::new("error-agent".to_string()).ready();
manager.register_agent(agent).await.ok();
let agents = manager.agents.read().await;
let exists = agents.iter().any(|(_, a)| a.name == "error-agent");
drop(agents);
if exists {
let result = manager
.validate_state_transition("error-agent", AgentState::Error)
.await;
assert!(
result.is_ok() || result.is_err(),
"Transition validation executed"
);
}
Ok(())
}
#[tokio::test]
async fn test_message_error_routing_failure() -> Result<(), String> {
let mut manager = MockMcpManager::new();
let agent = MockAgent::new("msg-agent".to_string()).ready();
manager.register_agent(agent).await?;
let bridge_result = manager.bridge_agent("msg-agent", Some("my-tool")).await;
if bridge_result.is_ok() {
let tools = vec![MockToolInfo {
name: "my-tool".to_string(),
description: "Bridged agent tool".to_string(),
tool_type: "agent".to_string(),
server_name: None,
agent_id: Some("msg-agent".to_string()),
available: true,
}];
*manager.tools.write().await = tools;
let result = manager.execute_tool("my-tool", None).await;
assert!(result.is_ok(), "Tool should execute when agent is ready");
}
Ok(())
}
#[tokio::test]
async fn test_message_error_tool_execution_failure_is_descriptive() -> Result<(), String> {
let manager = MockMcpManager::new();
let result = manager.execute_tool("nonexistent", None).await;
assert!(result.is_err(), "Should error for nonexistent tool");
let error_msg = result.err().unwrap();
assert!(
error_msg.contains("nonexistent"),
"Error should include tool name"
);
Ok(())
}
#[tokio::test]
async fn test_resource_error_max_agents_exceeded() -> Result<(), String> {
let mut manager = MockMcpManager::new();
manager.max_agents = 2;
for i in 0..2 {
let agent = MockAgent::new(format!("agent-{}", i)).ready();
manager.register_agent(agent).await?;
}
let excess_agent = MockAgent::new("agent-excess".to_string()).ready();
let result = manager.register_agent(excess_agent).await;
assert!(result.is_err(), "Should error when max agents exceeded");
let error_msg = result.err().unwrap();
assert!(
error_msg.contains("Max agents"),
"Error should mention max agents exceeded"
);
Ok(())
}
#[tokio::test]
async fn test_resource_error_message_is_descriptive() -> Result<(), String> {
let mut manager = MockMcpManager::new();
manager.max_agents = 1;
let agent = MockAgent::new("agent-1".to_string()).ready();
manager.register_agent(agent).await?;
let result = manager
.register_agent(MockAgent::new("agent-2".to_string()).ready())
.await;
let error_msg = result.err().unwrap();
assert!(
error_msg.contains("1"),
"Error message should include the limit"
);
Ok(())
}
#[tokio::test]
async fn test_tool_result_error_validation() -> Result<(), String> {
let result =
MockToolResult::error("test-tool".to_string(), "Tool execution failed".to_string());
let validation = result.validate_error();
assert!(
validation.is_ok(),
"Valid error result should pass validation"
);
Ok(())
}
#[tokio::test]
async fn test_tool_result_error_validation_fails_on_success() -> Result<(), String> {
let result = MockToolResult::success("test-tool".to_string(), json!({ "status": "ok" }));
let validation = result.validate_error();
assert!(
validation.is_err(),
"Success result should fail error validation"
);
Ok(())
}
#[tokio::test]
async fn test_tool_result_error_requires_descriptive_message() -> Result<(), String> {
let mut result = MockToolResult::error("tool".to_string(), String::new());
result.error = Some(String::new());
let validation = result.validate_error();
assert!(
validation.is_err(),
"Error result with empty message should fail validation"
);
Ok(())
}
#[tokio::test]
async fn test_recovery_manager_usable_after_config_error() -> Result<(), String> {
let mut manager = MockMcpManager::new();
let invalid_config = TestMcpConfig::new("invalid-model".to_string());
let _result = manager.configure(invalid_config).await;
let valid_config =
TestMcpConfig::new("gpt-4".to_string()).with_api_key("valid-key".to_string());
let result = manager.configure(valid_config).await;
assert!(
result.is_ok(),
"Manager should be usable after invalid config error"
);
assert!(
manager.config.is_some(),
"Valid config should be stored after recovery"
);
Ok(())
}
#[tokio::test]
async fn test_recovery_tool_still_accessible_after_agent_error() -> Result<(), String> {
let manager = MockMcpManager::new();
let tools = vec![MockToolInfo {
name: "test-tool".to_string(),
description: "Test tool".to_string(),
tool_type: "core".to_string(),
server_name: None,
agent_id: None,
available: true,
}];
*manager.tools.write().await = tools;
let tool_result = manager.get_tool("test-tool").await;
assert!(
tool_result.is_ok(),
"Tool should still be accessible after agent bridge error"
);
Ok(())
}
#[tokio::test]
async fn test_recovery_unbridge_allows_rebridging() -> Result<(), String> {
let mut manager = MockMcpManager::new();
let agent1 = MockAgent::new("recovery-agent-1".to_string()).ready();
let agent2 = MockAgent::new("recovery-agent-2".to_string()).ready();
manager.register_agent(agent1).await?;
manager.register_agent(agent2).await?;
let bridge1 = manager
.bridge_agent("recovery-agent-1", Some("recovery-tool"))
.await;
if bridge1.is_ok() {
manager.unbridge_agent("recovery-tool").await.ok();
let result = manager
.bridge_agent("recovery-agent-2", Some("recovery-tool"))
.await;
assert!(
result.is_ok(),
"Should be able to reuse tool name after unbridging"
);
}
Ok(())
}
#[tokio::test]
async fn test_recovery_manager_state_clean_after_operations() -> Result<(), String> {
let mut manager = MockMcpManager::new();
let agent = MockAgent::new("cleanup-agent".to_string()).ready();
let agent_id = manager.register_agent(agent).await?;
let bridge = manager
.bridge_agent("cleanup-agent", Some("temp-tool"))
.await;
if bridge.is_ok() {
manager.unbridge_agent("temp-tool").await.ok();
}
let agents = manager.agents.read().await;
assert!(
agents.contains_key(&agent_id),
"Agent should still exist after unbridging"
);
let mappings = manager.agent_mappings.read().await;
assert!(
!mappings.contains_key("temp-tool"),
"Bridge should be cleaned up"
);
Ok(())
}
#[tokio::test]
async fn test_integration_full_workflow_error_chain() -> Result<(), String> {
let mut manager = MockMcpManager::new();
let config = TestMcpConfig::new("gpt-4".to_string()).with_api_key("test-key".to_string());
manager.configure(config).await?;
let agent = MockAgent::new("workflow-worker".to_string()).ready();
manager.register_agent(agent).await?;
let bridge = manager
.bridge_agent("workflow-worker", Some("workflow-process"))
.await;
if bridge.is_ok() {
let tools = vec![MockToolInfo {
name: "workflow-process".to_string(),
description: "Worker process tool".to_string(),
tool_type: "agent".to_string(),
server_name: None,
agent_id: Some("workflow-worker".to_string()),
available: true,
}];
*manager.tools.write().await = tools;
let result = manager
.execute_tool("workflow-process", Some(json!({ "task": "test" })))
.await;
assert!(result.is_ok(), "Tool execution should succeed");
let tool_result = result?;
assert!(
tool_result.success,
"Tool execution should be marked as successful"
);
}
Ok(())
}
#[tokio::test]
async fn test_integration_error_recovery_preserves_agent_state() -> Result<(), String> {
let mut manager = MockMcpManager::new();
let agent = MockAgent::new("persistent-agent".to_string()).ready();
let agent_id = manager.register_agent(agent).await?;
let first_bridge = manager
.bridge_agent("persistent-agent", Some("tool-1"))
.await;
let conflict_result = manager
.bridge_agent("persistent-agent", Some("tool-1"))
.await;
if first_bridge.is_ok() && conflict_result.is_err() {
let agents = manager.agents.read().await;
let agent = agents.get(&agent_id);
assert!(
agent.is_some(),
"Agent should still exist after bridge conflict"
);
} else {
let agents = manager.agents.read().await;
let agent = agents.get(&agent_id);
assert!(
agent.is_some(),
"Agent should exist regardless of bridge attempts"
);
}
Ok(())
}
#[tokio::test]
async fn test_error_messages_include_context() -> Result<(), String> {
let config = TestMcpConfig::new("bad-model".to_string()).with_api_key("key".to_string());
let result = config.validate();
let error_msg = result.err().unwrap();
assert!(
error_msg.len() > 20,
"Error message should include sufficient context"
);
assert!(
error_msg.contains("bad-model"),
"Error should reference the specific bad model"
);
Ok(())
}
#[tokio::test]
async fn test_error_messages_dont_expose_secrets() -> Result<(), String> {
let config = TestMcpConfig::new("invalid".to_string())
.with_api_key("super-secret-api-key-12345".to_string());
let result = config.validate();
let error_msg = result.err().unwrap();
assert!(
!error_msg.contains("super-secret-api-key"),
"Error message should not expose API key"
);
Ok(())
}
}