use std::time::Duration;
use iroh_http_core::{IrohEndpoint, NetworkingOptions, NodeOptions};
async fn bind_disabled() -> IrohEndpoint {
IrohEndpoint::bind(NodeOptions {
networking: NetworkingOptions {
disabled: true,
..Default::default()
},
..Default::default()
})
.await
.unwrap()
}
#[tokio::test]
async fn repeated_subscribe_same_peer_counts_one_watcher() {
let ep = bind_disabled().await;
let peer = "test-peer-node-id";
let _rx1 = ep.subscribe_path_changes(peer, 1);
let _rx2 = ep.subscribe_path_changes(peer, 2);
let _rx3 = ep.subscribe_path_changes(peer, 3);
let stats = ep.endpoint_stats();
assert_eq!(
stats.active_path_watchers, 1,
"one live watcher per peer expected; got {} (leaked/over-counted watchers)",
stats.active_path_watchers
);
assert_eq!(
stats.active_path_subscriptions, 1,
"one subscription entry per peer expected"
);
ep.close().await;
}
#[tokio::test]
async fn unsubscribe_decrements_watcher_gauge_to_zero() {
let ep = bind_disabled().await;
let peer = "test-peer-node-id";
let rx1 = ep.subscribe_path_changes(peer, 1);
let mut rx2 = ep.subscribe_path_changes(peer, 2);
assert_eq!(ep.endpoint_stats().active_path_watchers, 1);
drop(rx1);
ep.unsubscribe_path_changes(peer, 1);
assert!(
matches!(
rx2.try_recv(),
Err(tokio::sync::mpsc::error::TryRecvError::Empty)
),
"unsubscribing one token must not disconnect another"
);
drop(rx2);
ep.unsubscribe_path_changes(peer, 2);
let mut watchers = usize::MAX;
for _ in 0..50 {
watchers = ep.endpoint_stats().active_path_watchers;
if watchers == 0 {
break;
}
tokio::time::sleep(Duration::from_millis(50)).await;
}
assert_eq!(
watchers, 0,
"watcher gauge should return to zero after unsubscribe, got {watchers}"
);
ep.close().await;
}
#[tokio::test]
async fn rapid_resubscribe_does_not_duplicate_peer_watcher() {
let ep = bind_disabled().await;
let peer = "test-peer-node-id";
let rx1 = ep.subscribe_path_changes(peer, 1);
drop(rx1);
ep.unsubscribe_path_changes(peer, 1);
let mut rx2 = ep.subscribe_path_changes(peer, 2);
tokio::time::sleep(Duration::from_millis(300)).await;
assert_eq!(
ep.endpoint_stats().active_path_watchers,
1,
"rapid resubscribe must reuse the existing peer watcher"
);
assert!(
matches!(
rx2.try_recv(),
Err(tokio::sync::mpsc::error::TryRecvError::Empty)
),
"replacement subscription must remain connected"
);
drop(rx2);
ep.unsubscribe_path_changes(peer, 2);
for _ in 0..50 {
if ep.endpoint_stats().active_path_watchers == 0 {
break;
}
tokio::time::sleep(Duration::from_millis(50)).await;
}
assert_eq!(ep.endpoint_stats().active_path_watchers, 0);
ep.close().await;
}