Skip to content

Commit 5243d77

Browse files
sanityclaude
andauthored
test(node): improve unit test coverage for node module (#2260)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent dd6b6ff commit 5243d77

File tree

2 files changed

+309
-0
lines changed

2 files changed

+309
-0
lines changed

crates/core/src/node/mod.rs

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1591,6 +1591,7 @@ mod tests {
15911591
use std::net::{Ipv4Addr, Ipv6Addr};
15921592

15931593
use super::*;
1594+
use crate::operations::OpError;
15941595

15951596
#[tokio::test]
15961597
async fn test_hostname_resolution() {
@@ -1612,4 +1613,160 @@ mod tests {
16121613
let socket_addr = NodeConfig::parse_socket_addr(&addr).await.unwrap();
16131614
assert_eq!(socket_addr.port(), 8080);
16141615
}
1616+
1617+
#[tokio::test]
1618+
async fn test_hostname_resolution_with_trailing_dot() {
1619+
// DNS names with trailing dot should be handled
1620+
let addr = Address::Hostname("localhost.".to_string());
1621+
let result = NodeConfig::parse_socket_addr(&addr).await;
1622+
// This should either succeed or fail gracefully
1623+
if let Ok(socket_addr) = result {
1624+
assert!(
1625+
socket_addr.ip() == IpAddr::V4(Ipv4Addr::LOCALHOST)
1626+
|| socket_addr.ip() == IpAddr::V6(Ipv6Addr::LOCALHOST)
1627+
);
1628+
}
1629+
}
1630+
1631+
#[tokio::test]
1632+
async fn test_hostname_resolution_direct_socket_addr() {
1633+
let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)), 8080);
1634+
let addr = Address::HostAddress(socket);
1635+
let resolved = NodeConfig::parse_socket_addr(&addr).await.unwrap();
1636+
assert_eq!(resolved, socket);
1637+
}
1638+
1639+
#[tokio::test]
1640+
async fn test_hostname_resolution_invalid_port() {
1641+
let addr = Address::Hostname("localhost:not_a_port".to_string());
1642+
let result = NodeConfig::parse_socket_addr(&addr).await;
1643+
assert!(result.is_err());
1644+
}
1645+
1646+
// PeerId tests
1647+
#[test]
1648+
fn test_peer_id_equality_based_on_addr() {
1649+
let keypair1 = TransportKeypair::new();
1650+
let keypair2 = TransportKeypair::new();
1651+
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
1652+
1653+
let peer1 = PeerId::new(addr, keypair1.public().clone());
1654+
let peer2 = PeerId::new(addr, keypair2.public().clone());
1655+
1656+
// PeerId equality is based on address, not public key
1657+
assert_eq!(peer1, peer2);
1658+
}
1659+
1660+
#[test]
1661+
fn test_peer_id_inequality_different_addr() {
1662+
let keypair = TransportKeypair::new();
1663+
let addr1 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
1664+
let addr2 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081);
1665+
1666+
let peer1 = PeerId::new(addr1, keypair.public().clone());
1667+
let peer2 = PeerId::new(addr2, keypair.public().clone());
1668+
1669+
assert_ne!(peer1, peer2);
1670+
}
1671+
1672+
#[test]
1673+
fn test_peer_id_ordering() {
1674+
let keypair = TransportKeypair::new();
1675+
let addr1 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
1676+
let addr2 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081);
1677+
1678+
let peer1 = PeerId::new(addr1, keypair.public().clone());
1679+
let peer2 = PeerId::new(addr2, keypair.public().clone());
1680+
1681+
// Ordering should be consistent
1682+
assert!(peer1 < peer2);
1683+
assert!(peer2 > peer1);
1684+
}
1685+
1686+
#[test]
1687+
fn test_peer_id_hash_consistency() {
1688+
use std::collections::hash_map::DefaultHasher;
1689+
use std::hash::{Hash, Hasher};
1690+
1691+
let keypair1 = TransportKeypair::new();
1692+
let keypair2 = TransportKeypair::new();
1693+
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
1694+
1695+
let peer1 = PeerId::new(addr, keypair1.public().clone());
1696+
let peer2 = PeerId::new(addr, keypair2.public().clone());
1697+
1698+
let mut hasher1 = DefaultHasher::new();
1699+
let mut hasher2 = DefaultHasher::new();
1700+
peer1.hash(&mut hasher1);
1701+
peer2.hash(&mut hasher2);
1702+
1703+
// Same address should produce same hash
1704+
assert_eq!(hasher1.finish(), hasher2.finish());
1705+
}
1706+
1707+
#[test]
1708+
fn test_peer_id_random_produces_unique() {
1709+
let peer1 = PeerId::random();
1710+
let peer2 = PeerId::random();
1711+
1712+
// Random peers should have different addresses (with high probability)
1713+
assert_ne!(peer1.addr, peer2.addr);
1714+
}
1715+
1716+
#[test]
1717+
fn test_peer_id_serialization() {
1718+
let peer = PeerId::random();
1719+
let bytes = peer.clone().to_bytes();
1720+
assert!(!bytes.is_empty());
1721+
1722+
// Should be deserializable
1723+
let deserialized: PeerId = bincode::deserialize(&bytes).unwrap();
1724+
assert_eq!(peer.addr, deserialized.addr);
1725+
}
1726+
1727+
#[test]
1728+
fn test_peer_id_display() {
1729+
let peer = PeerId::random();
1730+
let display = format!("{}", peer);
1731+
let debug = format!("{:?}", peer);
1732+
1733+
// Display and Debug should produce the same output
1734+
assert_eq!(display, debug);
1735+
// Should not be empty
1736+
assert!(!display.is_empty());
1737+
}
1738+
1739+
// InitPeerNode tests
1740+
#[test]
1741+
fn test_init_peer_node_construction() {
1742+
let keypair = TransportKeypair::new();
1743+
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)), 8080);
1744+
let peer_key_location = PeerKeyLocation::new(keypair.public().clone(), addr);
1745+
let location = Location::new(0.5);
1746+
1747+
let init_peer = InitPeerNode::new(peer_key_location.clone(), location);
1748+
1749+
assert_eq!(init_peer.peer_key_location, peer_key_location);
1750+
assert_eq!(init_peer.location, location);
1751+
}
1752+
1753+
// is_operation_completed tests
1754+
#[test]
1755+
fn test_is_operation_completed_with_none() {
1756+
let result: Result<Option<OpEnum>, OpError> = Ok(None);
1757+
assert!(!is_operation_completed(&result));
1758+
}
1759+
1760+
#[test]
1761+
fn test_is_operation_completed_with_error() {
1762+
let result: Result<Option<OpEnum>, OpError> =
1763+
Err(OpError::OpNotAvailable(super::OpNotAvailable::Running));
1764+
assert!(!is_operation_completed(&result));
1765+
}
1766+
1767+
#[test]
1768+
fn test_is_operation_completed_with_state_pushed_error() {
1769+
let result: Result<Option<OpEnum>, OpError> = Err(OpError::StatePushed);
1770+
assert!(!is_operation_completed(&result));
1771+
}
16151772
}

crates/core/src/node/network_bridge.rs

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,4 +283,156 @@ mod tests {
283283

284284
assert!(result.is_err(), "Send should fail when receiver is dropped");
285285
}
286+
287+
// Note: Tests that require NetMessage creation are omitted because
288+
// constructing valid NetMessage instances requires complex setup with
289+
// cryptographic keys, proper state management, and specific operation states.
290+
// The core channel functionality is already well-tested by the NodeEvent tests above.
291+
292+
/// Test channel capacity doesn't block under normal load
293+
#[tokio::test]
294+
async fn test_channel_capacity() {
295+
let (notification_channel, notification_tx) = event_loop_notification_channel();
296+
let mut rx = notification_channel.notifications_receiver;
297+
298+
// Send multiple messages without reading
299+
for _ in 0..50 {
300+
let test_event = crate::message::NodeEvent::Disconnect { cause: None };
301+
notification_tx
302+
.notifications_sender()
303+
.send(Either::Right(test_event))
304+
.await
305+
.expect("Should not block with capacity of 100");
306+
}
307+
308+
// Now read them all
309+
let mut count = 0;
310+
while count < 50 {
311+
match timeout(Duration::from_millis(10), rx.recv()).await {
312+
Ok(Some(_)) => count += 1,
313+
_ => break,
314+
}
315+
}
316+
317+
assert_eq!(count, 50, "Should receive all 50 messages");
318+
}
319+
320+
/// Test EventLoopNotificationsSender clone works correctly
321+
#[tokio::test]
322+
async fn test_sender_clone() {
323+
let (notification_channel, notification_tx) = event_loop_notification_channel();
324+
let mut rx = notification_channel.notifications_receiver;
325+
326+
// Clone the sender
327+
let cloned_tx = notification_tx.clone();
328+
329+
// Send from original
330+
let test_event1 = crate::message::NodeEvent::Disconnect { cause: None };
331+
notification_tx
332+
.notifications_sender()
333+
.send(Either::Right(test_event1))
334+
.await
335+
.expect("Should send from original");
336+
337+
// Send from clone
338+
let test_event2 = crate::message::NodeEvent::Disconnect {
339+
cause: Some("cloned".into()),
340+
};
341+
cloned_tx
342+
.notifications_sender()
343+
.send(Either::Right(test_event2))
344+
.await
345+
.expect("Should send from clone");
346+
347+
// Both should be received
348+
let mut received = 0;
349+
for _ in 0..2 {
350+
if timeout(Duration::from_millis(100), rx.recv()).await.is_ok() {
351+
received += 1;
352+
}
353+
}
354+
assert_eq!(received, 2, "Should receive both messages");
355+
}
356+
}
357+
358+
// ConnectionError tests
359+
#[cfg(test)]
360+
mod connection_error_tests {
361+
use super::*;
362+
363+
#[test]
364+
fn test_connection_error_clone() {
365+
let errors = vec![
366+
ConnectionError::LocationUnknown,
367+
ConnectionError::SendNotCompleted("127.0.0.1:8080".parse().unwrap()),
368+
ConnectionError::UnexpectedReq,
369+
ConnectionError::Serialization(None),
370+
ConnectionError::TransportError("test error".to_string()),
371+
ConnectionError::FailedConnectOp,
372+
ConnectionError::UnwantedConnection,
373+
ConnectionError::AddressBlocked("127.0.0.1:8080".parse().unwrap()),
374+
ConnectionError::IOError("io error".to_string()),
375+
ConnectionError::Timeout,
376+
];
377+
378+
for error in errors {
379+
let cloned = error.clone();
380+
// Verify clone produces equivalent error
381+
assert_eq!(format!("{}", error), format!("{}", cloned));
382+
}
383+
}
384+
385+
#[test]
386+
fn test_connection_error_from_io_error() {
387+
let io_error = std::io::Error::other("test io error");
388+
let conn_error: ConnectionError = io_error.into();
389+
390+
match conn_error {
391+
ConnectionError::IOError(msg) => {
392+
assert!(msg.contains("test io error"));
393+
}
394+
_ => panic!("Expected IOError variant"),
395+
}
396+
}
397+
398+
#[test]
399+
fn test_connection_error_display() {
400+
let error = ConnectionError::LocationUnknown;
401+
let display = format!("{}", error);
402+
assert!(!display.is_empty());
403+
assert!(display.contains("location unknown"));
404+
405+
let error = ConnectionError::SendNotCompleted("127.0.0.1:8080".parse().unwrap());
406+
let display = format!("{}", error);
407+
assert!(display.contains("127.0.0.1:8080"));
408+
409+
let error = ConnectionError::AddressBlocked("192.168.1.1:9000".parse().unwrap());
410+
let display = format!("{}", error);
411+
assert!(display.contains("192.168.1.1:9000"));
412+
413+
let error = ConnectionError::Timeout;
414+
let display = format!("{}", error);
415+
assert!(display.contains("timeout"));
416+
}
417+
418+
#[test]
419+
fn test_serialization_error_clone_loses_inner() {
420+
// When cloning a Serialization error, the inner error is lost
421+
let inner = Box::new(bincode::ErrorKind::SizeLimit);
422+
let original = ConnectionError::Serialization(Some(inner));
423+
let cloned = original.clone();
424+
425+
match cloned {
426+
ConnectionError::Serialization(None) => {} // Expected - inner is lost
427+
_ => panic!("Expected Serialization(None) after clone"),
428+
}
429+
}
430+
431+
#[test]
432+
fn test_connection_error_debug() {
433+
let error = ConnectionError::FailedConnectOp;
434+
let debug = format!("{:?}", error);
435+
assert!(!debug.is_empty());
436+
assert!(debug.contains("FailedConnectOp"));
437+
}
286438
}

0 commit comments

Comments
 (0)