1pub mod handlers;
9pub mod protocol;
10pub mod server;
11pub mod tools;
12
13pub use handlers::{handle_method, handle_notification};
14pub use server::run_server;
15
16#[cfg(test)]
17mod tests {
18 use super::handlers::{handle_method, handle_notification};
19 use super::protocol::{JsonRpcResponse, PROTOCOL_VERSION, SERVER_NAME};
20 use serde_json::json;
21
22 #[test]
23 fn test_handle_initialize() {
24 let result = handle_method("initialize", None).unwrap();
25 assert_eq!(result["protocolVersion"], PROTOCOL_VERSION);
26 assert_eq!(result["serverInfo"]["name"], SERVER_NAME);
27 }
28
29 #[test]
30 fn test_handle_tools_list() {
31 let result = handle_method("tools/list", None).unwrap();
32 let tools = result["tools"].as_array().unwrap();
33 assert_eq!(tools.len(), 24); assert_eq!(tools[0]["name"], "chant_spec_list");
36 assert_eq!(tools[1]["name"], "chant_spec_get");
37 assert_eq!(tools[2]["name"], "chant_ready");
38 assert_eq!(tools[3]["name"], "chant_status");
39 assert_eq!(tools[4]["name"], "chant_log");
40 assert_eq!(tools[5]["name"], "chant_search");
41 assert_eq!(tools[6]["name"], "chant_diagnose");
42 assert_eq!(tools[7]["name"], "chant_lint");
43 assert_eq!(tools[8]["name"], "chant_spec_update");
45 assert_eq!(tools[9]["name"], "chant_add");
46 assert_eq!(tools[10]["name"], "chant_finalize");
47 assert_eq!(tools[11]["name"], "chant_reset");
48 assert_eq!(tools[12]["name"], "chant_resume"); assert_eq!(tools[13]["name"], "chant_cancel");
50 assert_eq!(tools[14]["name"], "chant_archive");
51 assert_eq!(tools[15]["name"], "chant_verify");
52 assert_eq!(tools[16]["name"], "chant_work_start");
53 assert_eq!(tools[17]["name"], "chant_work_list");
54 assert_eq!(tools[18]["name"], "chant_pause");
55 assert_eq!(tools[19]["name"], "chant_takeover");
56 }
57
58 #[test]
59 fn test_json_rpc_response_success() {
60 let resp = JsonRpcResponse::success(json!(1), json!({"test": true}));
61 assert_eq!(resp.jsonrpc, "2.0");
62 assert!(resp.result.is_some());
63 assert!(resp.error.is_none());
64 }
65
66 #[test]
67 fn test_json_rpc_response_error() {
68 let resp = JsonRpcResponse::error(json!(1), -32600, "Invalid request");
69 assert_eq!(resp.jsonrpc, "2.0");
70 assert!(resp.result.is_none());
71 assert!(resp.error.is_some());
72 assert_eq!(resp.error.as_ref().unwrap().code, -32600);
73 }
74
75 #[test]
76 fn test_chant_status_schema_has_brief_and_activity() {
77 let result = handle_method("tools/list", None).unwrap();
78 let tools = result["tools"].as_array().unwrap();
79 let status_tool = tools.iter().find(|t| t["name"] == "chant_status").unwrap();
80
81 let props = &status_tool["inputSchema"]["properties"];
82 assert!(
83 props.get("brief").is_some(),
84 "chant_status should have 'brief' property"
85 );
86 assert!(
87 props.get("include_activity").is_some(),
88 "chant_status should have 'include_activity' property"
89 );
90
91 assert!(props["brief"]["description"]
93 .as_str()
94 .unwrap()
95 .contains("single-line"));
96 assert!(props["include_activity"]["description"]
97 .as_str()
98 .unwrap()
99 .contains("activity"));
100 }
101
102 #[test]
103 fn test_chant_ready_has_limit_param() {
104 let result = handle_method("tools/list", None).unwrap();
105 let tools = result["tools"].as_array().unwrap();
106 let ready_tool = tools.iter().find(|t| t["name"] == "chant_ready").unwrap();
107
108 let props = &ready_tool["inputSchema"]["properties"];
109 assert!(
110 props.get("limit").is_some(),
111 "chant_ready should have 'limit' property"
112 );
113 assert_eq!(props["limit"]["type"], "integer");
114 assert!(props["limit"]["description"]
115 .as_str()
116 .unwrap()
117 .contains("50"));
118 }
119
120 #[test]
121 fn test_chant_spec_list_has_limit_param() {
122 let result = handle_method("tools/list", None).unwrap();
123 let tools = result["tools"].as_array().unwrap();
124 let list_tool = tools
125 .iter()
126 .find(|t| t["name"] == "chant_spec_list")
127 .unwrap();
128
129 let props = &list_tool["inputSchema"]["properties"];
130 assert!(
131 props.get("limit").is_some(),
132 "chant_spec_list should have 'limit' property"
133 );
134 assert_eq!(props["limit"]["type"], "integer");
135 assert!(props["limit"]["description"]
136 .as_str()
137 .unwrap()
138 .contains("50"));
139 }
140
141 #[test]
142 fn test_handle_notification() {
143 handle_notification("notifications/initialized", None);
145 handle_notification("unknown_notification", None);
146 }
147}