mod common;
use common::Server;
use std::time::Duration;
const PARSE_ERROR: i64 = -32700;
const INVALID_REQUEST: i64 = -32600;
const METHOD_NOT_FOUND: i64 = -32601;
fn error_code(reply: &serde_json::Value, what: &str) -> i64 {
reply["error"]["code"]
.as_i64()
.unwrap_or_else(|| panic!("{what}: expected a JSON-RPC error object, got {reply}"))
}
fn assert_still_serving(server: &mut Server, after: &str) {
let tools = server
.request("tools/list", serde_json::json!({}))
.unwrap_or_else(|e| panic!("server stopped serving after {after}: {e}"));
let count = tools["result"]["tools"].as_array().map_or(0, Vec::len);
assert!(count > 0, "server answered after {after} but listed no tools: {tools}");
}
#[test]
fn a_line_that_is_not_json_is_a_parse_error_and_the_server_keeps_serving() {
let mut server = Server::start().expect("start server");
for junk in ["{not json", "]", "{\"unterminated\": ", "{\"a\": 1} trailing"] {
let reply = server.raw(junk).unwrap_or_else(|e| panic!("no reply to {junk:?}: {e}"));
assert_eq!(error_code(&reply, junk), PARSE_ERROR, "wrong code for {junk:?}: {reply}");
assert_eq!(reply["id"], serde_json::Value::Null, "unparseable input must answer with a null id");
assert_eq!(reply["jsonrpc"], "2.0", "the error must itself be well-formed JSON-RPC: {reply}");
}
assert_still_serving(&mut server, "four unparseable lines");
}
#[test]
fn valid_json_that_is_not_an_object_gets_an_error_rather_than_silence() {
let mut server = Server::start().expect("start server");
for not_an_object in ["42", "\"a bare string\"", "true", "null", r#"[{"jsonrpc": "2.0"}]"#] {
let reply = server
.raw(not_an_object)
.unwrap_or_else(|e| panic!("no reply to {not_an_object:?} — silence is the bug: {e}"));
assert_eq!(error_code(&reply, not_an_object), INVALID_REQUEST, "wrong code: {reply}");
assert_eq!(reply["id"], serde_json::Value::Null, "there is no id to echo: {reply}");
}
assert_still_serving(&mut server, "five non-object messages");
}
#[test]
fn a_json_object_that_is_not_a_request_is_refused_without_killing_the_server() {
let mut server = Server::start().expect("start server");
let reply = server.raw(r#"{"jsonrpc": "2.0", "id": 41, "params": {}}"#).expect("reply to id-only");
assert_eq!(error_code(&reply, "missing method"), INVALID_REQUEST, "{reply}");
let reply = server.raw(r#"{"jsonrpc": "2.0", "id": 42, "method": 7}"#).expect("reply to numeric method");
assert_eq!(error_code(&reply, "non-string method"), INVALID_REQUEST, "{reply}");
let reply = server
.raw(r#"{"jsonrpc": "2.0", "id": {"weird": true}, "method": "tools/list"}"#)
.expect("reply to object id");
assert_eq!(reply["id"], serde_json::json!({"weird": true}), "an id must come back exactly: {reply}");
assert!(reply["result"]["tools"].is_array(), "an odd id must not stop the call: {reply}");
assert_still_serving(&mut server, "three malformed requests");
}
#[test]
fn an_unknown_method_is_method_not_found() {
let mut server = Server::start().expect("start server");
let reply = server.request("tools/nope", serde_json::json!({})).expect("reply to unknown method");
assert_eq!(error_code(&reply, "unknown method"), METHOD_NOT_FOUND, "{reply}");
assert!(
reply["error"]["message"].as_str().unwrap_or_default().contains("tools/nope"),
"the error should name the method that wasn't found: {reply}"
);
assert_still_serving(&mut server, "an unknown method");
}
#[test]
fn notifications_and_blank_lines_are_answered_with_nothing_at_all() {
let mut server = Server::start().expect("start server");
for quiet in [
r#"{"jsonrpc": "2.0", "method": "notifications/initialized"}"#, r#"{"jsonrpc": "2.0", "method": "notifications/unheard-of"}"#, r#"{"jsonrpc": "2.0", "params": {}}"#, "",
" ",
] {
server.send_raw(quiet).expect("write a quiet line");
}
server.send_raw(r#"{"jsonrpc": "2.0", "id": 99, "method": "tools/list"}"#).expect("write request");
let next = server.read_reply().expect("reply to the request after the quiet lines");
assert_eq!(next["id"], 99, "something replied to a notification or a blank line: {next}");
assert!(next["result"]["tools"].is_array(), "the request after the quiet lines must succeed: {next}");
}
#[test]
fn eof_on_stdin_exits_cleanly() {
let mut server = Server::start().expect("start server");
assert_still_serving(&mut server, "startup");
let status =
server.close_stdin_and_wait(Duration::from_secs(10)).expect("server did not exit after EOF on stdin");
assert!(status.success(), "EOF should be a clean exit, got {status:?}");
}
#[test]
fn a_final_request_without_a_trailing_newline_is_answered_at_eof() {
let mut server = Server::start().expect("start server");
server
.send_raw_unterminated(r#"{"jsonrpc": "2.0", "id": 7, "method": "tools/list"}"#)
.expect("write an unterminated request");
let status = server.close_stdin_and_wait(Duration::from_secs(10)).expect("exit after EOF");
assert!(status.success(), "expected a clean exit, got {status:?}");
let reply = server.read_reply().expect("reply to an unterminated request");
assert_eq!(reply["id"], 7, "an unterminated final line must still be answered: {reply}");
assert!(reply["result"]["tools"].is_array(), "and answered properly, not with an error: {reply}");
}