//! The handshake, the method table and the protocol errors. mod common; use common::{ask, writable_server}; use serde_json::{Value, json}; #[test] fn the_handshake_echoes_a_supported_protocol_version() { let (_vault, mut server) = writable_server("handshake"); let reply = ask(&mut server, &json!({"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-06-18"}}).to_string()); let result = reply.get("result").expect("a result"); assert_eq!(result.get("protocolVersion").and_then(Value::as_str), Some("2025-06-18")); assert_eq!(result.get("serverInfo").and_then(|i| i.get("name")).and_then(Value::as_str), Some("notescraft")); assert_eq!(result.get("serverInfo").and_then(|i| i.get("version")).and_then(Value::as_str), Some(env!("CARGO_PKG_VERSION"))); let capabilities = result.get("capabilities").expect("capabilities"); assert!(capabilities.get("tools").is_some(), "tools are advertised"); assert!(capabilities.get("resources").is_some(), "resources are advertised"); let older = ask(&mut server, &json!({"jsonrpc": "2.0", "id": 2, "method": "initialize", "params": {"protocolVersion": "2024-11-05"}}).to_string()); assert_eq!(older.get("result").and_then(|r| r.get("protocolVersion")).and_then(Value::as_str), Some("2024-11-05")); let unknown = ask(&mut server, &json!({"jsonrpc": "2.0", "id": 3, "method": "initialize", "params": {"protocolVersion": "1999-01-01"}}).to_string()); assert_eq!(unknown.get("result").and_then(|r| r.get("protocolVersion")).and_then(Value::as_str), Some(notescraft_mcp::PROTOCOL_VERSION)); } #[test] fn notifications_get_no_reply_and_ping_answers() { let (_vault, mut server) = writable_server("notify"); assert!(server.handle_line(" \n ").is_none(), "a blank line is not a message"); assert!(server.handle_line(&json!({"jsonrpc": "2.0", "method": "notifications/initialized"}).to_string()).is_none()); assert!(server.handle_line("").is_none(), "a blank line is not a message"); let pong = ask(&mut server, &json!({"jsonrpc": "2.0", "id": "ping-1", "method": "ping"}).to_string()); assert_eq!(pong.get("id").and_then(Value::as_str), Some("ping-1"), "a string id is echoed"); assert_eq!(pong.get("result"), Some(&json!({}))); } #[test] fn every_protocol_error_has_its_code() { let (_vault, mut server) = writable_server("errors"); let cases = [ ("not json at all", -32700), (r#"{"jsonrpc": "2.0", "id": 1, "method": "nope/there"}"#, -32601), (r#"{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {}}"#, -32602), (r#"{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": 7}}"#, -32602), (r#"{"jsonrpc": "2.0", "id": 1, "method": "resources/read", "params": {"uri": "http://x/y"}}"#, -32602), ("[1, 2]", -32600), (r#"{"jsonrpc": "2.0", "id": [1], "method": "ping"}"#, -32600), ]; for (line, code) in cases { let reply = ask(&mut server, line); assert_eq!(reply.get("error").and_then(|error| error.get("code")).and_then(Value::as_i64), Some(code), "for {line}"); assert!(reply.get("result").is_none(), "an error reply carries no result"); } } #[test] fn a_line_over_the_cap_is_refused_without_parsing() { let (_vault, mut server) = writable_server("huge"); let line = format!(r#"{{"id":1,"method":"ping","pad":"{}"}}"#, "x".repeat(notescraft_mcp::MAX_LINE_BYTES + 1)); let reply = ask(&mut server, &line); assert_eq!(reply.get("error").and_then(|error| error.get("code")).and_then(Value::as_i64), Some(-32600)); } #[test] fn a_batch_answers_in_order_and_stops_at_the_cap() { let (_vault, mut server) = writable_server("batch"); let line = json!([{"jsonrpc": "2.0", "id": 1, "method": "ping"}, {"jsonrpc": "2.0", "method": "notifications/initialized"}, {"jsonrpc": "2.0", "id": "two", "method": "ping"}]); let reply = ask(&mut server, &line.to_string()); let answers = reply.as_array().expect("a batch reply is an array"); assert_eq!(answers.len(), 2, "the notification produced no entry"); assert_eq!(answers.first().and_then(|r| r.get("id")).and_then(Value::as_i64), Some(1)); assert_eq!(answers.get(1).and_then(|r| r.get("id")).and_then(Value::as_str), Some("two")); let too_many: Vec<_> = (0..=notescraft_mcp::MAX_BATCH).map(|id| json!({"jsonrpc": "2.0", "id": id, "method": "ping"})).collect(); let reply = ask(&mut server, &json!(too_many).to_string()); assert_eq!(reply.get("error").and_then(|error| error.get("code")).and_then(Value::as_i64), Some(-32600)); let empty = ask(&mut server, "[]"); assert_eq!(empty.get("error").and_then(|error| error.get("code")).and_then(Value::as_i64), Some(-32600)); }