feat(mcp): implement Prompts capability, Resources, and clean up daemon architecture

This commit is contained in:
Riz Ashraf committed 2026-09-22 21:41:17 +01:00
1 parent 9b9b403ef2
commit 251757f8fc
29 files changed
+1276 -182

No files matched your search

+56
View File
@@ -107,3 +107,59 @@ pub async fn gate_set_handler(
pub async fn health_handler() -> &'static str {
"OK"
}
#[cfg(test)]
mod tests {
use super::*;
use crate::router::MemoryHandler;
use crate::state::MemoryState;
use std::sync::atomic::AtomicUsize;
use std::sync::RwLock;
use tempfile::tempdir;
#[tokio::test]
async fn test_gate_handlers() {
let dir = tempdir().unwrap();
let state = Arc::new(MemoryState::new(dir.path().to_str().unwrap()));
let app_state = Arc::new(AppState {
handler: Arc::new(MemoryHandler::new(state.clone())),
clients: RwLock::new(HashMap::new()),
next_id: AtomicUsize::new(1),
});
// Set a gate to authorized
let set_req = GateSetReq {
action: "push".to_string(),
target: "main".to_string(),
namespace: Some("global".to_string()),
params: HashMap::new(),
authorize: Some(true),
block: None,
reason: None,
};
let res_set = gate_set_handler(State(app_state.clone()), Json(set_req)).await.unwrap();
assert_eq!(res_set.into_response().status(), axum::http::StatusCode::OK);
// Verify the gate (and consume it)
let verify_req = GateVerifyReq {
action: "push".to_string(),
target: "main".to_string(),
namespace: Some("global".to_string()),
params: HashMap::new(),
consume: true,
};
let res_verify = gate_verify_handler(State(app_state.clone()), Query(verify_req)).await.unwrap();
assert_eq!(res_verify.into_response().status(), axum::http::StatusCode::OK);
// Verify again should fail since it was consumed
let verify_req2 = GateVerifyReq {
action: "push".to_string(),
target: "main".to_string(),
namespace: Some("global".to_string()),
params: HashMap::new(),
consume: false,
};
let res_verify2 = gate_verify_handler(State(app_state.clone()), Query(verify_req2)).await;
assert!(res_verify2.is_err());
}
}
+37
View File
@@ -197,3 +197,40 @@ pub fn create_router(app_state: Arc<AppState>) -> Router {
)
.with_state(app_state)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::router::MemoryHandler;
use crate::state::MemoryState;
use crate::AppState;
use axum::http::Request;
use axum::body::Body;
use std::sync::atomic::AtomicUsize;
use std::sync::RwLock;
use std::collections::HashMap;
use tower::ServiceExt;
use tempfile::tempdir;
#[tokio::test]
async fn test_create_router_health() {
let dir = tempdir().unwrap();
let state = Arc::new(MemoryState::new(dir.path().to_str().unwrap()));
let app_state = Arc::new(AppState {
handler: Arc::new(MemoryHandler::new(state.clone())),
clients: RwLock::new(HashMap::new()),
next_id: AtomicUsize::new(1),
});
let app = create_router(app_state);
// Test health endpoint
let request = Request::builder()
.uri("/health")
.body(Body::empty())
.unwrap();
let response = app.oneshot(request).await.unwrap();
assert_eq!(response.status(), 200);
}
}
+38
View File
@@ -142,3 +142,41 @@ pub async fn handle_socket(socket: WebSocket, state: Arc<AppState>, _client_type
},
};
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::AtomicUsize;
use std::sync::RwLock;
use crate::router::MemoryHandler;
use crate::state::MemoryState;
use tempfile::tempdir;
#[tokio::test]
async fn test_session_cleanup_drop() {
let dir = tempdir().unwrap();
let state = Arc::new(MemoryState::new(dir.path().to_str().unwrap()));
let app_state = Arc::new(AppState {
handler: Arc::new(MemoryHandler::new(state)),
clients: RwLock::new(HashMap::new()),
next_id: AtomicUsize::new(1),
});
// Insert a dummy client
app_state.clients.write().unwrap().insert("test-session".to_string(), tokio::sync::mpsc::channel(1).0);
let send_task = tokio::spawn(async {});
let recv_task = tokio::spawn(async {});
{
let _cleanup = SessionCleanup {
session_id: "test-session".to_string(),
state: app_state.clone(),
send_task,
recv_task,
};
} // Drop happens here
assert!(app_state.clients.read().unwrap().is_empty());
}
}