#![allow(clippy::unwrap_used, clippy::expect_used)]
use ant_quic::crypto::raw_public_keys::pqc::generate_ml_dsa_keypair;
use ant_quic::transport::TransportAddr;
use ant_quic::{NatType, Node, NodeConfig, NodeStatus};
use std::collections::HashSet;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::time::Duration;
use tokio::time::timeout;
fn normalize_local_addr(addr: SocketAddr) -> SocketAddr {
if addr.ip().is_unspecified() {
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), addr.port())
} else {
addr
}
}
mod zero_config_tests {
use super::*;
#[tokio::test]
async fn test_node_new_zero_config() {
let node = Node::new().await.expect("Node::new() should succeed");
let local_addr = node.local_addr().expect("Should have local address");
assert!(local_addr.port() > 0, "Node should bind to a valid port");
println!("Zero-config node listening on: {}", local_addr);
let peer_id = node.peer_id();
println!("Zero-config node peer ID: {:?}", peer_id);
let public_key = node.public_key_bytes();
assert_eq!(
public_key.len(),
1952,
"ML-DSA-65 public key should be 1952 bytes"
);
node.shutdown().await;
}
#[tokio::test]
async fn test_node_bind_specific_addr() {
let bind_addr: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0);
let node = Node::bind(bind_addr)
.await
.expect("Node::bind() should succeed");
let local_addr = node.local_addr().expect("Should have local address");
assert!(
local_addr.ip() == IpAddr::V4(Ipv4Addr::LOCALHOST) || local_addr.ip().is_unspecified(),
"Should bind to localhost or unspecified (dual-stack), got: {}",
local_addr.ip()
);
println!("Node bound to: {}", local_addr);
node.shutdown().await;
}
#[tokio::test]
async fn test_node_with_peers() {
let node1 = Node::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0))
.await
.expect("First node should succeed");
let node1_addr = normalize_local_addr(node1.local_addr().expect("Should have address"));
println!("First node at: {}", node1_addr);
let node2 = Node::with_peers(vec![node1_addr])
.await
.expect("Node::with_peers() should succeed");
let node2_addr = node2.local_addr().expect("Should have address");
println!("Second node at: {}", node2_addr);
println!("Second node peer ID: {:?}", node2.peer_id());
assert_ne!(
node1.peer_id(),
node2.peer_id(),
"Nodes should have different peer IDs"
);
node1.shutdown().await;
node2.shutdown().await;
}
#[tokio::test]
async fn test_node_with_keypair_api() {
let (public_key, secret_key) = generate_ml_dsa_keypair().expect("keygen");
let node = Node::with_keypair(public_key, secret_key)
.await
.expect("Node::with_keypair() should succeed");
let local_addr = node.local_addr().expect("Should have address");
let public_key_bytes = node.public_key_bytes();
println!("Node with keypair at: {}", local_addr);
println!("Public key (peer ID): {}", hex::encode(public_key_bytes));
assert_eq!(public_key_bytes.len(), 1952);
node.shutdown().await;
}
#[tokio::test]
async fn test_node_with_config() {
let bind_addr: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0);
let config = NodeConfig::builder().bind_addr(bind_addr).build();
let node = Node::with_config(config)
.await
.expect("Node::with_config() should succeed");
let local_addr = node.local_addr().expect("Should have address");
assert!(
local_addr.ip() == IpAddr::V4(Ipv4Addr::LOCALHOST) || local_addr.ip().is_unspecified(),
"Should use config bind address (dual-stack may report [::])"
);
node.shutdown().await;
}
}
mod status_tests {
use super::*;
#[tokio::test]
async fn test_node_status_basic_fields() {
let node = Node::new().await.expect("Node should create");
let local_addr = node.local_addr().expect("Should have address");
let status: NodeStatus = node.status().await;
assert_eq!(
status.peer_id,
node.peer_id(),
"Status peer_id should match"
);
assert_eq!(
status.local_addr, local_addr,
"Status local_addr should match"
);
assert_eq!(
status.nat_type,
NatType::Unknown,
"NAT type should be unknown initially"
);
println!("NodeStatus:");
println!(" peer_id: {:?}", status.peer_id);
println!(" local_addr: {}", status.local_addr);
println!(" nat_type: {:?}", status.nat_type);
println!(" can_receive_direct: {}", status.can_receive_direct);
println!(" connected_peers: {}", status.connected_peers);
println!(" is_relaying: {}", status.is_relaying);
println!(" is_coordinating: {}", status.is_coordinating);
println!(" uptime: {:?}", status.uptime);
node.shutdown().await;
}
#[tokio::test]
async fn test_node_status_relay_fields() {
let node = Node::new().await.expect("Node should create");
let status = node.status().await;
println!("Relay status:");
println!(" is_relaying: {}", status.is_relaying);
println!(" relay_sessions: {}", status.relay_sessions);
println!(" relay_bytes_forwarded: {}", status.relay_bytes_forwarded);
assert_eq!(status.relay_sessions, 0, "No relay sessions initially");
node.shutdown().await;
}
#[tokio::test]
async fn test_node_status_coordinator_fields() {
let node = Node::new().await.expect("Node should create");
let status = node.status().await;
println!("Coordinator status:");
println!(" is_coordinating: {}", status.is_coordinating);
println!(" coordination_sessions: {}", status.coordination_sessions);
node.shutdown().await;
}
#[tokio::test]
async fn test_node_status_uptime() {
let node = Node::new().await.expect("Node should create");
let status1 = node.status().await;
let uptime1 = status1.uptime;
tokio::time::sleep(Duration::from_millis(100)).await;
let status2 = node.status().await;
let uptime2 = status2.uptime;
assert!(uptime2 > uptime1, "Uptime should increase over time");
println!("Uptime increased: {:?} -> {:?}", uptime1, uptime2);
node.shutdown().await;
}
#[tokio::test]
async fn test_node_status_helper_methods() {
let node = Node::new().await.expect("Node should create");
let status = node.status().await;
let is_connected = status.is_connected();
let can_help = status.can_help_traversal();
let total_conns = status.total_connections();
let direct_rate = status.direct_rate();
println!("NodeStatus helpers:");
println!(" is_connected(): {}", is_connected);
println!(" can_help_traversal(): {}", can_help);
println!(" total_connections(): {}", total_conns);
println!(" direct_rate(): {}", direct_rate);
assert!(!is_connected, "No connections initially");
assert_eq!(total_conns, 0, "No connections initially");
node.shutdown().await;
}
}
mod event_tests {
use super::*;
#[tokio::test]
async fn test_node_subscribe() {
let node = Node::new().await.expect("Node should create");
let mut events = node.subscribe();
println!("Subscribed to events");
node.shutdown().await;
let recv_result = events.try_recv();
println!("After shutdown, recv result: {:?}", recv_result);
}
#[tokio::test]
async fn test_multiple_subscribers() {
let node = Node::new().await.expect("Node should create");
let _sub1 = node.subscribe();
let _sub2 = node.subscribe();
let _sub3 = node.subscribe();
println!("Created 3 event subscribers");
node.shutdown().await;
}
}
mod connection_tests {
use super::*;
#[tokio::test]
async fn test_connect_addr_method_exists() {
let node = Node::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0))
.await
.expect("Node should create");
let target_addr: SocketAddr = "127.0.0.1:19999".parse().unwrap();
let result = timeout(Duration::from_millis(100), node.connect_addr(target_addr)).await;
match result {
Ok(Ok(_)) => println!("Unexpectedly connected"),
Ok(Err(e)) => println!("Connection error (expected): {}", e),
Err(_) => println!("Timeout (expected)"),
}
node.shutdown().await;
}
#[tokio::test]
async fn test_accept_method_exists() {
let node = Node::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0))
.await
.expect("Node should create");
let result = timeout(Duration::from_millis(50), node.accept()).await;
assert!(
result.is_err(),
"Should timeout with no incoming connections"
);
println!("Accept correctly timed out with no connections");
node.shutdown().await;
}
#[tokio::test]
async fn test_add_peer_dynamically() {
let node1 = Node::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0))
.await
.expect("Node 1 should create");
let node2 = Node::new().await.expect("Node 2 should create");
let node1_addr = node1.local_addr().expect("Should have address");
let _ = node2.add_peer(node1_addr).await;
println!("Added {} as known peer", node1_addr);
let peers = node2.connected_peers().await;
println!("Connected peers: {:?}", peers);
node1.shutdown().await;
node2.shutdown().await;
}
}
mod three_node_tests {
use super::*;
#[tokio::test]
async fn test_three_node_creation() {
println!("=== Three Node Simple API Test ===");
let node1 = Node::new().await.expect("Node 1 should create");
let node2 = Node::new().await.expect("Node 2 should create");
let node3 = Node::new().await.expect("Node 3 should create");
let addr1 = node1.local_addr().expect("Should have address");
let addr2 = node2.local_addr().expect("Should have address");
let addr3 = node3.local_addr().expect("Should have address");
println!("Node 1: {} -> {:?}", addr1, node1.peer_id());
println!("Node 2: {} -> {:?}", addr2, node2.peer_id());
println!("Node 3: {} -> {:?}", addr3, node3.peer_id());
let mut peer_ids = HashSet::new();
peer_ids.insert(node1.peer_id());
peer_ids.insert(node2.peer_id());
peer_ids.insert(node3.peer_id());
assert_eq!(peer_ids.len(), 3, "All nodes should have unique peer IDs");
let mut addrs = HashSet::new();
addrs.insert(addr1);
addrs.insert(addr2);
addrs.insert(addr3);
assert_eq!(addrs.len(), 3, "All nodes should have unique addresses");
node1.shutdown().await;
node2.shutdown().await;
node3.shutdown().await;
println!("=== Three Node Test Complete ===");
}
#[tokio::test]
async fn test_three_node_status_comparison() {
let node1 = Node::new().await.expect("Node 1 should create");
let node2 = Node::new().await.expect("Node 2 should create");
let node3 = Node::new().await.expect("Node 3 should create");
let status1 = node1.status().await;
let status2 = node2.status().await;
let status3 = node3.status().await;
println!("Status comparison:");
println!(
" Node 1: nat={:?}, peers={}",
status1.nat_type, status1.connected_peers
);
println!(
" Node 2: nat={:?}, peers={}",
status2.nat_type, status2.connected_peers
);
println!(
" Node 3: nat={:?}, peers={}",
status3.nat_type, status3.connected_peers
);
assert_eq!(status1.nat_type, NatType::Unknown);
assert_eq!(status2.nat_type, NatType::Unknown);
assert_eq!(status3.nat_type, NatType::Unknown);
node1.shutdown().await;
node2.shutdown().await;
node3.shutdown().await;
}
}
mod config_tests {
use super::*;
#[test]
fn test_config_default() {
let config = NodeConfig::default();
assert!(config.bind_addr.is_none());
assert!(config.known_peers.is_empty());
assert!(config.keypair.is_none());
}
#[test]
fn test_config_builder_bind_addr() {
let addr: SocketAddr = "127.0.0.1:9000".parse().unwrap();
let config = NodeConfig::builder().bind_addr(addr).build();
assert_eq!(config.bind_addr, Some(TransportAddr::from(addr)));
}
#[test]
fn test_config_builder_known_peers() {
let peer1: SocketAddr = "127.0.0.1:9000".parse().unwrap();
let peer2: SocketAddr = "127.0.0.1:9001".parse().unwrap();
let config = NodeConfig::builder()
.known_peer(peer1)
.known_peer(peer2)
.build();
assert_eq!(config.known_peers.len(), 2);
assert!(config.known_peers.contains(&TransportAddr::from(peer1)));
assert!(config.known_peers.contains(&TransportAddr::from(peer2)));
}
#[test]
fn test_config_builder_full() {
let addr: SocketAddr = "127.0.0.1:9000".parse().unwrap();
let peer: SocketAddr = "1.2.3.4:9000".parse().unwrap();
let (public_key, secret_key) = generate_ml_dsa_keypair().expect("keygen");
let config = NodeConfig::builder()
.bind_addr(addr)
.known_peer(peer)
.keypair(public_key, secret_key)
.build();
assert_eq!(config.bind_addr, Some(TransportAddr::from(addr)));
assert_eq!(config.known_peers.len(), 1);
assert!(config.keypair.is_some());
}
#[test]
fn test_config_with_constructors() {
let addr: SocketAddr = "127.0.0.1:9000".parse().unwrap();
let config1 = NodeConfig::with_bind_addr(addr);
assert_eq!(config1.bind_addr, Some(TransportAddr::from(addr)));
let peers: Vec<SocketAddr> = vec![
"127.0.0.1:9000".parse().unwrap(),
"127.0.0.1:9001".parse().unwrap(),
];
let config2 = NodeConfig::with_known_peers(peers.clone());
assert_eq!(
config2.known_peers,
peers
.into_iter()
.map(TransportAddr::from)
.collect::<Vec<_>>()
);
}
}
mod nat_type_tests {
use super::*;
#[test]
fn test_nat_type_display() {
assert_eq!(format!("{}", NatType::None), "None (Public IP)");
assert_eq!(format!("{}", NatType::FullCone), "Full Cone");
assert_eq!(
format!("{}", NatType::AddressRestricted),
"Address Restricted"
);
assert_eq!(format!("{}", NatType::PortRestricted), "Port Restricted");
assert_eq!(format!("{}", NatType::Symmetric), "Symmetric");
assert_eq!(format!("{}", NatType::Unknown), "Unknown");
}
#[test]
fn test_nat_type_default() {
assert_eq!(NatType::default(), NatType::Unknown);
}
#[test]
fn test_nat_type_equality() {
assert_eq!(NatType::FullCone, NatType::FullCone);
assert_ne!(NatType::FullCone, NatType::Symmetric);
}
}
#[tokio::test]
async fn test_simple_api_integration_summary() {
println!("\n=== Simple Node API Integration Summary ===\n");
println!("1. Zero-config node creation...");
let node = Node::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0))
.await
.expect("Node::bind() failed");
let local_addr = normalize_local_addr(node.local_addr().expect("Should have address"));
println!(" Success: {} / {:?}", local_addr, node.peer_id());
println!("\n2. Status observability...");
let status = node.status().await;
println!(" NAT type: {:?}", status.nat_type);
println!(" Can receive direct: {}", status.can_receive_direct);
println!(" Is relaying: {}", status.is_relaying);
println!(" Uptime: {:?}", status.uptime);
println!("\n3. Event subscription...");
let _events = node.subscribe();
println!(" Subscribed to NodeEvent broadcast");
println!("\n4. Config builder...");
let peer_addr: SocketAddr = "127.0.0.1:9000".parse().unwrap();
let config = NodeConfig::builder().known_peer(peer_addr).build();
println!(
" Built config with {} known peers",
config.known_peers.len()
);
println!("\n5. Node with known peer...");
let config2 = NodeConfig::builder().known_peer(local_addr).build();
let node2 = Node::with_config(config2)
.await
.expect("Node::with_config() failed");
let node2_addr = node2.local_addr().expect("Should have address");
println!(" Second node: {} / {:?}", node2_addr, node2.peer_id());
node.shutdown().await;
node2.shutdown().await;
println!("\n=== Simple API Tests Complete ===\n");
}