use std::sync::Arc;
use axum::body::Body;
use axum::http::{Request, StatusCode};
use http_body_util::BodyExt;
use tower::ServiceExt;
use mnemo_core::embedding::NoopEmbedding;
use mnemo_core::index::usearch::UsearchIndex;
use mnemo_core::query::MnemoEngine;
use mnemo_core::storage::duckdb::DuckDbStorage;
fn create_test_engine() -> (Arc<MnemoEngine>, tempfile::TempDir) {
let dir = tempfile::tempdir().unwrap();
let db = dir.path().join("admin-test.duckdb");
let storage = Arc::new(DuckDbStorage::open(&db).unwrap());
let index = Arc::new(UsearchIndex::new(128).unwrap());
let embedding = Arc::new(NoopEmbedding::new(128));
let engine = Arc::new(MnemoEngine::new(
storage,
index,
embedding,
"test-agent".to_string(),
None,
));
(engine, dir)
}
#[tokio::test]
#[serial_test::serial]
#[ignore = "duckdb migration race on ubuntu CI; passes locally"]
async fn test_admin_stats_endpoint() {
let (engine, _tmp) = create_test_engine();
engine
.remember(mnemo_core::query::remember::RememberRequest {
content: "Admin stats test memory".to_string(),
agent_id: None,
memory_type: None,
scope: None,
importance: Some(0.8),
tags: None,
metadata: None,
source_type: None,
source_id: None,
related_to: None,
org_id: None,
thread_id: None,
ttl_seconds: None,
decay_rate: None,
created_by: None,
})
.await
.unwrap();
let app = mnemo_admin::router(engine);
let response = app
.oneshot(
Request::builder()
.uri("/admin/api/stats")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let body = response.into_body().collect().await.unwrap().to_bytes();
let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
assert!(
json.get("memory_count").is_some(),
"response must contain memory_count"
);
assert!(
json.get("event_count").is_some(),
"response must contain event_count"
);
assert!(
json.get("agent_ids").is_some(),
"response must contain agent_ids"
);
assert!(json["memory_count"].as_u64().unwrap() >= 1);
assert!(json["event_count"].as_u64().is_some());
assert!(json["agent_ids"].is_array());
assert!(!json["agent_ids"].as_array().unwrap().is_empty());
}
#[tokio::test]
#[serial_test::serial]
#[ignore = "duckdb migration race on ubuntu CI; passes locally"]
async fn test_admin_agents_endpoint() {
let (engine, _tmp) = create_test_engine();
engine
.remember(mnemo_core::query::remember::RememberRequest {
content: "Memory from agent-alpha".to_string(),
agent_id: Some("agent-alpha".to_string()),
memory_type: None,
scope: None,
importance: Some(0.7),
tags: None,
metadata: None,
source_type: None,
source_id: None,
related_to: None,
org_id: None,
thread_id: None,
ttl_seconds: None,
decay_rate: None,
created_by: None,
})
.await
.unwrap();
engine
.remember(mnemo_core::query::remember::RememberRequest {
content: "Memory from agent-beta".to_string(),
agent_id: Some("agent-beta".to_string()),
memory_type: None,
scope: None,
importance: Some(0.6),
tags: None,
metadata: None,
source_type: None,
source_id: None,
related_to: None,
org_id: None,
thread_id: None,
ttl_seconds: None,
decay_rate: None,
created_by: None,
})
.await
.unwrap();
let app = mnemo_admin::router(engine);
let response = app
.oneshot(
Request::builder()
.uri("/admin/api/agents")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let body = response.into_body().collect().await.unwrap().to_bytes();
let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
assert!(json.is_array(), "agents endpoint must return a JSON array");
let agents = json.as_array().unwrap();
assert!(
agents.len() >= 2,
"expected at least 2 agents, got {}",
agents.len()
);
let agent_strings: Vec<String> = agents
.iter()
.map(|v| v.as_str().unwrap().to_string())
.collect();
assert!(
agent_strings.contains(&"agent-alpha".to_string()),
"expected agent-alpha in list"
);
assert!(
agent_strings.contains(&"agent-beta".to_string()),
"expected agent-beta in list"
);
}