#![allow(clippy::unwrap_used, clippy::expect_used)]
use saorsa_core::{AdaptiveDhtConfig, NodeConfig, NodeMode, P2PNode};
use std::time::Duration;
fn test_config() -> NodeConfig {
NodeConfig::builder()
.local(true)
.port(0)
.ipv6(false)
.build()
.expect("test config should be valid")
}
#[tokio::test]
async fn new_node_is_not_running() {
let node = P2PNode::new(test_config()).await.unwrap();
assert!(!node.is_running(), "New node should not be running");
assert!(
!node.is_bootstrapped(),
"New node should not be bootstrapped"
);
}
#[tokio::test]
async fn each_node_gets_unique_peer_id() {
let node_a = P2PNode::new(test_config()).await.unwrap();
let node_b = P2PNode::new(test_config()).await.unwrap();
assert_ne!(
node_a.peer_id(),
node_b.peer_id(),
"Two nodes should have different peer IDs"
);
}
#[tokio::test]
async fn config_accessible_after_creation() {
let config = NodeConfig::builder()
.local(true)
.port(0)
.ipv6(false)
.max_connections(42)
.connection_timeout(Duration::from_secs(7))
.build()
.unwrap();
let node = P2PNode::new(config).await.unwrap();
assert_eq!(node.config().max_connections, 42);
assert_eq!(node.config().connection_timeout, Duration::from_secs(7));
}
#[tokio::test]
async fn start_makes_node_running() {
let node = P2PNode::new(test_config()).await.unwrap();
node.start().await.unwrap();
assert!(node.is_running(), "Node should be running after start()");
let addrs = node.listen_addrs().await;
assert!(
!addrs.is_empty(),
"Started node should have at least one listen address"
);
node.stop().await.unwrap();
}
#[tokio::test]
async fn stop_makes_node_not_running() {
let node = P2PNode::new(test_config()).await.unwrap();
node.start().await.unwrap();
assert!(node.is_running());
node.stop().await.unwrap();
assert!(
!node.is_running(),
"Node should not be running after stop()"
);
}
#[tokio::test]
async fn shutdown_alias_works() {
let node = P2PNode::new(test_config()).await.unwrap();
node.start().await.unwrap();
node.shutdown().await.unwrap();
assert!(
!node.is_running(),
"Node should not be running after shutdown()"
);
}
#[tokio::test]
async fn health_check_passes_with_no_peers() {
let node = P2PNode::new(test_config()).await.unwrap();
assert!(node.health_check().await.is_ok());
}
#[tokio::test]
async fn uptime_increases() {
let node = P2PNode::new(test_config()).await.unwrap();
let t1 = node.uptime();
tokio::time::sleep(Duration::from_millis(10)).await;
let t2 = node.uptime();
assert!(t2 > t1, "Uptime should increase over time");
}
#[tokio::test]
async fn started_node_has_zero_peers_when_isolated() {
let node = P2PNode::new(test_config()).await.unwrap();
node.start().await.unwrap();
let peers = node.connected_peers().await;
assert!(peers.is_empty(), "Isolated node should have no peers");
assert_eq!(node.peer_count().await, 0);
node.stop().await.unwrap();
}
#[tokio::test]
async fn builder_defaults_produce_valid_node() {
let config = NodeConfig::builder().local(true).port(0).build().unwrap();
let node = P2PNode::new(config).await.unwrap();
assert!(!node.is_running());
}
#[tokio::test]
async fn builder_client_mode() {
let config = NodeConfig::builder()
.local(true)
.port(0)
.ipv6(false)
.mode(NodeMode::Client)
.build()
.unwrap();
let node = P2PNode::new(config).await.unwrap();
assert_eq!(node.config().mode, NodeMode::Client);
}
#[tokio::test]
async fn builder_trust_enforcement_toggle() {
let config_off = NodeConfig::builder()
.local(true)
.port(0)
.ipv6(false)
.trust_enforcement(false)
.build()
.unwrap();
let node_off = P2PNode::new(config_off).await.unwrap();
assert!(
(node_off.adaptive_dht().config().swap_threshold - 0.0).abs() < f64::EPSILON,
"trust_enforcement(false) should set threshold to 0.0"
);
let config_on = NodeConfig::builder()
.local(true)
.port(0)
.ipv6(false)
.trust_enforcement(true)
.build()
.unwrap();
let node_on = P2PNode::new(config_on).await.unwrap();
assert!(
(node_on.adaptive_dht().config().swap_threshold
- AdaptiveDhtConfig::default().swap_threshold)
.abs()
< f64::EPSILON,
"trust_enforcement(true) should use default threshold"
);
}
#[tokio::test]
async fn local_mode_auto_enables_loopback() {
let config = NodeConfig::builder()
.local(true)
.port(0)
.ipv6(false)
.build()
.unwrap();
assert!(
config.allow_loopback,
"local(true) should auto-enable allow_loopback"
);
}
#[tokio::test]
async fn trust_system_available_before_start() {
let node = P2PNode::new(test_config()).await.unwrap();
let _engine = node.trust_engine();
let _dht = node.adaptive_dht();
let score = node.peer_trust(&saorsa_core::PeerId::random());
assert!((score - 0.5).abs() < f64::EPSILON);
}
#[tokio::test]
async fn trust_events_work_before_start() {
let node = P2PNode::new(test_config()).await.unwrap();
let peer = saorsa_core::PeerId::random();
node.report_trust_event(&peer, saorsa_core::TrustEvent::ApplicationSuccess(1.0))
.await;
assert!(
node.peer_trust(&peer) > 0.5,
"Trust event should take effect before start()"
);
}
#[tokio::test]
async fn event_subscription_works() {
let node = P2PNode::new(test_config()).await.unwrap();
let _rx = node.subscribe_events();
}
#[tokio::test]
async fn multiple_nodes_coexist() {
let mut nodes = Vec::new();
for _ in 0..3 {
let node = P2PNode::new(test_config()).await.unwrap();
node.start().await.unwrap();
nodes.push(node);
}
let mut all_addrs: Vec<String> = Vec::new();
for node in &nodes {
let addrs = node.listen_addrs().await;
assert!(!addrs.is_empty());
for addr in &addrs {
let addr_str = addr.to_string();
assert!(
!all_addrs.contains(&addr_str),
"Duplicate address found: {addr_str}"
);
all_addrs.push(addr_str);
}
}
for node in &nodes {
node.stop().await.unwrap();
}
}