#![cfg(all(feature = "http-server-volga", feature = "http-client"))]
use neva::{
App,
client::Client,
middleware::{MwContext, Next},
};
use std::sync::{Arc, Mutex};
#[tokio::test(flavor = "multi_thread")]
async fn disconnecting_sends_no_notification() {
let port = pick_free_port();
let addr = format!("127.0.0.1:{port}");
let seen: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
let recorder = seen.clone();
let mut app = App::new()
.with_options(|o| o.with_http(|h| h.bind(&addr).with_endpoint("/mcp")))
.wrap_notification(move |ctx: MwContext, next: Next| {
let recorder = recorder.clone();
async move {
if let neva::types::Message::Notification(n) = &ctx.msg
&& let Ok(mut seen) = recorder.lock()
{
seen.push(n.method.to_string());
}
next(ctx).await
}
});
app.map_tool("ping", || async move { "pong".to_string() });
let handle = tokio::spawn(async move { app.run().await });
tokio::time::sleep(std::time::Duration::from_millis(300)).await;
for _ in 0..20 {
let mut client = Client::new().with_options(|o| {
o.with_http(|h| h.bind(&addr).with_endpoint("/mcp"))
.with_timeout(std::time::Duration::from_secs(10))
});
client.connect().await.expect("connect");
client.list_tools(None).await.expect("list tools");
client.disconnect().await.expect("disconnect");
}
tokio::time::sleep(std::time::Duration::from_millis(300)).await;
let seen = seen.lock().expect("recorded notifications").clone();
assert!(
!seen.iter().any(|m| m == "notifications/cancelled"),
"disconnect must not announce itself with a request cancellation: {seen:?}"
);
handle.abort();
}
fn pick_free_port() -> u16 {
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let port = listener.local_addr().unwrap().port();
drop(listener);
port
}