mod common;
use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::time::timeout;
use truffle_core::network::{NetworkPeerEvent, NetworkProvider};
const OP_TIMEOUT: Duration = Duration::from_secs(15);
#[tokio::test]
async fn test_provider_start_and_auth() {
let Some(authkey) = common::require_authkey("test_provider_start_and_auth") else {
return;
};
common::init_test_tracing();
let pair = common::make_pair_of_nodes(&authkey).await;
let alpha_identity = pair.alpha.local_identity_async().await;
let beta_identity = pair.beta.local_identity_async().await;
assert!(alpha_identity.ip.is_some(), "alpha should have an IP");
assert!(beta_identity.ip.is_some(), "beta should have an IP");
assert!(
!alpha_identity.tailscale_hostname.is_empty(),
"alpha should have a hostname"
);
assert!(
!beta_identity.tailscale_hostname.is_empty(),
"beta should have a hostname"
);
let alpha_health = pair.alpha.health().await;
assert_eq!(alpha_health.state, "running");
assert!(alpha_health.healthy);
pair.stop().await;
}
#[tokio::test]
async fn test_peer_discovery() {
let Some(authkey) = common::require_authkey("test_peer_discovery") else {
return;
};
common::init_test_tracing();
let pair = common::make_pair_of_nodes(&authkey).await;
let alpha_peers = pair.alpha.peers().await;
let beta_peers = pair.beta.peers().await;
let alpha_sees_beta = alpha_peers.iter().any(|p| p.hostname == pair.beta_hostname);
let beta_sees_alpha = beta_peers.iter().any(|p| p.hostname == pair.alpha_hostname);
assert!(
alpha_sees_beta,
"alpha should see beta. Alpha peers: {:?}",
alpha_peers.iter().map(|p| &p.hostname).collect::<Vec<_>>()
);
assert!(
beta_sees_alpha,
"beta should see alpha. Beta peers: {:?}",
beta_peers.iter().map(|p| &p.hostname).collect::<Vec<_>>()
);
pair.stop().await;
}
#[tokio::test]
async fn test_peer_events() {
let Some(authkey) = common::require_authkey("test_peer_events") else {
return;
};
common::init_test_tracing();
let pair = common::make_pair_of_nodes(&authkey).await;
let mut alpha_events = pair.alpha.peer_events();
let collect = async {
let mut events = Vec::new();
let collect_deadline = tokio::time::Instant::now() + Duration::from_secs(3);
loop {
let remaining = collect_deadline.saturating_duration_since(tokio::time::Instant::now());
if remaining.is_zero() {
break events;
}
match timeout(remaining, alpha_events.recv()).await {
Ok(Ok(event)) => events.push(event),
_ => break events,
}
}
};
let events: Vec<NetworkPeerEvent> = collect.await;
eprintln!(" alpha observed {} event(s) in 3s window", events.len());
for e in &events {
eprintln!(" {e:?}");
}
let alpha_peers = pair.alpha.peers().await;
assert!(
alpha_peers.iter().any(|p| p.hostname == pair.beta_hostname),
"alpha.peers() must contain beta after rendezvous"
);
pair.stop().await;
}
#[tokio::test]
async fn test_dial_tcp() {
let Some(authkey) = common::require_authkey("test_dial_tcp") else {
return;
};
common::init_test_tracing();
let pair = common::make_pair_of_nodes(&authkey).await;
let mut listener = pair
.beta
.listen_tcp(0)
.await
.expect("beta should listen on ephemeral port");
let beta_listen_port = listener.port;
let nonce = b"truffle-test-nonce-01";
let beta_task = tokio::spawn(async move {
let incoming = listener
.incoming
.recv()
.await
.expect("beta should accept connection");
let mut stream = incoming.stream;
let mut buf = [0u8; 32];
let n = stream.read(&mut buf).await.expect("beta read nonce");
stream.write_all(&buf[..n]).await.expect("beta echo nonce");
stream.shutdown().await.ok();
buf[..n].to_vec()
});
let beta_ip = pair.beta_ip().await.to_string();
eprintln!(" dialing beta at {beta_ip}:{beta_listen_port}");
let mut stream = timeout(OP_TIMEOUT, pair.alpha.dial_tcp(&beta_ip, beta_listen_port))
.await
.expect("dial did not time out")
.expect("alpha dial should succeed");
stream.write_all(nonce).await.expect("alpha write nonce");
let mut echo = [0u8; 32];
let n = timeout(OP_TIMEOUT, stream.read(&mut echo))
.await
.expect("alpha read did not time out")
.expect("alpha read echo");
let echoed = &echo[..n];
assert_eq!(echoed, nonce, "alpha should read back the exact nonce");
let beta_received = beta_task.await.expect("beta task joined");
assert_eq!(beta_received, nonce, "beta should have received the nonce");
pair.stop().await;
}
#[tokio::test]
async fn test_ping() {
let Some(authkey) = common::require_authkey("test_ping") else {
return;
};
common::init_test_tracing();
let pair = common::make_pair_of_nodes(&authkey).await;
let beta_ip = pair.beta_ip().await.to_string();
eprintln!(" pinging beta at {beta_ip}");
let result = timeout(OP_TIMEOUT, pair.alpha.ping(&beta_ip))
.await
.expect("ping did not time out")
.expect("ping should succeed");
eprintln!(
" ping ok: latency={:?} connection={} peer_addr={:?}",
result.latency, result.connection, result.peer_addr
);
assert!(
result.latency > Duration::ZERO,
"ping latency should be > 0"
);
assert!(
!result.connection.is_empty(),
"ping connection type should not be empty"
);
pair.stop().await;
}
#[tokio::test]
async fn test_health() {
let Some(authkey) = common::require_authkey("test_health") else {
return;
};
common::init_test_tracing();
let pair = common::make_pair_of_nodes(&authkey).await;
let health = pair.alpha.health().await;
eprintln!(
" health: state={} healthy={} key_expiry={:?}",
health.state, health.healthy, health.key_expiry
);
assert_eq!(health.state, "running");
assert!(health.healthy);
let mut alpha = pair.alpha;
let beta = pair.beta;
let _alpha_hostname = pair.alpha_hostname;
let _beta_hostname = pair.beta_hostname;
alpha.stop().await.expect("alpha stop should succeed");
let after = alpha.health().await;
assert_eq!(after.state, "stopped");
assert!(!after.healthy);
let mut beta = beta;
let _ = beta.stop().await;
}