use std::io::{BufRead, BufReader, Write};
use std::process::{Command, Stdio};
use std::sync::Once;
static BUILD: Once = Once::new();
fn ensure_binary_built() {
BUILD.call_once(|| {
let status = Command::new(env!("CARGO"))
.args(["build", "-p", "stand-in-reference"])
.status()
.expect("Failed to run cargo build");
assert!(status.success(), "Failed to build stand-in-reference");
});
}
fn reference_binary_path() -> std::path::PathBuf {
let mut path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.pop(); path.push("target");
path.push("debug");
if cfg!(windows) {
path.push("stand-in-reference.exe");
} else {
path.push("stand-in-reference");
}
path
}
fn spawn_server() -> std::process::Child {
ensure_binary_built();
let binary = reference_binary_path();
Command::new(binary)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
.expect("Failed to spawn stand-in-reference")
}
fn send_and_receive(child: &mut std::process::Child, request: &str) -> String {
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
stdin
.write_all(format!("{request}\n").as_bytes())
.expect("Failed to write to stdin");
stdin.flush().expect("Failed to flush stdin");
let stdout = child.stdout.as_mut().expect("Failed to open stdout");
let mut reader = BufReader::new(stdout);
let mut line = String::new();
reader.read_line(&mut line).expect("Failed to read line");
line
}
#[test]
fn test_full_lifecycle() {
let mut child = spawn_server();
let resp = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}"#,
);
let json: serde_json::Value = serde_json::from_str(resp.trim()).unwrap();
assert_eq!(json["result"]["protocolVersion"], "2025-03-26");
assert!(json["result"]["serverInfo"]["name"].is_string());
let resp = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":2,"method":"notifications/initialized"}"#,
);
let json: serde_json::Value = serde_json::from_str(resp.trim()).unwrap();
assert!(json["error"].is_null());
let resp = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":3,"method":"tools/list"}"#,
);
let json: serde_json::Value = serde_json::from_str(resp.trim()).unwrap();
let tools = json["result"]["tools"].as_array().unwrap();
assert_eq!(tools.len(), 3);
let names: Vec<&str> = tools.iter().map(|t| t["name"].as_str().unwrap()).collect();
assert!(names.contains(&"greet"));
assert!(names.contains(&"add"));
assert!(names.contains(&"echo"));
let resp = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"greet","arguments":{"name":"World"}}}"#,
);
let json: serde_json::Value = serde_json::from_str(resp.trim()).unwrap();
assert_eq!(json["result"]["content"][0]["text"], "Hello, World!");
drop(child.stdin.take());
let status = child.wait().expect("Failed to wait for child");
assert!(status.success());
}
#[test]
fn test_unknown_method_error() {
let mut child = spawn_server();
let resp = send_and_receive(&mut child, r#"{"jsonrpc":"2.0","id":1,"method":"foo/bar"}"#);
let json: serde_json::Value = serde_json::from_str(resp.trim()).unwrap();
assert_eq!(json["error"]["code"], -32601);
drop(child.stdin.take());
child.wait().unwrap();
}
#[test]
fn test_malformed_json_error() {
let mut child = spawn_server();
let resp = send_and_receive(&mut child, r#"not valid json"#);
let json: serde_json::Value = serde_json::from_str(resp.trim()).unwrap();
assert_eq!(json["error"]["code"], -32700);
drop(child.stdin.take());
child.wait().unwrap();
}
#[test]
fn test_unknown_tool_error() {
let mut child = spawn_server();
let resp = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"nonexistent","arguments":{}}}"#,
);
let json: serde_json::Value = serde_json::from_str(resp.trim()).unwrap();
assert_eq!(json["result"]["isError"], true);
drop(child.stdin.take());
child.wait().unwrap();
}
#[test]
fn test_resources_list() {
let mut child = spawn_server();
let _ = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}"#,
);
let _ = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":2,"method":"notifications/initialized"}"#,
);
let resp = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":3,"method":"resources/list"}"#,
);
let json: serde_json::Value = serde_json::from_str(resp.trim()).unwrap();
let resources = json["result"]["resources"].as_array().unwrap();
assert_eq!(resources.len(), 2);
let uris: Vec<&str> = resources
.iter()
.map(|r| r["uri"].as_str().unwrap())
.collect();
assert!(uris.contains(&"info://version"));
assert!(uris.contains(&"config://settings"));
drop(child.stdin.take());
child.wait().unwrap();
}
#[test]
fn test_resources_templates_list() {
let mut child = spawn_server();
let _ = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}"#,
);
let _ = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":2,"method":"notifications/initialized"}"#,
);
let resp = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":3,"method":"resources/templates/list"}"#,
);
let json: serde_json::Value = serde_json::from_str(resp.trim()).unwrap();
let templates = json["result"]["resourceTemplates"].as_array().unwrap();
assert_eq!(templates.len(), 1);
assert_eq!(
templates[0]["uriTemplate"].as_str().unwrap(),
"docs://{topic}/readme"
);
assert_eq!(templates[0]["name"].as_str().unwrap(), "Documentation");
drop(child.stdin.take());
child.wait().unwrap();
}
#[test]
fn test_resources_read_concrete() {
let mut child = spawn_server();
let _ = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}"#,
);
let _ = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":2,"method":"notifications/initialized"}"#,
);
let resp = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":3,"method":"resources/read","params":{"uri":"info://version"}}"#,
);
let json: serde_json::Value = serde_json::from_str(resp.trim()).unwrap();
assert!(json["error"].is_null());
let contents = json["result"]["contents"].as_array().unwrap();
assert_eq!(contents.len(), 1);
assert_eq!(contents[0]["uri"].as_str().unwrap(), "info://version");
assert!(contents[0]["text"].as_str().unwrap().contains("version"));
drop(child.stdin.take());
child.wait().unwrap();
}
#[test]
fn test_resources_read_template() {
let mut child = spawn_server();
let _ = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}"#,
);
let _ = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":2,"method":"notifications/initialized"}"#,
);
let resp = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":3,"method":"resources/read","params":{"uri":"docs://rust/readme"}}"#,
);
let json: serde_json::Value = serde_json::from_str(resp.trim()).unwrap();
assert!(json["error"].is_null());
let contents = json["result"]["contents"].as_array().unwrap();
assert_eq!(contents.len(), 1);
assert_eq!(contents[0]["uri"].as_str().unwrap(), "docs://rust/readme");
let text = contents[0]["text"].as_str().unwrap();
assert!(text.contains("# rust"));
assert!(text.contains("Documentation for rust"));
drop(child.stdin.take());
child.wait().unwrap();
}
#[test]
fn test_resources_read_unknown() {
let mut child = spawn_server();
let _ = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}"#,
);
let _ = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":2,"method":"notifications/initialized"}"#,
);
let resp = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":3,"method":"resources/read","params":{"uri":"file:///nonexistent"}}"#,
);
let json: serde_json::Value = serde_json::from_str(resp.trim()).unwrap();
assert!(json["error"].is_object());
assert_eq!(json["error"]["code"], -32601);
drop(child.stdin.take());
child.wait().unwrap();
}
#[test]
fn test_resources_subscribe_unsubscribe() {
let mut child = spawn_server();
let _ = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}"#,
);
let _ = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":2,"method":"notifications/initialized"}"#,
);
let resp = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":3,"method":"resources/subscribe","params":{"uri":"info://version"}}"#,
);
let json: serde_json::Value = serde_json::from_str(resp.trim()).unwrap();
assert!(json["error"].is_null());
let resp = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":4,"method":"resources/unsubscribe","params":{"uri":"info://version"}}"#,
);
let json: serde_json::Value = serde_json::from_str(resp.trim()).unwrap();
assert!(json["error"].is_null());
drop(child.stdin.take());
child.wait().unwrap();
}
#[test]
fn test_resources_unknown_method() {
let mut child = spawn_server();
let _ = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}"#,
);
let _ = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":2,"method":"notifications/initialized"}"#,
);
let resp = send_and_receive(
&mut child,
r#"{"jsonrpc":"2.0","id":3,"method":"resources/invalid"}"#,
);
let json: serde_json::Value = serde_json::from_str(resp.trim()).unwrap();
assert!(json["error"].is_object());
assert_eq!(json["error"]["code"], -32601);
drop(child.stdin.take());
child.wait().unwrap();
}