use crate::common::TestDatabaseManager;
use anyhow::Result;
use codex_memory::mcp_server::MCPHandlers;
use codex_memory::Storage;
use serde_json::json;
use serial_test::serial;
use std::sync::Arc;
#[tokio::test]
#[serial]
async fn test_mcp_store_with_all_fields() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Arc::new(Storage::new(pool));
let handlers = MCPHandlers::new(storage);
let params = json!({
"content": "Test content for MCP integration",
"context": "Integration test context",
"summary": "Testing MCP with all fields populated",
"tags": ["mcp", "integration", "test"]
});
let result = handlers.handle_tool_call("store_memory", params).await?;
assert!(result["id"].is_string());
assert_eq!(result["message"], "Memory stored successfully");
let id = result["id"].as_str().unwrap();
let get_params = json!({"id": id});
let retrieved = handlers.handle_tool_call("get_memory", get_params).await?;
assert_eq!(retrieved["content"], "Test content for MCP integration");
assert_eq!(retrieved["context"], "Integration test context");
assert_eq!(
retrieved["summary"],
"Testing MCP with all fields populated"
);
assert_eq!(retrieved["tags"], json!(["mcp", "integration", "test"]));
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_mcp_store_content_only() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Arc::new(Storage::new(pool));
let handlers = MCPHandlers::new(storage);
let params = json!({
"content": "Just the content, nothing else",
"context": "Default context",
"summary": "Default summary",
"tags": []
});
let result = handlers.handle_tool_call("store_memory", params).await?;
assert!(result["id"].is_string());
assert_eq!(result["message"], "Memory stored successfully");
let id = result["id"].as_str().unwrap();
let get_params = json!({"id": id});
let retrieved = handlers.handle_tool_call("get_memory", get_params).await?;
assert_eq!(retrieved["content"], "Just the content, nothing else");
assert_eq!(retrieved["context"], "Default context");
assert_eq!(retrieved["summary"], "Default summary");
assert_eq!(retrieved["tags"], json!([]));
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_mcp_store_with_partial_fields() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Arc::new(Storage::new(pool));
let handlers = MCPHandlers::new(storage);
let params = json!({
"content": "Content with summary but minimal context",
"context": "Partial test context",
"summary": "A brief summary",
"tags": ["partial"]
});
let result = handlers.handle_tool_call("store_memory", params).await?;
assert!(result["id"].is_string());
let id = result["id"].as_str().unwrap();
let get_params = json!({"id": id});
let retrieved = handlers.handle_tool_call("get_memory", get_params).await?;
assert_eq!(retrieved["summary"], "A brief summary");
assert_eq!(retrieved["context"], "Partial test context");
assert_eq!(retrieved["tags"], json!(["partial"]));
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_mcp_delete_memory() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Arc::new(Storage::new(pool));
let handlers = MCPHandlers::new(storage);
let store_params = json!({
"content": "Content to be deleted",
"context": "Test delete context",
"summary": "Test delete summary",
"tags": []
});
let store_result = handlers
.handle_tool_call("store_memory", store_params)
.await?;
let id = store_result["id"].as_str().unwrap();
let delete_params = json!({"id": id});
let delete_result = handlers
.handle_tool_call("delete_memory", delete_params)
.await?;
assert_eq!(delete_result["deleted"], true);
assert_eq!(delete_result["message"], "Memory deleted successfully");
let get_params = json!({"id": id});
let get_result = handlers.handle_tool_call("get_memory", get_params).await;
assert!(get_result.is_err());
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_mcp_get_statistics() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Arc::new(Storage::new(pool));
let handlers = MCPHandlers::new(storage);
let stats = handlers
.handle_tool_call("get_statistics", json!({}))
.await?;
assert_eq!(stats["total_memories"], 0);
for i in 0..3 {
let params = json!({
"content": format!("Memory {}", i),
"context": format!("Context for memory {}", i),
"summary": format!("Summary for memory {}", i),
"tags": []
});
handlers.handle_tool_call("store_memory", params).await?;
}
let stats = handlers
.handle_tool_call("get_statistics", json!({}))
.await?;
assert_eq!(stats["total_memories"], 3);
assert!(stats["last_memory_created"].is_string());
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_mcp_error_handling() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Arc::new(Storage::new(pool));
let handlers = MCPHandlers::new(storage);
let params = json!({
"summary": "Summary without content"
});
let result = handlers.handle_tool_call("store_memory", params).await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Missing content"));
let params = json!({"id": "not-a-uuid"});
let result = handlers.handle_tool_call("get_memory", params).await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Invalid UUID"));
let params = json!({"id": "00000000-0000-0000-0000-000000000000"});
let result = handlers.handle_tool_call("get_memory", params).await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Memory not found"));
let result = handlers.handle_tool_call("unknown_tool", json!({})).await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Unknown tool"));
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_mcp_content_not_modified() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Arc::new(Storage::new(pool));
let handlers = MCPHandlers::new(storage);
let test_contents = vec![
"Plain text",
"Text with 'single quotes'",
r#"Text with "double quotes""#,
"Multi\nline\ntext",
"Text with special chars: !@#$%^&*()",
" Spaces at start and end ",
"Text\twith\ttabs",
"Emoji content 🎉 🚀 💾",
];
for content in test_contents {
let params = json!({
"content": content,
"context": "Content preservation test",
"summary": "Testing content storage without modification",
"tags": []
});
let result = handlers.handle_tool_call("store_memory", params).await?;
let id = result["id"].as_str().unwrap();
let get_params = json!({"id": id});
let retrieved = handlers.handle_tool_call("get_memory", get_params).await?;
assert_eq!(
retrieved["content"].as_str().unwrap(),
content,
"Content should be stored without modification"
);
}
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_mcp_multiple_storage_operations() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Arc::new(Storage::new(pool));
let handlers = MCPHandlers::new(storage);
let test_cases = [
("First test content", "programming", "testing"),
("Second test content", "debugging", "validation"),
("Third test content", "documentation", "writing"),
];
for (i, (content, tag1, tag2)) in test_cases.iter().enumerate() {
let params = json!({
"content": format!("{} - {}", content, i),
"context": format!("Test context for operation {}", i),
"summary": format!("Test summary for operation {}", i),
"tags": [tag1, tag2, "mcp-test"]
});
let result = handlers.handle_tool_call("store_memory", params).await?;
let id = result["id"].as_str().unwrap();
let get_params = json!({"id": id});
let retrieved = handlers.handle_tool_call("get_memory", get_params).await?;
assert_eq!(retrieved["content"], format!("{} - {}", content, i));
assert_eq!(retrieved["tags"], json!([tag1, tag2, "mcp-test"]));
}
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_mcp_search_memory_basic() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Arc::new(Storage::new(pool));
let handlers = MCPHandlers::new(storage);
let memories = [
(
"Rust programming tutorial",
"Programming guide",
"Learn Rust basics",
vec!["rust", "programming"],
),
(
"Python data analysis",
"Data science context",
"Analyze data with Python",
vec!["python", "data-science"],
),
(
"Web development tips",
"Frontend development",
"Modern web development practices",
vec!["web", "frontend"],
),
];
for (content, context, summary, tags) in memories {
let params = json!({
"content": content,
"context": context,
"summary": summary,
"tags": tags
});
handlers.handle_tool_call("store_memory", params).await?;
}
let search_params = json!({
"query": "programming"
});
let results = handlers
.handle_tool_call("search_memory", search_params)
.await?;
assert!(results.is_array(), "Search should return array of results");
let results_array = results.as_array().unwrap();
assert!(
!results_array.is_empty(),
"Should find programming-related results"
);
let first_result = &results_array[0];
assert!(first_result["id"].is_string());
assert!(first_result["content"].is_string());
assert!(first_result["combined_score"].is_number());
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_mcp_search_memory_with_filters() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Arc::new(Storage::new(pool));
let handlers = MCPHandlers::new(storage);
let memories = [
(
"Rust web framework",
"Web development",
"Building web apps with Rust",
vec!["rust", "web"],
),
(
"Python web framework",
"Web development",
"Building web apps with Python",
vec!["python", "web"],
),
(
"Database design",
"Backend development",
"Database schema design principles",
vec!["database", "backend"],
),
];
for (content, context, summary, tags) in memories {
let params = json!({
"content": content,
"context": context,
"summary": summary,
"tags": tags
});
handlers.handle_tool_call("store_memory", params).await?;
}
let search_params = json!({
"query": "web",
"tag_filter": ["rust"],
"max_results": 5
});
let results = handlers
.handle_tool_call("search_memory", search_params)
.await?;
let results_array = results.as_array().unwrap();
for result in results_array {
let tags = result["tags"].as_array().unwrap();
let tag_strings: Vec<String> = tags
.iter()
.map(|t| t.as_str().unwrap().to_string())
.collect();
assert!(
tag_strings.contains(&"rust".to_string()),
"Result should contain rust tag"
);
}
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_mcp_search_memory_different_strategies() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Arc::new(Storage::new(pool));
let handlers = MCPHandlers::new(storage);
let params = json!({
"content": "Machine learning algorithms and techniques",
"context": "AI research context",
"summary": "Overview of ML algorithms",
"tags": ["machine-learning", "ai", "algorithms"]
});
handlers.handle_tool_call("store_memory", params).await?;
let strategies = ["TagsFirst", "ContentFirst", "Hybrid"];
for strategy in strategies {
let search_params = json!({
"query": "machine learning",
"search_strategy": strategy,
"similarity_threshold": 0.1
});
let result = handlers
.handle_tool_call("search_memory", search_params)
.await;
assert!(
result.is_ok(),
"Search should succeed for strategy: {}",
strategy
);
let results = result.unwrap();
assert!(
results["search_metadata"]["search_strategy"]
.as_str()
.unwrap()
== strategy
);
}
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_mcp_search_memory_parameters() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Arc::new(Storage::new(pool));
let handlers = MCPHandlers::new(storage);
for i in 1..=10 {
let params = json!({
"content": format!("Test memory content number {}", i),
"context": "Test context",
"summary": "Test summary",
"tags": ["test"]
});
handlers.handle_tool_call("store_memory", params).await?;
}
let search_params = json!({
"query": "test memory",
"max_results": 3
});
let results = handlers
.handle_tool_call("search_memory", search_params)
.await?;
let results_array = results.as_array().unwrap();
assert!(results_array.len() <= 3, "Should respect max_results limit");
let search_params = json!({
"query": "test memory",
"similarity_threshold": 0.9
});
let results = handlers
.handle_tool_call("search_memory", search_params)
.await?;
assert!(results["search_metadata"]["similarity_threshold"] == 0.9);
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_mcp_search_memory_error_handling() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Arc::new(Storage::new(pool));
let handlers = MCPHandlers::new(storage);
let params = json!({
"max_results": 10
});
let result = handlers.handle_tool_call("search_memory", params).await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("query"));
let params = json!({
"query": "test",
"similarity_threshold": 1.5 });
let result = handlers.handle_tool_call("search_memory", params).await;
assert!(result.is_err());
let params = json!({
"query": "test",
"max_results": -1
});
let result = handlers.handle_tool_call("search_memory", params).await;
assert!(result.is_err());
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_mcp_search_memory_empty_results() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Arc::new(Storage::new(pool));
let handlers = MCPHandlers::new(storage);
let params = json!({
"content": "Blockchain technology overview",
"context": "Crypto context",
"summary": "Understanding blockchain",
"tags": ["blockchain", "crypto"]
});
handlers.handle_tool_call("store_memory", params).await?;
let search_params = json!({
"query": "cooking recipes pasta italian cuisine",
"similarity_threshold": 0.8
});
let results = handlers
.handle_tool_call("search_memory", search_params)
.await?;
let results_array = results.as_array().unwrap();
assert!(
results_array.len() < 2,
"Should return few results for unrelated search"
);
assert_eq!(
results["search_metadata"]["total_results"],
results_array.len()
);
manager.cleanup().await?;
Ok(())
}