use do_memory_core::{MemoryConfig, SelfLearningMemory};
use do_memory_mcp::MemoryMCPServer;
use do_memory_mcp::types::SandboxConfig;
use std::sync::Arc;
fn get_dispatchable_tool_names() -> Vec<&'static str> {
vec![
"query_memory",
"analyze_patterns",
"health_check",
"get_metrics",
"advanced_pattern_analysis",
"quality_metrics",
"configure_embeddings",
"query_semantic_memory",
"test_embeddings",
"generate_embedding",
"search_by_embedding",
"embedding_provider_status",
"search_patterns",
"recommend_patterns",
"recommend_playbook",
"explain_pattern",
"record_recommendation_session",
"record_recommendation_feedback",
"get_recommendation_stats",
"checkpoint_episode",
"get_handoff_pack",
"resume_from_handoff",
"bulk_episodes",
"create_episode",
"add_episode_step",
"complete_episode",
"get_episode",
"delete_episode",
"update_episode",
"get_episode_timeline",
"add_episode_tags",
"remove_episode_tags",
"set_episode_tags",
"get_episode_tags",
"search_episodes_by_tags",
"add_episode_relationship",
"remove_episode_relationship",
"get_episode_relationships",
"find_related_episodes",
"check_relationship_exists",
"get_dependency_graph",
"validate_no_cycles",
"get_topological_order",
]
}
#[tokio::test]
async fn test_all_advertised_tools_are_dispatchable() {
let server = MemoryMCPServer::new(
SandboxConfig::default(),
Arc::new(SelfLearningMemory::with_config(MemoryConfig {
quality_threshold: 0.0,
batch_config: None,
..Default::default()
})),
)
.await
.expect("Failed to create MCP server");
let advertised_tools = server.list_tools().await;
let advertised_names: Vec<String> = advertised_tools.iter().map(|t| t.name.clone()).collect();
let dispatchable_names = get_dispatchable_tool_names();
let mut missing_handlers: Vec<String> = Vec::new();
for name in &advertised_names {
if !dispatchable_names.contains(&name.as_str()) {
missing_handlers.push(name.clone());
}
}
if !missing_handlers.is_empty() {
eprintln!("\n=== TOOL CONTRACT PARITY FAILURE ===");
eprintln!("The following tools are advertised but have no dispatch handlers:");
for name in &missing_handlers {
eprintln!(" - {}", name);
}
eprintln!("\nThis means clients can see these tools in tools/list but");
eprintln!("will get 'Tool not found' error when calling tools/call.");
eprintln!("\nTo fix this:");
eprintln!("1. Add the handler to handlers.rs match statement, OR");
eprintln!("2. Remove the tool definition from tool_definitions_extended.rs");
eprintln!("=====================================\n");
}
assert!(
missing_handlers.is_empty(),
"Tools advertised but not dispatchable: {:?}",
missing_handlers
);
}
#[tokio::test]
async fn test_dispatch_table_covers_advertised_tools() {
let server = MemoryMCPServer::new(
SandboxConfig::default(),
Arc::new(SelfLearningMemory::with_config(MemoryConfig {
quality_threshold: 0.0,
batch_config: None,
..Default::default()
})),
)
.await
.expect("Failed to create MCP server");
let advertised_tools = server.list_tools().await;
let advertised_names: Vec<&str> = advertised_tools.iter().map(|t| t.name.as_str()).collect();
let dispatchable_names = get_dispatchable_tool_names();
let not_advertised: Vec<&&str> = dispatchable_names
.iter()
.filter(|name| !advertised_names.contains(name))
.collect();
if !not_advertised.is_empty() {
println!("\nInfo: Some dispatchable tools are not currently advertised:");
for name in ¬_advertised {
println!(" - {}", name);
}
}
}
#[tokio::test]
async fn test_unimplemented_batch_tools_not_advertised() {
let server = MemoryMCPServer::new(
SandboxConfig::default(),
Arc::new(SelfLearningMemory::with_config(MemoryConfig {
quality_threshold: 0.0,
batch_config: None,
..Default::default()
})),
)
.await
.expect("Failed to create MCP server");
let advertised_tools = server.list_tools().await;
let advertised_names: Vec<&str> = advertised_tools.iter().map(|t| t.name.as_str()).collect();
let unimplemented_tools = [
"batch_query_episodes",
"batch_pattern_analysis",
"batch_compare_episodes",
];
let mut incorrectly_advertised: Vec<&str> = Vec::new();
for tool in &unimplemented_tools {
if advertised_names.contains(tool) {
incorrectly_advertised.push(tool);
}
}
if !incorrectly_advertised.is_empty() {
eprintln!("\n=== UNIMPLEMENTED TOOLS INCORRECTLY ADVERTISED ===");
eprintln!("The following tools are advertised but have no implementation:");
for name in &incorrectly_advertised {
eprintln!(" - {}", name);
}
eprintln!("\nThese tools are intentionally deferred in WG-053.");
eprintln!("\nTo re-enable these tools:");
eprintln!("1. Implement the handlers in the appropriate module");
eprintln!("2. Add them to the dispatch table in handlers.rs");
eprintln!("3. Add them to get_dispatchable_tool_names() in this test");
eprintln!("===================================================\n");
}
assert!(
incorrectly_advertised.is_empty(),
"Unimplemented tools should not be advertised: {:?}",
incorrectly_advertised
);
}
#[tokio::test]
async fn test_deferred_batch_tools_cannot_be_resolved() {
let server = MemoryMCPServer::new(
SandboxConfig::default(),
Arc::new(SelfLearningMemory::with_config(MemoryConfig {
quality_threshold: 0.0,
batch_config: None,
..Default::default()
})),
)
.await
.expect("Failed to create MCP server");
let deferred_tools = [
"batch_query_episodes",
"batch_pattern_analysis",
"batch_compare_episodes",
];
for tool in deferred_tools {
let result = server.get_tool(tool).await;
assert!(
result.is_none(),
"Deferred tool '{tool}' should not resolve from tool registry"
);
}
}
#[tokio::test]
async fn test_core_tools_always_available() {
let server = MemoryMCPServer::new(
SandboxConfig::default(),
Arc::new(SelfLearningMemory::with_config(MemoryConfig {
quality_threshold: 0.0,
batch_config: None,
..Default::default()
})),
)
.await
.expect("Failed to create MCP server");
let advertised_tools = server.list_tools().await;
let advertised_names: Vec<&str> = advertised_tools.iter().map(|t| t.name.as_str()).collect();
let core_tools = [
"query_memory",
"health_check",
"get_metrics",
"analyze_patterns",
"create_episode",
"add_episode_step",
"complete_episode",
"get_episode",
];
for tool in &core_tools {
assert!(
advertised_names.contains(tool),
"Core tool '{}' should always be advertised",
tool
);
}
}