use codex_memory::{
config::Config,
error::Error,
mcp_server::{handlers::MCPHandlers, MCPServer},
storage::Storage,
};
use serde_json::{json, Value};
use std::sync::Arc;
use crate::common::test_db_manager::TestDatabaseManager;
use tokio::time::{timeout, Duration};
#[tokio::test]
async fn test_json_rpc_error_codes() -> Result<(), Box<dyn std::error::Error>> {
let mut db_manager = TestDatabaseManager::new()?;
let pool = db_manager.setup_test_database().await?;
let config = Config {
database_url: db_manager.get_test_database_url(),
mcp_port: 3333,
log_level: "info".to_string(),
};
let storage = Arc::new(Storage::new(pool));
let server = MCPServer::new(config, storage);
let malformed_json = r#"{"jsonrpc":"2.0","method":"test","id":1"#; let response = server.handle_request(malformed_json).await;
let response_json: Value = serde_json::from_str(&response)?;
assert_eq!(response_json["error"]["code"], -32700);
assert!(response_json["error"]["message"]
.as_str()
.unwrap()
.contains("Parse error"));
let invalid_request = r#"{"jsonrpc":"2.0","id":1}"#;
let response = server.handle_request(invalid_request).await;
let response_json: Value = serde_json::from_str(&response)?;
assert_eq!(response_json["error"]["code"], -32600);
assert!(response_json["error"]["message"]
.as_str()
.unwrap()
.contains("Invalid request"));
let unknown_method = r#"{"jsonrpc":"2.0","method":"unknown_method","id":1}"#;
let response = server.handle_request(unknown_method).await;
let response_json: Value = serde_json::from_str(&response)?;
assert_eq!(response_json["error"]["code"], -32601);
assert!(response_json["error"]["message"]
.as_str()
.unwrap()
.contains("Method not found"));
let invalid_params = r#"{"jsonrpc":"2.0","method":"tools/call","params":{"name":"store_memory"},"id":1}"#;
let response = server.handle_request(invalid_params).await;
let response_json: Value = serde_json::from_str(&response)?;
assert_eq!(response_json["error"]["code"], -32602);
assert!(response_json["error"]["message"]
.as_str()
.unwrap()
.contains("Invalid params"));
db_manager.cleanup().await?;
Ok(())
}
#[tokio::test]
async fn test_parameter_validation_limits() -> Result<(), Box<dyn std::error::Error>> {
let mut db_manager = TestDatabaseManager::new()?;
let pool = db_manager.setup_test_database().await?;
let config = Config {
database_url: db_manager.get_test_database_url(),
mcp_port: 3333,
log_level: "info".to_string(),
};
let storage = Arc::new(Storage::new(pool));
let server = MCPServer::new(config, storage);
let large_content = "a".repeat(1024 * 1024 + 1); let large_content_request = json!({
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "store_memory",
"arguments": {
"content": large_content,
"context": "test",
"summary": "test",
"tags": ["test"]
}
},
"id": 1
});
let response = server.handle_request(&large_content_request.to_string()).await;
let response_json: Value = serde_json::from_str(&response)?;
assert_eq!(response_json["error"]["code"], -32602);
assert!(response_json["error"]["message"]
.as_str()
.unwrap()
.contains("Content size"));
assert!(response_json["error"]["message"]
.as_str()
.unwrap()
.contains("1MB"));
let long_context = "x".repeat(1001);
let long_context_request = json!({
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "store_memory",
"arguments": {
"content": "test content",
"context": long_context,
"summary": "test",
"tags": ["test"]
}
},
"id": 2
});
let response = server.handle_request(&long_context_request.to_string()).await;
let response_json: Value = serde_json::from_str(&response)?;
assert_eq!(response_json["error"]["code"], -32602);
assert!(response_json["error"]["message"]
.as_str()
.unwrap()
.contains("Context length"));
assert!(response_json["error"]["message"]
.as_str()
.unwrap()
.contains("1000 characters"));
let long_summary = "y".repeat(501);
let long_summary_request = json!({
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "store_memory",
"arguments": {
"content": "test content",
"context": "test context",
"summary": long_summary,
"tags": ["test"]
}
},
"id": 3
});
let response = server.handle_request(&long_summary_request.to_string()).await;
let response_json: Value = serde_json::from_str(&response)?;
assert_eq!(response_json["error"]["code"], -32602);
assert!(response_json["error"]["message"]
.as_str()
.unwrap()
.contains("Summary length"));
assert!(response_json["error"]["message"]
.as_str()
.unwrap()
.contains("500 characters"));
let many_tags: Vec<String> = (0..51).map(|i| format!("tag{}", i)).collect();
let many_tags_request = json!({
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "store_memory",
"arguments": {
"content": "test content",
"context": "test context",
"summary": "test summary",
"tags": many_tags
}
},
"id": 4
});
let response = server.handle_request(&many_tags_request.to_string()).await;
let response_json: Value = serde_json::from_str(&response)?;
assert_eq!(response_json["error"]["code"], -32602);
assert!(response_json["error"]["message"]
.as_str()
.unwrap()
.contains("Tags count"));
assert!(response_json["error"]["message"]
.as_str()
.unwrap()
.contains("50 tags"));
db_manager.cleanup().await?;
Ok(())
}
#[tokio::test]
async fn test_request_timeout_handling() -> Result<(), Box<dyn std::error::Error>> {
let mut db_manager = TestDatabaseManager::new()?;
let pool = db_manager.setup_test_database().await?;
let _config = Config {
database_url: db_manager.get_test_database_url(),
mcp_port: 3333,
log_level: "info".to_string(),
};
let storage = Arc::new(Storage::new(pool));
let handlers = Arc::new(MCPHandlers::new(storage));
let search_params = json!({
"query": "test query that might be slow",
"max_results": 1000,
"similarity_threshold": 0.1
});
let start = std::time::Instant::now();
let result = timeout(
Duration::from_secs(61), handlers.handle_tool_call("search_memory", search_params)
).await;
let elapsed = start.elapsed();
match result {
Ok(Ok(_)) => {
assert!(elapsed.as_secs() <= 60, "Operation should complete within timeout");
}
Ok(Err(Error::Timeout(msg))) => {
assert!(msg.contains("timed out after 60 seconds"));
}
Err(_) => {
panic!("Operation should not take longer than test timeout of 61s");
}
Ok(Err(other)) => {
println!("Other error during timeout test: {}", other);
}
}
db_manager.cleanup().await?;
Ok(())
}
#[tokio::test]
async fn test_valid_parameter_acceptance() -> Result<(), Box<dyn std::error::Error>> {
let mut db_manager = TestDatabaseManager::new()?;
let pool = db_manager.setup_test_database().await?;
let config = Config {
database_url: db_manager.get_test_database_url(),
mcp_port: 3333,
log_level: "info".to_string(),
};
let storage = Arc::new(Storage::new(pool));
let server = MCPServer::new(config, storage);
let valid_request = json!({
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "store_memory",
"arguments": {
"content": "This is valid content within limits",
"context": "Valid context under 1000 chars",
"summary": "Valid summary under 500 chars",
"tags": ["tag1", "tag2", "tag3"]
}
},
"id": 1
});
let response = server.handle_request(&valid_request.to_string()).await;
let response_json: Value = serde_json::from_str(&response)?;
assert!(response_json.get("result").is_some());
assert!(response_json.get("error").is_none());
db_manager.cleanup().await?;
Ok(())
}
#[tokio::test]
async fn test_error_code_consistency() -> Result<(), Box<dyn std::error::Error>> {
let parse_error = Error::ParseError("test".to_string());
assert_eq!(parse_error.json_rpc_code(), -32700);
let invalid_request = Error::InvalidRequest("test".to_string());
assert_eq!(invalid_request.json_rpc_code(), -32600);
let method_not_found = Error::MethodNotFound("test".to_string());
assert_eq!(method_not_found.json_rpc_code(), -32601);
let invalid_params = Error::InvalidParams("test".to_string());
assert_eq!(invalid_params.json_rpc_code(), -32602);
let internal_error = Error::InternalError("test".to_string());
assert_eq!(internal_error.json_rpc_code(), -32603);
let timeout_error = Error::Timeout("test".to_string());
assert_eq!(timeout_error.json_rpc_code(), -32603);
let error_response = parse_error.to_json_rpc_error(Some(json!(123)));
assert_eq!(error_response["jsonrpc"], "2.0");
assert_eq!(error_response["id"], 123);
assert_eq!(error_response["error"]["code"], -32700);
assert!(error_response["error"]["message"].as_str().unwrap().contains("test"));
Ok(())
}