use std::sync::Arc;
use parking_lot::Mutex;
use serde_json::json;
use engram::embedding::{create_embedder, EmbeddingCache};
use engram::mcp::{get_tool_definitions, handlers};
use engram::search::{AdaptiveCacheConfig, FuzzyEngine, SearchConfig, SearchResultCache};
use engram::storage::Storage;
use engram::types::EmbeddingConfig;
fn test_ctx() -> handlers::HandlerContext {
let storage = Storage::open_in_memory().expect("in-memory storage");
let embedder = create_embedder(&EmbeddingConfig::default()).expect("tfidf embedder");
handlers::HandlerContext {
storage,
embedder,
fuzzy_engine: Arc::new(Mutex::new(FuzzyEngine::new())),
search_config: SearchConfig::default(),
realtime: None,
embedding_cache: Arc::new(EmbeddingCache::default()),
search_cache: Arc::new(SearchResultCache::new(AdaptiveCacheConfig::default())),
#[cfg(feature = "meilisearch")]
meili: None,
#[cfg(feature = "meilisearch")]
meili_indexer: None,
#[cfg(feature = "meilisearch")]
meili_sync_interval: 60,
#[cfg(feature = "langfuse")]
langfuse_runtime: Arc::new(tokio::runtime::Runtime::new().expect("langfuse runtime")),
}
}
#[test]
fn test_agent_register_and_get_via_dispatch() {
let ctx = test_ctx();
let result = handlers::dispatch(
&ctx,
"agent_register",
json!({
"agent_id": "test-agent-1",
"display_name": "Test Agent",
"capabilities": ["search", "create"],
"namespaces": ["production"]
}),
);
assert_eq!(result["agent_id"], "test-agent-1");
assert_eq!(result["display_name"], "Test Agent");
assert_eq!(result["status"], "active");
let get_result = handlers::dispatch(&ctx, "agent_get", json!({"agent_id": "test-agent-1"}));
assert_eq!(get_result["display_name"], "Test Agent");
assert!(get_result.get("error").is_none());
}
#[test]
fn test_agent_register_upsert() {
let ctx = test_ctx();
handlers::dispatch(
&ctx,
"agent_register",
json!({"agent_id": "upsert-agent", "display_name": "V1", "capabilities": ["a"]}),
);
let result = handlers::dispatch(
&ctx,
"agent_register",
json!({"agent_id": "upsert-agent", "display_name": "V2", "capabilities": ["a", "b"]}),
);
assert_eq!(result["display_name"], "V2");
let caps = result["capabilities"].as_array().unwrap();
assert_eq!(caps.len(), 2);
}
#[test]
fn test_namespace_isolation() {
let ctx = test_ctx();
handlers::dispatch(
&ctx,
"agent_register",
json!({"agent_id": "prod-agent", "namespaces": ["production"]}),
);
handlers::dispatch(
&ctx,
"agent_register",
json!({"agent_id": "staging-agent", "namespaces": ["staging"]}),
);
handlers::dispatch(
&ctx,
"agent_register",
json!({"agent_id": "multi-agent", "namespaces": ["production", "staging"]}),
);
let prod = handlers::dispatch(&ctx, "agent_list", json!({"namespace": "production"}));
assert_eq!(
prod["count"], 2,
"production should have prod-agent and multi-agent"
);
let staging = handlers::dispatch(&ctx, "agent_list", json!({"namespace": "staging"}));
assert_eq!(
staging["count"], 2,
"staging should have staging-agent and multi-agent"
);
}
#[test]
fn test_namespace_isolation_empty() {
let ctx = test_ctx();
handlers::dispatch(
&ctx,
"agent_register",
json!({"agent_id": "isolated", "namespaces": ["private"]}),
);
let result = handlers::dispatch(&ctx, "agent_list", json!({"namespace": "nonexistent"}));
assert_eq!(result["count"], 0);
}
#[test]
fn test_namespace_with_status_filter() {
let ctx = test_ctx();
handlers::dispatch(
&ctx,
"agent_register",
json!({"agent_id": "active-prod", "namespaces": ["prod"]}),
);
handlers::dispatch(
&ctx,
"agent_register",
json!({"agent_id": "inactive-prod", "namespaces": ["prod"]}),
);
handlers::dispatch(
&ctx,
"agent_deregister",
json!({"agent_id": "inactive-prod"}),
);
let result = handlers::dispatch(
&ctx,
"agent_list",
json!({"status": "inactive", "namespace": "prod"}),
);
assert_eq!(result["count"], 1, "should find 1 inactive agent in prod");
assert_eq!(result["agents"][0]["agent_id"], "inactive-prod");
let active = handlers::dispatch(
&ctx,
"agent_list",
json!({"status": "active", "namespace": "prod"}),
);
assert_eq!(active["count"], 1, "should find 1 active agent in prod");
}
#[test]
fn test_agent_heartbeat_and_deregister() {
let ctx = test_ctx();
handlers::dispatch(
&ctx,
"agent_register",
json!({"agent_id": "lifecycle-agent"}),
);
let hb = handlers::dispatch(
&ctx,
"agent_heartbeat",
json!({"agent_id": "lifecycle-agent"}),
);
assert!(hb.get("last_heartbeat").is_some());
assert_eq!(hb["status"], "active");
let dereg = handlers::dispatch(
&ctx,
"agent_deregister",
json!({"agent_id": "lifecycle-agent"}),
);
assert_eq!(dereg["success"], true);
let get = handlers::dispatch(&ctx, "agent_get", json!({"agent_id": "lifecycle-agent"}));
assert_eq!(get["status"], "inactive");
}
#[test]
fn test_agent_capabilities_update() {
let ctx = test_ctx();
handlers::dispatch(
&ctx,
"agent_register",
json!({"agent_id": "cap-agent", "capabilities": ["search"]}),
);
let result = handlers::dispatch(
&ctx,
"agent_capabilities",
json!({"agent_id": "cap-agent", "capabilities": ["search", "create", "analyze"]}),
);
let caps = result["capabilities"].as_array().unwrap();
assert_eq!(caps.len(), 3);
}
#[test]
fn test_agent_tools_in_definitions() {
let tools = get_tool_definitions();
let agent_tool_names = [
"agent_register",
"agent_deregister",
"agent_heartbeat",
"agent_list",
"agent_get",
"agent_capabilities",
];
for name in &agent_tool_names {
let found = tools.iter().any(|t| t.name == *name);
assert!(found, "tool '{}' should be in TOOL_DEFINITIONS", name);
}
}
#[test]
fn test_sse_event_type_serialization() {
use engram::realtime::RealtimeEvent;
let event = RealtimeEvent::memory_created(42, "test content".to_string());
let serialized = serde_json::to_string(&event).expect("event should serialize");
assert!(serialized.contains("memory_created"));
assert!(serialized.contains("42"));
assert!(serialized.contains("test content"));
}
#[test]
fn test_mcp_dispatch_unknown_tool_returns_error() {
let ctx = test_ctx();
let result = handlers::dispatch(&ctx, "nonexistent_tool", json!({}));
assert!(result.get("error").is_some());
assert!(result["error"].as_str().unwrap().contains("Unknown tool"));
}