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_complete_memory_workflow() -> 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 memory_data = [
(
"Rust ownership system explained",
"Programming tutorial",
"Understanding Rust's ownership model",
vec!["rust", "programming", "ownership"],
),
(
"Rust lifetimes and borrowing",
"Advanced Rust concepts",
"How lifetimes work in Rust",
vec!["rust", "programming", "lifetimes"],
),
(
"Python memory management",
"Language comparison",
"How Python handles memory",
vec!["python", "programming", "memory"],
),
(
"JavaScript closures guide",
"Web development",
"Understanding JS closures",
vec!["javascript", "web", "closures"],
),
];
let mut stored_ids = Vec::new();
for (content, context, summary, tags) in memory_data {
let params = json!({
"content": content,
"context": context,
"summary": summary,
"tags": tags
});
let result = handlers.handle_tool_call("store_memory", params).await?;
assert!(result["id"].is_string());
stored_ids.push(result["id"].as_str().unwrap().to_string());
}
let search_params = json!({
"query": "Rust",
"tag_filter": ["rust"],
"max_results": 5
});
let search_results = handlers
.handle_tool_call("search_memory", search_params)
.await?;
let results_array = if search_results.is_array() {
search_results.as_array().unwrap()
} else if search_results["results"].is_array() {
search_results["results"].as_array().unwrap()
} else {
panic!("Unexpected search results format: {:?}", search_results);
};
assert!(
!results_array.is_empty(),
"Should find Rust-related memories. Got {} results",
results_array.len()
);
assert!(results_array.len() >= 2, "Should find both Rust memories");
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()),
"All results should have rust tag"
);
}
let first_id = &stored_ids[0];
let get_params = json!({"id": first_id});
let retrieved = handlers
.handle_tool_call("get_memory", get_params.clone())
.await?;
assert_eq!(retrieved["content"], "Rust ownership system explained");
assert_eq!(retrieved["context"], "Programming tutorial");
let content_search = json!({
"query": "memory management",
"search_strategy": "ContentFirst",
"similarity_threshold": 0.1
});
let content_results = handlers
.handle_tool_call("search_memory", content_search)
.await?;
let content_array = if content_results.is_array() {
content_results.as_array().unwrap()
} else {
content_results["results"].as_array().unwrap()
};
assert!(
!content_array.is_empty(),
"Should find memory management related content"
);
let stats = handlers
.handle_tool_call("get_statistics", json!({}))
.await?;
assert_eq!(stats["total_memories"], 4);
let delete_params = json!({"id": first_id});
let delete_result = handlers
.handle_tool_call("delete_memory", delete_params)
.await?;
assert_eq!(delete_result["deleted"], true);
let get_deleted = handlers.handle_tool_call("get_memory", get_params).await;
assert!(
get_deleted.is_err(),
"Deleted memory should not be retrievable"
);
let final_stats = handlers
.handle_tool_call("get_statistics", json!({}))
.await?;
assert_eq!(final_stats["total_memories"], 3);
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_search_ranking_workflow() -> 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 = [
(
"Machine learning algorithms overview",
"AI research",
"Introduction to ML algorithms",
vec!["machine-learning", "ai", "algorithms"],
),
(
"Deep learning neural networks",
"AI research",
"Neural network architectures",
vec!["deep-learning", "ai", "neural-networks"],
),
(
"Machine learning data preprocessing",
"Data science",
"Preparing data for ML models",
vec!["machine-learning", "data-science", "preprocessing"],
),
(
"Software engineering principles",
"Development",
"Good coding practices",
vec!["software", "engineering", "practices"],
),
];
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": "machine learning",
"search_strategy": "Hybrid",
"similarity_threshold": 0.1,
"max_results": 10
});
let results = handlers
.handle_tool_call("search_memory", search_params)
.await?;
let results_array = if results.is_array() {
results.as_array().unwrap()
} else {
results["results"].as_array().unwrap()
};
assert!(
results_array.len() >= 2,
"Should find multiple relevant results"
);
let mut prev_score = 1.0;
for result in results_array {
let current_score = result["combined_score"].as_f64().unwrap();
assert!(
current_score <= prev_score,
"Results should be sorted by descending score"
);
prev_score = current_score;
assert!(
(0.0..=1.0).contains(¤t_score),
"Score should be between 0 and 1"
);
}
let top_result = &results_array[0];
let content = top_result["content"].as_str().unwrap().to_lowercase();
let tags = top_result["tags"].as_array().unwrap();
let has_ml_in_content = content.contains("machine learning");
let has_ml_in_tags = tags.iter().any(|tag| {
tag.as_str()
.unwrap()
.to_lowercase()
.contains("machine-learning")
});
assert!(
has_ml_in_content || has_ml_in_tags,
"Top result should be most relevant to machine learning"
);
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_progressive_filtering_workflow() -> 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 development with Axum",
"Web backend",
"Building APIs with Axum",
vec!["rust", "web", "backend", "axum"],
),
(
"Rust systems programming",
"Low-level programming",
"Rust for system tools",
vec!["rust", "systems", "low-level"],
),
(
"Python web development with Django",
"Web backend",
"Building web apps with Django",
vec!["python", "web", "backend", "django"],
),
(
"JavaScript frontend frameworks",
"Web frontend",
"React, Vue, Angular comparison",
vec!["javascript", "web", "frontend", "frameworks"],
),
(
"Database design patterns",
"Backend architecture",
"SQL database design",
vec!["database", "backend", "sql", "design"],
),
(
"Mobile app development",
"Mobile development",
"iOS and Android development",
vec!["mobile", "ios", "android", "app"],
),
];
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 broad_search = json!({
"query": "web development"
});
let broad_results = handlers
.handle_tool_call("search_memory", broad_search)
.await?;
let broad_count = if broad_results.is_array() {
broad_results.as_array().unwrap().len()
} else {
broad_results["results"].as_array().unwrap().len()
};
let medium_search = json!({
"query": "web development",
"tag_filter": ["backend"]
});
let medium_results = handlers
.handle_tool_call("search_memory", medium_search)
.await?;
let medium_count = if medium_results.is_array() {
medium_results.as_array().unwrap().len()
} else {
medium_results["results"].as_array().unwrap().len()
};
let narrow_search = json!({
"query": "web development",
"tag_filter": ["rust", "backend"]
});
let narrow_results = handlers
.handle_tool_call("search_memory", narrow_search)
.await?;
let narrow_count = if narrow_results.is_array() {
narrow_results.as_array().unwrap().len()
} else {
narrow_results["results"].as_array().unwrap().len()
};
assert!(
broad_count >= medium_count,
"Broad search should return more results than medium"
);
assert!(
medium_count >= narrow_count,
"Medium search should return more results than narrow"
);
if narrow_count > 0 {
let narrow_array = if narrow_results.is_array() {
narrow_results.as_array().unwrap()
} else {
narrow_results["results"].as_array().unwrap()
};
for result in narrow_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()) ||
tag_strings.contains(&"backend".to_string()),
"Should contain at least one of the requested tags"
);
}
}
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_multi_strategy_comparison_workflow() -> 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 = [
(
"Advanced Rust programming techniques",
"Programming guide",
"Expert-level Rust concepts",
vec!["advanced", "expert"],
),
(
"Beginner Rust tutorial",
"Programming guide",
"Learning Rust from scratch",
vec!["beginner", "tutorial", "rust"],
),
(
"Rust performance optimization",
"Performance guide",
"Making Rust programs faster",
vec!["performance", "optimization"],
),
];
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 query = "Rust programming";
let strategies = ["TagsFirst", "ContentFirst", "Hybrid"];
let mut strategy_results = Vec::new();
for strategy in strategies {
let search_params = json!({
"query": query,
"search_strategy": strategy,
"similarity_threshold": 0.1
});
let results = handlers
.handle_tool_call("search_memory", search_params)
.await?;
let results_array = if results.is_array() {
results.as_array().unwrap()
} else {
results["results"].as_array().unwrap()
};
assert!(
!results_array.is_empty(),
"Strategy {} should find results",
strategy
);
strategy_results.push((strategy, results_array.len(), results));
}
for (strategy, count, _) in &strategy_results {
assert!(
*count > 0,
"Strategy {} should return at least one result",
strategy
);
}
for (strategy, _, results) in strategy_results {
let results_array = if results.is_array() {
results.as_array().unwrap()
} else {
results["results"].as_array().unwrap()
};
let found_explicit = results_array.iter().any(|result| {
result["content"]
.as_str()
.unwrap()
.to_lowercase()
.contains("rust programming")
});
assert!(
found_explicit,
"Strategy {} should find explicit 'Rust programming' content",
strategy
);
}
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_large_dataset_workflow() -> 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 topics = [
("rust", "programming", "systems"),
("python", "data-science", "web"),
("javascript", "web", "frontend"),
("database", "sql", "backend"),
("machine-learning", "ai", "algorithms"),
];
let mut total_stored = 0;
for (topic, category, subcategory) in topics {
for i in 1..=10 {
let content = format!("{} {} example number {}", topic, category, i);
let context = format!("{} context", category);
let summary = format!("Example {} about {} {}", i, topic, category);
let tags = vec![
topic.to_string(),
category.to_string(),
subcategory.to_string(),
];
let params = json!({
"content": content,
"context": context,
"summary": summary,
"tags": tags
});
handlers.handle_tool_call("store_memory", params).await?;
total_stored += 1;
}
}
let stats = handlers
.handle_tool_call("get_statistics", json!({}))
.await?;
assert_eq!(stats["total_memories"], total_stored);
let search_params = json!({
"query": "programming",
"max_results": 5
});
let results = handlers
.handle_tool_call("search_memory", search_params)
.await?;
let results_array = if results.is_array() {
results.as_array().unwrap()
} else {
results["results"].as_array().unwrap()
};
assert!(results_array.len() <= 5, "Should respect max_results limit");
assert!(
!results_array.is_empty(),
"Should find programming-related results"
);
let rust_search = json!({
"query": "rust programming",
"tag_filter": ["rust"],
"max_results": 20
});
let rust_results = handlers
.handle_tool_call("search_memory", rust_search)
.await?;
let rust_array = if rust_results.is_array() {
rust_results.as_array().unwrap()
} else {
rust_results["results"].as_array().unwrap()
};
assert!(
rust_array.len() <= 10,
"Should not exceed actual Rust memories"
);
assert!(!rust_array.is_empty(), "Should find Rust memories");
for result in rust_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()),
"All results should have rust tag"
);
}
let strict_search = json!({
"query": "programming",
"similarity_threshold": 0.9,
"max_results": 50
});
let strict_results = handlers
.handle_tool_call("search_memory", strict_search)
.await?;
let strict_count = if strict_results.is_array() {
strict_results.as_array().unwrap().len()
} else {
strict_results["results"].as_array().unwrap().len()
};
let loose_search = json!({
"query": "programming",
"similarity_threshold": 0.1,
"max_results": 50
});
let loose_results = handlers
.handle_tool_call("search_memory", loose_search)
.await?;
let loose_count = if loose_results.is_array() {
loose_results.as_array().unwrap().len()
} else {
loose_results["results"].as_array().unwrap().len()
};
assert!(
loose_count >= strict_count,
"Lower threshold should return more results"
);
manager.cleanup().await?;
Ok(())
}