use crate::common::TestDatabaseManager;
use anyhow::Result;
use codex_memory::{mcp_server::MCPHandlers, Storage};
use serde_json::json;
use std::sync::Arc;
#[tokio::test]
async fn test_ollama_service_unavailable() -> 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.clone());
let params = json!({
"content": "This content should be stored even if Ollama is down",
"context": "Test context for Ollama service unavailable test",
"summary": "Test summary for Ollama service unavailable test",
"tags": ["ollama-down", "fallback"]
});
let result = handlers.handle_tool_call("store_memory", params).await;
match result {
Ok(response) => {
println!("Content stored successfully despite potential LLM unavailability");
if let Some(id) = response["id"].as_str() {
let retrieved = storage
.get(uuid::Uuid::parse_str(id)?)
.await?
.expect("Should retrieve stored content");
assert_eq!(
retrieved.content,
"This content should be stored even if Ollama is down"
);
assert_eq!(retrieved.tags, vec!["ollama-down", "fallback"]);
println!(
"Context: {:?}, Summary: {:?}",
retrieved.context, retrieved.summary
);
}
}
Err(e) => {
println!("Storage failed when LLM unavailable: {}", e);
}
}
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
async fn test_llm_timeout_scenarios() -> 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.clone());
let timeout_triggering_content = [
"repeat ".repeat(10000),
"This is an extremely long run-on sentence that goes on and on without any punctuation or breaks and might cause the LLM to struggle with processing or generating appropriate summaries or context ".repeat(500),
"English text 中文内容 русский текст العربية मराठी ภาษาไทย 한국어 日本語 ".repeat(100),
"fn main() { let mut vec: Vec<Arc<RwLock<HashMap<String, Box<dyn Trait>>>>> = Vec::new(); }".repeat(1000),
];
for (i, content) in timeout_triggering_content.iter().enumerate() {
println!("Testing potential timeout content #{}", i);
let params = json!({
"content": content,
"context": format!("Timeout test context {}", i),
"summary": format!("Timeout test summary {}", i),
"tags": [format!("timeout-test-{}", i)]
});
let start = std::time::Instant::now();
let result = handlers.handle_tool_call("store_memory", params).await;
let duration = start.elapsed();
println!("Processing took: {:?}", duration);
match result {
Ok(response) => {
println!("Content #{} processed successfully", i);
if duration > std::time::Duration::from_secs(60) {
println!(
"WARNING: Processing took longer than expected: {:?}",
duration
);
}
if let Some(id) = response["id"].as_str() {
let retrieved = storage
.get(uuid::Uuid::parse_str(id)?)
.await?
.expect("Should retrieve content");
assert_eq!(retrieved.content.len(), content.len());
}
}
Err(e) => {
println!("Content #{} failed (timeout expected): {}", i, e);
let error_msg = e.to_string().to_lowercase();
assert!(
error_msg.contains("timeout")
|| error_msg.contains("processing")
|| error_msg.contains("connection")
|| error_msg.contains("unavailable"),
"Error should indicate processing/timeout issue: {}",
e
);
}
}
}
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
async fn test_llm_malformed_response_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.clone());
let problematic_inputs = [
r#"Content with "quotes" and 'apostrophes' and `backticks` and \backslashes"#,
r#"{"this": "looks like json", "but": "it's actually content"}"#,
"Content with\nnewlines\tand\ttabs\rand\0nulls",
"\n\n\n \t\t\t \n\n",
"This content discusses security vulnerabilities and potential attack vectors",
];
for (i, content) in problematic_inputs.iter().enumerate() {
println!(
"Testing problematic input #{}: {}",
i,
content.escape_debug()
);
let params = json!({
"content": content,
"context": format!("Malformed test context {}", i),
"summary": format!("Malformed test summary {}", i),
"tags": [format!("malformed-test-{}", i)]
});
let result = handlers.handle_tool_call("store_memory", params).await;
match result {
Ok(response) => {
println!("Problematic input #{} handled successfully", i);
if let Some(id) = response["id"].as_str() {
let retrieved = storage
.get(uuid::Uuid::parse_str(id)?)
.await?
.expect("Should retrieve content");
assert_eq!(retrieved.content, *content);
assert!(
!retrieved.context.is_empty(),
"Context should not be empty string"
);
assert!(
retrieved.context.len() < 10000,
"Context should not be excessively long"
);
assert!(
!retrieved.summary.is_empty(),
"Summary should not be empty string"
);
assert!(
retrieved.summary.len() < 5000,
"Summary should not be excessively long"
);
}
}
Err(e) => {
println!("Problematic input #{} failed: {}", i, e);
let error_msg = e.to_string();
assert!(
!error_msg.contains("panic"),
"Should not panic on malformed input"
);
assert!(
!error_msg.contains("unwrap"),
"Should handle errors gracefully"
);
}
}
}
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
async fn test_llm_context_length_exceeded() -> 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.clone());
let base_text = "This is a sample paragraph that will be repeated many times to create content that exceeds the context window limits of most language models. It contains various concepts and ideas that might be interesting to summarize. ";
let context_length_tests = vec![
(1000, base_text.repeat(1000)), (2000, base_text.repeat(2000)), (4000, base_text.repeat(4000)), (8000, base_text.repeat(8000)), ];
for (repeat_count, content) in context_length_tests {
println!(
"Testing context length with {} repeats ({} chars)",
repeat_count,
content.len()
);
let params = json!({
"content": content,
"context": format!("Context length test context {}", repeat_count),
"summary": format!("Context length test summary {}", repeat_count),
"tags": [format!("context-length-{}", repeat_count)]
});
let start = std::time::Instant::now();
let result = handlers.handle_tool_call("store_memory", params).await;
let duration = start.elapsed();
println!("Processing took: {:?}", duration);
match result {
Ok(response) => {
println!(
"Large content ({} chars) processed successfully",
content.len()
);
if let Some(id) = response["id"].as_str() {
let retrieved = storage
.get(uuid::Uuid::parse_str(id)?)
.await?
.expect("Should retrieve content");
assert_eq!(retrieved.content.len(), content.len());
let compression_ratio = content.len() as f64 / retrieved.summary.len() as f64;
println!("Compression ratio: {:.2}:1", compression_ratio);
assert!(
compression_ratio > 10.0,
"Summary should compress content significantly"
);
}
}
Err(e) => {
println!("Large content ({} chars) failed: {}", content.len(), e);
let error_msg = e.to_string().to_lowercase();
assert!(
error_msg.contains("too large")
|| error_msg.contains("context")
|| error_msg.contains("limit")
|| error_msg.contains("timeout")
|| error_msg.contains("memory"),
"Error should indicate size/processing limitation: {}",
e
);
}
}
}
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
async fn test_concurrent_llm_requests() -> Result<()> {
let mut manager = TestDatabaseManager::new()?;
let pool = manager.setup_test_database().await?;
let storage = Arc::new(Storage::new(pool));
let handlers = Arc::new(MCPHandlers::new(storage));
let mut handles = vec![];
for i in 0..20 {
let handlers_clone = handlers.clone();
let handle = tokio::spawn(async move {
let content = format!(
"Concurrent LLM test content #{} - This is a longer piece of text that should
trigger LLM processing for context and summary generation. It contains multiple
sentences and concepts that need to be analyzed and processed by the language model
to generate appropriate metadata and summaries.",
i
);
let params = json!({
"content": content,
"context": format!("Concurrent LLM test context {}", i),
"summary": format!("Concurrent LLM test summary {}", i),
"tags": [format!("concurrent-llm-{}", i), "stress-test"]
});
handlers_clone
.handle_tool_call("store_memory", params)
.await
});
handles.push(handle);
}
let timeout_duration = std::time::Duration::from_secs(120); let start = std::time::Instant::now();
let mut successes = 0;
let mut failures = 0;
let mut timeouts = 0;
for handle in handles {
match tokio::time::timeout(timeout_duration, handle).await {
Ok(Ok(Ok(_))) => successes += 1,
Ok(Ok(Err(e))) => {
println!("LLM request failed: {}", e);
failures += 1;
}
Ok(Err(e)) => {
println!("Task failed: {}", e);
failures += 1;
}
Err(_) => {
println!("Request timed out");
timeouts += 1;
}
}
}
let total_duration = start.elapsed();
println!("Concurrent LLM requests completed in {:?}", total_duration);
println!(
"Results: {} succeeded, {} failed, {} timed out",
successes, failures, timeouts
);
assert!(
successes > 0,
"At least some concurrent LLM requests should succeed"
);
assert!(
total_duration < std::time::Duration::from_secs(300),
"Concurrent requests should complete within reasonable time"
);
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
async fn test_llm_service_recovery() -> 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.clone());
println!("Testing LLM service recovery scenarios");
let params1 = json!({
"content": "First request to test service availability",
"context": "Recovery test context for first request",
"summary": "Recovery test summary for first request",
"tags": ["recovery-test", "first"]
});
let result1 = handlers.handle_tool_call("store_memory", params1).await;
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
let params2 = json!({
"content": "Second request after potential recovery",
"context": "Recovery test context for second request",
"summary": "Recovery test summary for second request",
"tags": ["recovery-test", "second"]
});
let result2 = handlers.handle_tool_call("store_memory", params2).await;
match (result1, result2) {
(Ok(_), Ok(_)) => {
println!("Both requests succeeded - service is stable");
}
(Err(e1), Ok(_)) => {
println!("Service recovered: first failed ({}), second succeeded", e1);
}
(Ok(_), Err(e2)) => {
println!("Service degraded: first succeeded, second failed ({})", e2);
}
(Err(e1), Err(e2)) => {
println!("Service unavailable: both failed ({}, {})", e1, e2);
}
}
let basic_params = json!({
"content": "Basic storage test without LLM dependency",
"context": "Basic storage test context",
"summary": "Basic storage test summary"
});
let basic_result = handlers
.handle_tool_call("store_memory", basic_params)
.await;
match basic_result {
Ok(response) => {
println!("Basic storage works independent of LLM service");
if let Some(id) = response["id"].as_str() {
let retrieved = storage
.get(uuid::Uuid::parse_str(id)?)
.await?
.expect("Should retrieve basic content");
assert_eq!(
retrieved.content,
"Basic storage test without LLM dependency"
);
}
}
Err(e) => {
println!("WARNING: Basic storage failed: {}", e);
}
}
manager.cleanup().await?;
Ok(())
}
#[tokio::test]
async fn test_llm_response_validation() -> 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.clone());
let validation_tests = vec![
("Empty response trigger", ""),
(
"JSON breaking quotes",
r#"Content with "nested "quotes" and 'mixed quotes'"#,
),
("Unicode stress test", "🎉🚀💾🔥⭐🎯📝🏷️🌈🔧✨🎨🎪🎭🎪🎨"),
(
"Code-like content",
"fn main() { println!(\"Hello, world!\"); }",
),
("Markdown-like", "# Header\n- List item\n**bold** *italic*"),
];
for (test_name, content) in validation_tests {
println!("Testing LLM response validation: {}", test_name);
let params = json!({
"content": content,
"context": "Validation test context",
"summary": "Validation test summary",
"tags": ["validation-test"]
});
let result = handlers.handle_tool_call("store_memory", params).await;
match result {
Ok(response) => {
if let Some(id) = response["id"].as_str() {
let retrieved = storage
.get(uuid::Uuid::parse_str(id)?)
.await?
.expect("Should retrieve content");
assert!(
retrieved.context.is_ascii() || content.chars().all(|c| !c.is_control()),
"Context should not contain control characters"
);
assert!(
retrieved.context.len() < 50000,
"Context should not be excessively long"
);
if content.len() > 100 {
assert!(
retrieved.summary.len() < content.len(),
"Summary should be shorter than original content"
);
}
assert!(
retrieved.summary.len() < 10000,
"Summary should not be excessively long"
);
println!(" ✅ {} passed validation", test_name);
}
}
Err(e) => {
println!(" ⚠️ {} failed: {}", test_name, e);
}
}
}
manager.cleanup().await?;
Ok(())
}