use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;
use yoagent::mcp::transport::McpTransport;
use yoagent::mcp::types::JsonRpcRequest;
use yoagent::mcp::HttpTransport;
async fn serve_then_hold_open(frames: String) -> String {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = format!("http://{}", listener.local_addr().unwrap());
tokio::spawn(async move {
let (mut socket, _) = listener.accept().await.unwrap();
let mut buf = [0u8; 4096];
let _ = socket.read(&mut buf).await;
let head = "HTTP/1.1 200 OK\r\n\
Content-Type: text/event-stream\r\n\
Transfer-Encoding: chunked\r\n\
\r\n";
socket.write_all(head.as_bytes()).await.unwrap();
socket
.write_all(format!("{:x}\r\n{}\r\n", frames.len(), frames).as_bytes())
.await
.unwrap();
socket.flush().await.unwrap();
tokio::time::sleep(Duration::from_secs(300)).await;
});
addr
}
#[tokio::test]
async fn returns_without_waiting_for_the_server_to_close_the_stream() {
let request = JsonRpcRequest::new("tools/list", None);
let frames = format!(
"event: message\ndata: {{\"jsonrpc\":\"2.0\",\"id\":{},\"result\":{{\"tools\":[]}}}}\n\n",
request.id
);
let addr = serve_then_hold_open(frames).await;
let transport = HttpTransport::new(&addr).unwrap();
let response = tokio::time::timeout(Duration::from_secs(10), transport.send(request))
.await
.expect("send must return once the response frame arrives, not at EOF")
.expect("the response must parse");
assert!(response.result.unwrap()["tools"].is_array());
}
#[tokio::test]
async fn returns_at_the_result_frame_despite_leading_notifications() {
let request = JsonRpcRequest::new("tools/call", None);
let frames = format!(
concat!(
"event: message\ndata: {{\"jsonrpc\":\"2.0\",\"method\":\"notifications/progress\",\"params\":{{\"progress\":1}}}}\n\n",
"event: message\ndata: {{\"jsonrpc\":\"2.0\",\"method\":\"notifications/message\",\"params\":{{\"level\":\"info\"}}}}\n\n",
"event: message\ndata: {{\"jsonrpc\":\"2.0\",\"id\":{},\"result\":{{\"content\":[{{\"type\":\"text\"}}]}}}}\n\n"
),
request.id
);
let addr = serve_then_hold_open(frames).await;
let transport = HttpTransport::new(&addr).unwrap();
let response = tokio::time::timeout(Duration::from_secs(10), transport.send(request))
.await
.expect("send must return at the result frame")
.expect("the response must parse");
assert!(
response.result.is_some(),
"the result must be selected over the notifications"
);
}