diff --git a/linux-nvim/tests/integration_test.rs b/linux-nvim/tests/integration_test.rs index 2619403..187dc7f 100644 --- a/linux-nvim/tests/integration_test.rs +++ b/linux-nvim/tests/integration_test.rs @@ -1,5 +1,6 @@ -use serde_json::Value; +use serde_json::{json, Value}; use std::io::{BufRead, BufReader, Read, Write}; +use std::process::{Command, Stdio}; fn send_message(stdin: &mut std::process::ChildStdin, msg: Value) { let s = serde_json::to_string(&msg).unwrap(); diff --git a/server/src/handlers.rs b/server/src/handlers.rs index 78d5aec..f188298 100644 --- a/server/src/handlers.rs +++ b/server/src/handlers.rs @@ -2690,4 +2690,334 @@ mod tests { let tasks = handler.state.tasks.read(); assert_eq!(tasks[0].status, "in_progress"); } + + #[tokio::test] + async fn test_handle_pin_file() { + let handler = setup_test_handler("pin_file"); + let req = json!({ + "jsonrpc": "2.0", + "id": 100, + "method": "tools/call", + "params": { + "name": "pin_file", + "arguments": { + "file_path": "/path/to/pinned.rs", + "namespace": "global" + } + } + }); + let _ = handler.handle_request(req).await.unwrap(); + let pinned = handler.state.pinned_files.read(); + assert!(pinned.iter().any(|p| p.file_path == "/path/to/pinned.rs")); + } + + #[tokio::test] + async fn test_handle_unpin_file() { + let handler = setup_test_handler("unpin_file"); + handler.state.pinned_files.modify(|files| { + files.push(crate::models::PinnedFile { + namespace: "global".to_string(), + file_path: "/path/to/unpin.rs".to_string(), + timestamp: 0, + git_branch: None, + }); + }); + let req = json!({ + "jsonrpc": "2.0", + "id": 101, + "method": "tools/call", + "params": { + "name": "unpin_file", + "arguments": { + "file_path": "/path/to/unpin.rs", + "namespace": "global" + } + } + }); + let _ = handler.handle_request(req).await.unwrap(); + let pinned = handler.state.pinned_files.read(); + assert!(!pinned.iter().any(|p| p.file_path == "/path/to/unpin.rs")); + } + + #[tokio::test] + async fn test_handle_learn_preference() { + let handler = setup_test_handler("learn_preference"); + let req = json!({ + "jsonrpc": "2.0", + "id": 102, + "method": "tools/call", + "params": { + "name": "learn_preference", + "arguments": { + "key": "formatting", + "value": "use spaces" + } + } + }); + let _ = handler.handle_request(req).await.unwrap(); + let prefs = handler.state.prefs.read(); + assert!(prefs.values().any(|p| p.key == "formatting" && p.value == "use spaces")); + } + + #[tokio::test] + async fn test_handle_read_preferences() { + let handler = setup_test_handler("read_preferences"); + handler.state.prefs.modify(|prefs| { + prefs.insert("theme".to_string(), crate::models::Preference { + key: "theme".to_string(), + value: "dark".to_string(), + updated_at: 0, + }); + }); + let req = json!({ + "jsonrpc": "2.0", + "id": 103, + "method": "tools/call", + "params": { + "name": "read_preferences", + "arguments": {} + } + }); + let response = handler.handle_request(req).await.unwrap(); + let content = response["result"]["content"][0]["text"].as_str().unwrap(); + assert!(content.contains("theme")); + assert!(content.contains("dark")); + } + + #[tokio::test] + async fn test_handle_delete_task() { + let handler = setup_test_handler("delete_task"); + handler.state.tasks.modify(|tasks| { + tasks.push(crate::models::Task { + id: "task-to-delete".to_string(), + title: "".to_string(), + status: "".to_string(), + description: "".to_string(), + created_at: 0, + updated_at: 0, + git_branch: None, + acceptance_criteria: vec![], + dependencies: vec![], + parent_id: None, + }); + }); + let req = json!({ + "jsonrpc": "2.0", + "id": 104, + "method": "tools/call", + "params": { + "name": "delete_task", + "arguments": { + "id": "task-to-delete" + } + } + }); + let _ = handler.handle_request(req).await.unwrap(); + let tasks = handler.state.tasks.read(); + assert!(tasks.is_empty()); + } + + #[tokio::test] + async fn test_handle_add_milestone() { + let handler = setup_test_handler("add_milestone"); + let req = json!({ + "jsonrpc": "2.0", + "id": 105, + "method": "tools/call", + "params": { + "name": "add_milestone", + "arguments": { + "title": "v1.0", + "namespace": "global" + } + } + }); + let _ = handler.handle_request(req).await.unwrap(); + let milestones = handler.state.milestones.read(); + assert_eq!(milestones.len(), 1); + assert_eq!(milestones[0].title, "v1.0"); + } + + #[tokio::test] + async fn test_handle_update_milestone() { + let handler = setup_test_handler("update_milestone"); + handler.state.milestones.modify(|ms| { + ms.push(crate::models::Milestone { + id: "v1.0".to_string(), + title: "v1.0".to_string(), + status: "pending".to_string(), + namespace: "".to_string(), + target_date: None, + }); + }); + let req = json!({ + "jsonrpc": "2.0", + "id": 106, + "method": "tools/call", + "params": { + "name": "update_milestone", + "arguments": { + "id": "v1.0", + "status": "completed" + } + } + }); + let _ = handler.handle_request(req).await.unwrap(); + let ms = handler.state.milestones.read(); + assert_eq!(ms[0].status, "completed"); + } + + #[tokio::test] + async fn test_handle_list_milestones() { + let handler = setup_test_handler("list_milestones"); + handler.state.milestones.modify(|ms| { + ms.push(crate::models::Milestone { + id: "v2.0".to_string(), + title: "v2.0".to_string(), + status: "pending".to_string(), + namespace: "".to_string(), + target_date: None, + }); + }); + let req = json!({ + "jsonrpc": "2.0", + "id": 107, + "method": "tools/call", + "params": { + "name": "list_milestones", + "arguments": {} + } + }); + let response = handler.handle_request(req).await.unwrap(); + let content = response["result"]["content"][0]["text"].as_str().unwrap(); + assert!(content.contains("v2.0")); + } + + #[tokio::test] + async fn test_handle_add_pr_checklist_item() { + let handler = setup_test_handler("add_pr_checklist_item"); + let req = json!({ + "jsonrpc": "2.0", + "id": 108, + "method": "tools/call", + "params": { + "name": "add_pr_checklist_item", + "arguments": { + "description": "Check tests", + "namespace": "global" + } + } + }); + let _ = handler.handle_request(req).await.unwrap(); + let cl = handler.state.pr_checklists.read(); + assert_eq!(cl.len(), 1); + assert_eq!(cl[0].description, "Check tests"); + } + + #[tokio::test] + async fn test_handle_clear_pr_checklist() { + let handler = setup_test_handler("clear_pr_checklist"); + handler.state.pr_checklists.modify(|cl| { + cl.push(crate::models::PrChecklistItem { + namespace: "global".to_string(), + id: "item".to_string(), + description: "".to_string(), + }); + }); + let req = json!({ + "jsonrpc": "2.0", + "id": 109, + "method": "tools/call", + "params": { + "name": "clear_pr_checklist", + "arguments": { + "namespace": "global" + } + } + }); + let _ = handler.handle_request(req).await.unwrap(); + let cl = handler.state.pr_checklists.read(); + assert!(cl.is_empty()); + } + + #[tokio::test] + async fn test_handle_clear_handoff_memos() { + let handler = setup_test_handler("clear_handoff_memos"); + handler.state.handoff_memos.modify(|ms| { + ms.push(crate::models::HandoffMemo { + id: "memo".to_string(), + namespace: "".to_string(), + content: "".to_string(), + author: "".to_string(), + timestamp: 0, + }); + }); + let req = json!({ + "jsonrpc": "2.0", + "id": 110, + "method": "tools/call", + "params": { + "name": "clear_handoff_memos", + "arguments": { + "ids": ["memo"] + } + } + }); + let _ = handler.handle_request(req).await.unwrap(); + let ms = handler.state.handoff_memos.read(); + assert!(ms.is_empty()); + } + + #[tokio::test] + async fn test_handle_clear_sticky_notes() { + let handler = setup_test_handler("clear_sticky_notes"); + handler.state.sticky.modify(|ns| { + ns.push(crate::models::StickyNote { + content: "".to_string(), + timestamp: 0, + }); + }); + let req = json!({ + "jsonrpc": "2.0", + "id": 111, + "method": "tools/call", + "params": { + "name": "clear_sticky_notes", + "arguments": {} + } + }); + let _ = handler.handle_request(req).await.unwrap(); + let ns = handler.state.sticky.read(); + assert!(ns.is_empty()); + } + + #[tokio::test] + async fn test_handle_delete_sticky_note() { + let handler = setup_test_handler("delete_sticky_note"); + handler.state.sticky.modify(|ns| { + ns.push(crate::models::StickyNote { + content: "note1".to_string(), + timestamp: 0, + }); + ns.push(crate::models::StickyNote { + content: "note2".to_string(), + timestamp: 0, + }); + }); + let req = json!({ + "jsonrpc": "2.0", + "id": 112, + "method": "tools/call", + "params": { + "name": "delete_sticky_note", + "arguments": { + "index": 1 + } + } + }); + let _ = handler.handle_request(req).await.unwrap(); + let ns = handler.state.sticky.read(); + assert_eq!(ns.len(), 1); + assert_eq!(ns[0].content, "note2"); + } } diff --git a/server/src/store.rs b/server/src/store.rs index a44ff3b..7ac0cb1 100644 --- a/server/src/store.rs +++ b/server/src/store.rs @@ -45,6 +45,14 @@ impl Store(&self, f: F) -> R + where + F: FnOnce(&T) -> R, + { + let lock = self.cache.read().unwrap(); + f(&lock) + } + pub fn modify(&self, f: F) { let mut lock = self.cache.write().unwrap(); f(&mut lock);