50e6ce5ed0
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
82 lines
4.6 KiB
Rust
82 lines
4.6 KiB
Rust
//! 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));
|
|
}
|