refactor: consolidate nvim crates, extract server library, and update workspace dependencies
This commit is contained in:
1 parent
a083719cf1
commit
533adfd41b
53 files changed
+5967
-1230
No files matched your search
+252
-8
@@ -120,8 +120,20 @@ pub fn create_router(app_state: Arc<AppState>) -> Router {
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
"Received shutdown request via /shutdown endpoint. Initiating graceful shutdown."
|
||||
"Received shutdown request via /shutdown endpoint. Creating session checkpoint and initiating graceful shutdown."
|
||||
);
|
||||
let mem_state = state.handler.state.clone();
|
||||
let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_secs();
|
||||
let memo = crate::models::HandoffMemo {
|
||||
id: format!("chk_shutdown_{}", now),
|
||||
author: "ShutdownHook".to_string(),
|
||||
content: format!("Automatic session checkpoint triggered on graceful server shutdown at epoch {}", now),
|
||||
expires_at: None,
|
||||
namespace: "global".to_string(),
|
||||
timestamp: now,
|
||||
};
|
||||
mem_state.telemetry.handoff_memos.modify(|m| m.push(memo));
|
||||
|
||||
if let Some(tx) = state.shutdown_tx.lock().unwrap().take() {
|
||||
let _ = tx.send(());
|
||||
}
|
||||
@@ -389,15 +401,14 @@ mod tests {
|
||||
use crate::router::MemoryHandler;
|
||||
use crate::state::MemoryState;
|
||||
use axum::body::Body;
|
||||
use axum::http::Request;
|
||||
use axum::http::{Request, StatusCode};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::RwLock;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use tempfile::tempdir;
|
||||
use tower::ServiceExt;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_router_health() {
|
||||
async fn setup_app() -> (axum::Router, Arc<AppState>, tempfile::TempDir) {
|
||||
let dir = tempdir().unwrap();
|
||||
let state = Arc::new(MemoryState::new(dir.path().to_str().unwrap()));
|
||||
let (shutdown_tx, _) = tokio::sync::oneshot::channel();
|
||||
@@ -408,16 +419,249 @@ mod tests {
|
||||
shutdown_tx: std::sync::Mutex::new(Some(shutdown_tx)),
|
||||
});
|
||||
|
||||
let app = create_router(app_state);
|
||||
let app = create_router(app_state.clone());
|
||||
(app, app_state, dir)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_router_health() {
|
||||
let (app, _, _dir) = setup_app().await;
|
||||
|
||||
// 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);
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_ping_endpoint() {
|
||||
let (app, _, _dir) = setup_app().await;
|
||||
|
||||
let request = Request::builder()
|
||||
.uri("/ping")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let response = app.oneshot(request).await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_version_endpoint() {
|
||||
let (app, _, _dir) = setup_app().await;
|
||||
|
||||
let request = Request::builder()
|
||||
.uri("/api/version")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let response = app.oneshot(request).await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_metrics_endpoint() {
|
||||
let (app, _, _dir) = setup_app().await;
|
||||
|
||||
let request = Request::builder()
|
||||
.uri("/metrics")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let response = app.oneshot(request).await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_activity_endpoint() {
|
||||
let (app, _, _dir) = setup_app().await;
|
||||
|
||||
let request = Request::builder()
|
||||
.uri("/api/activity")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let response = app.oneshot(request).await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_nvim_telemetry_endpoint() {
|
||||
let (app, _, _dir) = setup_app().await;
|
||||
|
||||
let body_json = serde_json::json!({
|
||||
"session_id": "test_session",
|
||||
"event": "BufEnter",
|
||||
"file": "test.rs"
|
||||
});
|
||||
|
||||
let request = Request::builder()
|
||||
.method("POST")
|
||||
.uri("/nvim/telemetry")
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(body_json.to_string()))
|
||||
.unwrap();
|
||||
|
||||
let response = app.oneshot(request).await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_terminal_history_endpoint() {
|
||||
let (app, _, _dir) = setup_app().await;
|
||||
|
||||
let request = Request::builder()
|
||||
.uri("/api/terminal/history")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let response = app.oneshot(request).await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_terminal_telemetry_endpoint() {
|
||||
let (app, _, _dir) = setup_app().await;
|
||||
|
||||
let body_json = serde_json::json!({
|
||||
"command": "cargo build",
|
||||
"exit_code": 0,
|
||||
"cwd": "/tmp",
|
||||
"os": "windows",
|
||||
"timestamp": 1234567890
|
||||
});
|
||||
|
||||
let request = Request::builder()
|
||||
.method("POST")
|
||||
.uri("/terminal/telemetry")
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(body_json.to_string()))
|
||||
.unwrap();
|
||||
|
||||
let response = app.oneshot(request).await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_post_event_endpoint() {
|
||||
let (app, _, _dir) = setup_app().await;
|
||||
|
||||
let body_json = serde_json::json!({
|
||||
"topic": "test_topic",
|
||||
"session_id": "s123",
|
||||
"payload": { "key": "value" }
|
||||
});
|
||||
|
||||
let request = Request::builder()
|
||||
.method("POST")
|
||||
.uri("/events")
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(body_json.to_string()))
|
||||
.unwrap();
|
||||
|
||||
let response = app.oneshot(request).await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_gate_verify_endpoint() {
|
||||
let (app, _, _dir) = setup_app().await;
|
||||
|
||||
let request = Request::builder()
|
||||
.uri("/gate/verify?action=deploy&target=prod")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let response = app.oneshot(request).await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_gate_set_and_verify_success() {
|
||||
let (app, app_state, _dir) = setup_app().await;
|
||||
|
||||
let set_req = Request::builder()
|
||||
.method("POST")
|
||||
.uri("/gate/set")
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(serde_json::json!({
|
||||
"action": "deploy",
|
||||
"target": "prod",
|
||||
"authorize": true,
|
||||
"reason": "Tests passed"
|
||||
}).to_string()))
|
||||
.unwrap();
|
||||
|
||||
let response = app.oneshot(set_req).await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let app2 = create_router(app_state);
|
||||
let verify_req = Request::builder()
|
||||
.uri("/gate/verify?action=deploy&target=prod")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let verify_resp = app2.oneshot(verify_req).await.unwrap();
|
||||
assert_eq!(verify_resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_all_rest_endpoints_comprehensive() {
|
||||
let (_app, app_state, _dir) = setup_app().await;
|
||||
|
||||
let endpoints = vec![
|
||||
"/",
|
||||
"/api/graph",
|
||||
"/api/tasks",
|
||||
"/api/sticky",
|
||||
"/api/search?q=test",
|
||||
"/api/tech_debts",
|
||||
"/api/adrs",
|
||||
"/api/context_workspaces",
|
||||
"/api/handoff_memos",
|
||||
"/api/milestones",
|
||||
"/api/snippets",
|
||||
"/api/pr_checklists",
|
||||
"/api/error_fixes",
|
||||
"/api/stats",
|
||||
];
|
||||
|
||||
for ep in endpoints {
|
||||
let app_inst = create_router(app_state.clone());
|
||||
let req = Request::builder()
|
||||
.uri(ep)
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
let resp = app_inst.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK, "Failed endpoint: {}", ep);
|
||||
}
|
||||
|
||||
// Test task completion endpoint
|
||||
let app_task = create_router(app_state.clone());
|
||||
let complete_req = Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/tasks/task_123/complete")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
let complete_resp = app_task.oneshot(complete_req).await.unwrap();
|
||||
assert_eq!(complete_resp.status(), StatusCode::OK);
|
||||
|
||||
// Test /shutdown unauthorized without token
|
||||
let app_shut = create_router(app_state.clone());
|
||||
let shut_req = Request::builder()
|
||||
.method("POST")
|
||||
.uri("/shutdown")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
let shut_resp = app_shut.oneshot(shut_req).await.unwrap();
|
||||
assert_eq!(shut_resp.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
// Force recompile
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -104,14 +104,7 @@ pub async fn terminal_telemetry_handler(
|
||||
state
|
||||
.handler
|
||||
.state
|
||||
.telemetry
|
||||
.terminal_history
|
||||
.modify(|history| {
|
||||
history.push_front(payload.clone());
|
||||
if history.len() > 100 {
|
||||
history.pop_back();
|
||||
}
|
||||
});
|
||||
.record_terminal_history(payload.clone());
|
||||
|
||||
let ws_msg = serde_json::json!({
|
||||
"type": "terminal_telemetry",
|
||||
|
||||
Reference in new issue
Block a user