use super::serve;
use serde_json::{Value, json};
use std::io::Cursor;
fn exchange(requests: &[Value]) -> Vec<Value> {
let input = requests
.iter()
.map(Value::to_string)
.collect::<Vec<_>>()
.join("\n");
let mut output = Vec::new();
serve(Cursor::new(input), &mut output).expect("serve over in-memory buffers");
decode(output)
}
fn initialized_exchange(requests: &[Value]) -> Vec<Value> {
let mut all = vec![
json!({
"jsonrpc": "2.0",
"id": "initialize",
"method": "initialize",
"params": { "protocolVersion": "2025-11-25" }
}),
json!({
"jsonrpc": "2.0",
"method": "notifications/initialized"
}),
];
all.extend_from_slice(requests);
let mut responses = exchange(&all);
responses.remove(0);
responses
}
fn decode(output: Vec<u8>) -> Vec<Value> {
String::from_utf8(output)
.expect("responses are utf-8")
.lines()
.filter(|line| !line.trim().is_empty())
.map(|line| serde_json::from_str(line).expect("each response line is valid json"))
.collect()
}
#[test]
fn initialize_reports_server_info_and_tools_capability() {
let responses = exchange(&[
json!({
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": { "protocolVersion": "2025-06-18" }
}),
json!({
"jsonrpc": "2.0",
"id": 2,
"method": "initialize",
"params": { "protocolVersion": "2024-11-05" }
}),
]);
assert_eq!(responses.len(), 2);
let result = &responses[0]["result"];
assert_eq!(result["serverInfo"]["name"], "repopilot");
assert!(result["capabilities"]["tools"].is_object());
assert_eq!(result["protocolVersion"], "2025-11-25");
assert_eq!(responses[1]["result"]["protocolVersion"], "2024-11-05");
}
#[test]
fn tools_list_advertises_all_tools_with_schemas() {
let responses = initialized_exchange(&[json!({
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list"
})]);
let tools = responses[0]["result"]["tools"]
.as_array()
.expect("tools array");
let names: Vec<&str> = tools
.iter()
.map(|tool| tool["name"].as_str().expect("tool name"))
.collect();
assert_eq!(
names,
[
"repopilot_review_change",
"repopilot_scan",
"repopilot_context",
"repopilot_explain_file",
]
);
for tool in tools {
assert_eq!(
tool["inputSchema"]["type"], "object",
"tool {}",
tool["name"]
);
assert!(tool["outputSchema"].is_object());
assert_eq!(tool["annotations"]["readOnlyHint"], true);
}
}
#[test]
fn notifications_receive_no_response() {
let responses = exchange(&[json!({
"jsonrpc": "2.0",
"method": "notifications/initialized"
})]);
assert!(responses.is_empty());
}
#[test]
fn unknown_method_returns_method_not_found() {
let responses = initialized_exchange(&[json!({
"jsonrpc": "2.0",
"id": 3,
"method": "does/not/exist"
})]);
assert_eq!(responses[0]["error"]["code"], -32601);
}
#[test]
fn unknown_tool_returns_in_band_error_result() {
let responses = initialized_exchange(&[json!({
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": { "name": "repopilot_nope", "arguments": {} }
})]);
let result = &responses[0]["result"];
assert_eq!(result["isError"], true);
assert!(
result["content"][0]["text"]
.as_str()
.unwrap()
.contains("unknown tool")
);
}
#[test]
fn malformed_line_returns_parse_error_and_server_continues() {
let input = "this is not json\n{\"jsonrpc\":\"2.0\",\"id\":9,\"method\":\"ping\"}";
let mut output = Vec::new();
serve(Cursor::new(input), &mut output).expect("serve");
let responses = decode(output);
assert_eq!(responses.len(), 2);
assert_eq!(responses[0]["error"]["code"], -32700);
assert_eq!(responses[1]["id"], 9);
assert!(responses[1]["result"].is_object());
}
#[test]
fn lists_resources_and_prompts() {
let responses = initialized_exchange(&[
json!({"jsonrpc":"2.0","id":1,"method":"resources/list"}),
json!({"jsonrpc":"2.0","id":2,"method":"prompts/list"}),
json!({
"jsonrpc":"2.0",
"id":3,
"method":"prompts/get",
"params":{"name":"review-change"}
}),
]);
assert!(
responses[0]["result"]["resources"]
.as_array()
.unwrap()
.iter()
.any(|resource| resource["uri"] == "repopilot://rules")
);
assert!(
responses[0]["result"]["resources"]
.as_array()
.unwrap()
.iter()
.any(|resource| resource["uri"] == "repopilot://repository-summary")
);
assert_eq!(
responses[1]["result"]["prompts"].as_array().unwrap().len(),
2
);
assert_eq!(
responses[2]["result"]["messages"][0]["content"]["type"],
"text"
);
}