use std::sync::Arc;
use std::time::Duration;
use mcpmesh::allowlist::{AllowlistGate, PeerEntry, PeerStore};
use mcpmesh::config::Config;
use mcpmesh::daemon::{self, MeshState, build_services_audited};
use mcpmesh::limits::MeshLimiters;
use mcpmesh::pairing::LiveInvites;
use mcpmesh::roster::gate::RosterGate;
use mcpmesh_net::registry::ConnRegistry;
use mcpmesh_net::{ALPN_MCP, ALPN_PING, TrustGate};
use tokio::time::timeout;
static SERIAL: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
fn assemble(
endpoint: iroh::Endpoint,
store: Arc<PeerStore>,
config_path: std::path::PathBuf,
) -> Arc<MeshState> {
let gate: Arc<dyn TrustGate> = Arc::new(AllowlistGate::new(store.clone()));
MeshState::new(
endpoint,
gate,
store,
Arc::new(LiveInvites::new()),
"self".into(),
config_path,
Arc::new(RosterGate::empty()),
Arc::new(ConnRegistry::new()),
None,
None,
None,
None,
)
}
#[allow(clippy::type_complexity)]
async fn live_session_harness() -> (
(
tempfile::TempDir,
tokio::task::JoinHandle<()>,
Box<dyn std::any::Any + Send>,
),
Arc<MeshState>,
Arc<MeshState>,
Arc<PeerStore>,
) {
let dir = tempfile::tempdir().unwrap();
let (relay_map, relay_url, relay_guard) = iroh::test_utils::run_relay_server()
.await
.expect("run in-process relay");
let mk = || {
iroh::Endpoint::builder(iroh::endpoint::presets::Minimal)
.relay_mode(iroh::RelayMode::Custom(relay_map.clone()))
.ca_tls_config(iroh_relay::tls::CaTlsConfig::insecure_skip_verify())
.alpns(vec![ALPN_MCP.to_vec(), ALPN_PING.to_vec()])
.bind()
};
let peer_ep = mk().await.expect("bind peer");
let our_ep = mk().await.expect("bind ours");
let peer_id = *peer_ep.id().as_bytes();
let our_id = *our_ep.id().as_bytes();
let peer_store = Arc::new(PeerStore::open(&dir.path().join("peer.redb")).unwrap());
peer_store
.add(PeerEntry {
endpoint_id: our_id,
nickname: "us".into(),
services: vec![],
paired_at: None,
user_id: None,
last_addr: None,
})
.unwrap();
let our_store = Arc::new(PeerStore::open(&dir.path().join("our.redb")).unwrap());
our_store
.add(PeerEntry {
endpoint_id: peer_id,
nickname: "bob".into(),
services: vec![],
paired_at: None,
user_id: None,
last_addr: Some(
serde_json::to_string(
&iroh::EndpointAddr::new(iroh::EndpointId::from_bytes(&peer_id).unwrap())
.with_relay_url(relay_url.clone()),
)
.expect("serialize relay addr"),
),
})
.unwrap();
let peer_cfg = dir.path().join("peer.toml");
std::fs::write(&peer_cfg, "").unwrap();
let peer_mesh = assemble(peer_ep, peer_store, peer_cfg);
let accept = daemon::spawn_accept_loop(
peer_mesh.clone(),
Arc::new(build_services_audited(
&Config::default(),
&mcpmesh::audit::AuditSink::disabled(),
&MeshLimiters::unlimited(),
)),
);
let our_cfg = dir.path().join("our.toml");
std::fs::write(&our_cfg, "").unwrap();
let mesh = assemble(our_ep, our_store.clone(), our_cfg);
(
(dir, accept, Box::new(relay_guard)),
mesh,
peer_mesh,
our_store,
)
}
#[tokio::test(flavor = "multi_thread")]
async fn a_live_relay_to_direct_transition_pushes_a_frame() {
let _serial = SERIAL.lock().await;
timeout(Duration::from_secs(240), async {
let dir = tempfile::tempdir().unwrap();
let (relay_map, relay_url, _relay_guard) = iroh::test_utils::run_relay_server()
.await
.expect("run in-process relay");
let mk = || {
iroh::Endpoint::builder(iroh::endpoint::presets::Minimal)
.relay_mode(iroh::RelayMode::Custom(relay_map.clone()))
.ca_tls_config(iroh_relay::tls::CaTlsConfig::insecure_skip_verify())
.alpns(vec![ALPN_MCP.to_vec(), ALPN_PING.to_vec()])
.bind()
};
let peer_ep = mk().await.expect("bind peer");
let our_ep = mk().await.expect("bind ours");
let peer_id = *peer_ep.id().as_bytes();
let our_id = *our_ep.id().as_bytes();
let peer_store = Arc::new(PeerStore::open(&dir.path().join("peer.redb")).unwrap());
peer_store
.add(PeerEntry {
endpoint_id: our_id,
nickname: "us".into(),
services: vec![],
paired_at: None,
user_id: None,
last_addr: None,
})
.unwrap();
let our_store = Arc::new(PeerStore::open(&dir.path().join("our.redb")).unwrap());
our_store
.add(PeerEntry {
endpoint_id: peer_id,
nickname: "bob".into(),
services: vec![],
paired_at: None,
user_id: None,
last_addr: Some(
serde_json::to_string(
&iroh::EndpointAddr::new(iroh::EndpointId::from_bytes(&peer_id).unwrap())
.with_relay_url(relay_url.clone()),
)
.expect("serialize relay addr"),
),
})
.unwrap();
let peer_cfg = dir.path().join("peer.toml");
std::fs::write(&peer_cfg, "").unwrap();
let peer_mesh = assemble(peer_ep, peer_store, peer_cfg);
let _peer_accept = daemon::spawn_accept_loop(
peer_mesh.clone(),
Arc::new(build_services_audited(
&Config::default(),
&mcpmesh::audit::AuditSink::disabled(),
&MeshLimiters::unlimited(),
)),
);
let our_cfg = dir.path().join("our.toml");
std::fs::write(&our_cfg, "").unwrap();
let mesh = assemble(our_ep, our_store, our_cfg);
let mut rx = mesh.reach_bcast_for_test().subscribe();
let seq_before = mesh.probe_seq_for_test();
let _session = daemon::dial_service(&mesh, "bob", "echo")
.await
.expect("open a mesh session to the peer");
let frame = timeout(Duration::from_secs(120), rx.recv())
.await
.expect(
"a live path change must push a Reachability frame — with no watcher on the \
session this times out, which is exactly the #92 item 2 defect",
)
.expect("broadcast channel alive");
assert_eq!(
frame.peer.path,
mcpmesh_local_api::PeerPath::Direct,
"the frame must report the path the session ACTUALLY moved to"
);
assert_eq!(
frame.source,
mcpmesh_local_api::ReachabilitySource::Session,
"the frame must be watcher-sourced: a probe-driven path frame is item (1), already \
shipped in 0.19.0, and this suite exists for item (2)"
);
assert_eq!(
frame.peer.rtt_ms, None,
"a watcher-SEEDED entry fabricates no measurement (true of this fixture, where no \
probe has run — not of a session frame in general)"
);
assert!(
mesh.probe_seq_for_test() > seq_before,
"the watcher must TAKE a ticket — sharing probe_peer's counter is what keeps an \
in-flight probe from overwriting this observation"
);
})
.await
.expect("live path event test timed out");
}
#[tokio::test(flavor = "multi_thread")]
async fn status_agrees_with_the_frame_the_watcher_just_pushed() {
let _serial = SERIAL.lock().await;
timeout(Duration::from_secs(240), async {
let (harness, mesh, _peer_mesh, _relay) = live_session_harness().await;
let mut rx = mesh.reach_bcast_for_test().subscribe();
let _session = daemon::dial_service(&mesh, "bob", "echo")
.await
.expect("open a mesh session to the peer");
let frame = timeout(Duration::from_secs(120), rx.recv())
.await
.expect("a live path change must push a frame")
.expect("broadcast channel alive");
let rows = daemon::reachability_of(&mesh);
let row = rows
.iter()
.find(|r| r.name == "bob")
.expect("the peer must appear in the status projection");
assert_eq!(
row.path, frame.peer.path,
"status must agree with the frame that was just pushed — a watcher that emits without \
writing the cache leaves status contradicting its own event stream"
);
drop(harness);
})
.await
.expect("status coherence test timed out");
}
#[tokio::test(flavor = "multi_thread")]
async fn the_watcher_stops_when_its_session_closes() {
let _serial = SERIAL.lock().await;
timeout(Duration::from_secs(240), async {
let (harness, mesh, _peer_mesh, _relay) = live_session_harness().await;
let mut rx = mesh.reach_bcast_for_test().subscribe();
let session = daemon::dial_service(&mesh, "bob", "echo")
.await
.expect("open a mesh session to the peer");
let _ = timeout(Duration::from_secs(120), rx.recv())
.await
.expect("a live path change must push a frame");
drop(session);
let ended = timeout(Duration::from_secs(20), async {
loop {
if daemon::live_path_watchers_for_test() == 0 {
break;
}
tokio::time::sleep(Duration::from_millis(100)).await;
}
})
.await;
assert!(
ended.is_ok(),
"the watcher must END with its connection — a task outliving what it watches is the \
#61 shape, and it holds the peer store's redb handle open"
);
drop(harness);
})
.await
.expect("watcher lifetime test timed out");
}