use aingle_minimal::{
config::{GossipConfig, PowerMode, StorageConfig, TransportConfig},
BloomFilter, Config, GossipManager, Hash, MinimalNode, SyncManager,
};
use std::net::SocketAddr;
use std::time::Duration;
fn test_config_memory() -> Config {
Config {
node_id: None,
publish_interval: Duration::ZERO,
power_mode: PowerMode::Full,
transport: TransportConfig::Memory,
gossip: GossipConfig::default(),
storage: StorageConfig::memory(),
memory_limit: 256 * 1024,
enable_metrics: false,
enable_mdns: false,
log_level: "debug".to_string(),
}
}
#[test]
fn test_single_node_creation() {
let config = test_config_memory();
let node = MinimalNode::new(config).unwrap();
let pubkey = node.public_key();
assert!(!pubkey.to_hex().is_empty());
assert_eq!(pubkey.to_hex().len(), 64); }
#[test]
fn test_multiple_nodes_unique_identities() {
let nodes: Vec<_> = (0..5)
.map(|_| {
let config = test_config_memory();
MinimalNode::new(config).unwrap()
})
.collect();
let pubkeys: Vec<_> = nodes.iter().map(|n| n.public_key().to_hex()).collect();
for (i, pk1) in pubkeys.iter().enumerate() {
for (j, pk2) in pubkeys.iter().enumerate() {
if i != j {
assert_ne!(pk1, pk2, "Nodes {} and {} have same pubkey", i, j);
}
}
}
}
#[test]
fn test_node_initial_stats() {
let config = test_config_memory();
let node = MinimalNode::new(config).unwrap();
let stats = node.stats().unwrap();
assert_eq!(stats.entries_count, 0);
assert_eq!(stats.actions_count, 0);
assert_eq!(stats.peer_count, 0);
}
#[test]
fn test_add_peers_to_node() {
let config = test_config_memory();
let mut node = MinimalNode::new(config).unwrap();
assert_eq!(node.stats().unwrap().peer_count, 0);
for i in 1..=5 {
let addr: SocketAddr = format!("192.168.1.{}:5683", i).parse().unwrap();
node.add_peer(addr);
}
assert_eq!(node.stats().unwrap().peer_count, 5);
}
#[test]
fn test_add_duplicate_peers() {
let config = test_config_memory();
let mut node = MinimalNode::new(config).unwrap();
let addr: SocketAddr = "192.168.1.100:5683".parse().unwrap();
node.add_peer(addr);
node.add_peer(addr);
node.add_peer(addr);
assert_eq!(node.stats().unwrap().peer_count, 1);
}
#[test]
fn test_peer_count_tracking() {
let config = test_config_memory();
let mut node = MinimalNode::new(config).unwrap();
let addr1: SocketAddr = "192.168.1.100:5683".parse().unwrap();
let addr2: SocketAddr = "192.168.1.101:5683".parse().unwrap();
let addr3: SocketAddr = "192.168.1.102:5683".parse().unwrap();
node.add_peer(addr1);
node.add_peer(addr2);
node.add_peer(addr3);
assert_eq!(node.stats().unwrap().peer_count, 3);
}
#[test]
fn test_create_entry() {
let config = test_config_memory();
let mut node = MinimalNode::new(config).unwrap();
let data = serde_json::json!({
"sensor": "temperature",
"value": 23.5,
"unit": "celsius"
});
let hash = node.create_entry(data).unwrap();
assert_eq!(hash.as_bytes().len(), 32);
let stats = node.stats().unwrap();
assert!(stats.entries_count > 0 || stats.actions_count > 0);
}
#[test]
fn test_create_multiple_entries() {
let config = test_config_memory();
let mut node = MinimalNode::new(config).unwrap();
let mut hashes = Vec::new();
for i in 0..10 {
let data = serde_json::json!({
"reading": i,
"timestamp": i * 1000
});
let hash = node.create_entry(data).unwrap();
hashes.push(hash);
}
for (i, h1) in hashes.iter().enumerate() {
for (j, h2) in hashes.iter().enumerate() {
if i != j {
assert_ne!(
h1.to_hex(),
h2.to_hex(),
"Hashes {} and {} are duplicates",
i,
j
);
}
}
}
}
#[test]
fn test_get_entry() {
let config = test_config_memory();
let mut node = MinimalNode::new(config).unwrap();
let data = serde_json::json!({
"test": "data",
"value": 42
});
let hash = node.create_entry(data.clone()).unwrap();
let retrieved = node.get_entry(&hash);
assert!(retrieved.is_ok());
}
#[test]
fn test_gossip_manager_tracking() {
let config = GossipConfig::default();
let mut gossip = GossipManager::new(config);
let hashes: Vec<_> = (0..5).map(|i| Hash::from_bytes(&[i; 32])).collect();
for hash in &hashes {
gossip.announce(hash.clone());
}
for hash in &hashes {
assert!(
gossip.is_known(hash),
"Hash should be known after announcement"
);
}
let unknown = Hash::from_bytes(&[99; 32]);
assert!(!gossip.is_known(&unknown));
}
#[test]
fn test_gossip_pending_announcements() {
let config = GossipConfig::default();
let mut gossip = GossipManager::new(config);
let stats = gossip.stats();
assert_eq!(stats.pending_announcements, 0);
for i in 0..3 {
let hash = Hash::from_bytes(&[i; 32]);
gossip.announce(hash);
}
let stats = gossip.stats();
assert!(stats.pending_announcements > 0);
}
#[test]
fn test_bloom_filter_set_reconciliation() {
let mut filter_node_a = BloomFilter::new();
let mut filter_node_b = BloomFilter::new();
for i in 0..10 {
let hash = Hash::from_bytes(&[i; 32]);
filter_node_a.insert(&hash);
}
for i in 5..15 {
let hash = Hash::from_bytes(&[i; 32]);
filter_node_b.insert(&hash);
}
for i in 0..5 {
let hash = Hash::from_bytes(&[i; 32]);
assert!(
!filter_node_b.may_contain(&hash),
"Node B shouldn't have hash {}",
i
);
}
for i in 5..10 {
let hash = Hash::from_bytes(&[i; 32]);
assert!(filter_node_a.may_contain(&hash));
assert!(filter_node_b.may_contain(&hash));
}
}
#[test]
fn test_sync_peer_state_tracking() {
let mut sync = SyncManager::new(Duration::from_secs(60));
let peer1: SocketAddr = "192.168.1.100:5683".parse().unwrap();
let peer2: SocketAddr = "192.168.1.101:5683".parse().unwrap();
let peer3: SocketAddr = "192.168.1.102:5683".parse().unwrap();
sync.get_peer_state(&peer1).record_success();
sync.get_peer_state(&peer1).record_success();
sync.get_peer_state(&peer2).record_success();
sync.get_peer_state(&peer2).record_failure();
sync.get_peer_state(&peer3).record_failure();
sync.get_peer_state(&peer3).record_failure();
let stats = sync.stats();
assert_eq!(stats.peer_count, 3);
assert_eq!(stats.total_successful_syncs, 3);
assert_eq!(stats.total_failed_syncs, 3);
}
#[test]
fn test_sync_local_hash_management() {
let mut sync = SyncManager::new(Duration::from_secs(60));
for i in 0..50 {
let hash = Hash::from_bytes(&[i; 32]);
sync.add_local_hash(hash);
}
let stats = sync.stats();
assert_eq!(stats.local_hashes, 50);
let filter = sync.build_local_filter();
for i in 0..50 {
let hash = Hash::from_bytes(&[i; 32]);
assert!(
filter.may_contain(&hash),
"Filter should contain hash {}",
i
);
}
}
#[test]
fn test_sync_recovery_after_failures() {
let mut sync = SyncManager::new(Duration::from_secs(1));
let peer: SocketAddr = "192.168.1.100:5683".parse().unwrap();
for _ in 0..5 {
sync.get_peer_state(&peer).record_failure();
}
let state = sync.get_peer_state(&peer);
assert_eq!(state.failed_syncs, 5);
sync.get_peer_state(&peer).record_success();
let state = sync.get_peer_state(&peer);
assert_eq!(state.successful_syncs, 1);
assert_eq!(state.failed_syncs, 0); }
#[test]
fn test_two_node_data_exchange_simulation() {
let config1 = test_config_memory();
let config2 = test_config_memory();
let mut node1 = MinimalNode::new(config1).unwrap();
let mut node2 = MinimalNode::new(config2).unwrap();
let mut node1_hashes = Vec::new();
for i in 0..5 {
let data = serde_json::json!({"node": "1", "entry": i});
let hash = node1.create_entry(data).unwrap();
node1_hashes.push(hash);
}
let mut node2_hashes = Vec::new();
for i in 0..3 {
let data = serde_json::json!({"node": "2", "entry": i});
let hash = node2.create_entry(data).unwrap();
node2_hashes.push(hash);
}
let stats1 = node1.stats().unwrap();
let stats2 = node2.stats().unwrap();
assert!(stats1.entries_count >= 5 || stats1.actions_count >= 5);
assert!(stats2.entries_count >= 3 || stats2.actions_count >= 3);
let mut gossip1 = GossipManager::new(GossipConfig::default());
let mut gossip2 = GossipManager::new(GossipConfig::default());
for hash in &node1_hashes {
gossip1.announce(hash.clone());
}
for hash in &node2_hashes {
gossip2.announce(hash.clone());
}
let _filter1 = gossip1.get_bloom_filter(); let filter2 = gossip2.get_bloom_filter();
let mut missing_count = 0;
for hash in &node1_hashes {
if !filter2.may_contain(hash) {
missing_count += 1;
}
}
assert!(
missing_count > 0 || node1_hashes.is_empty(),
"Should identify hashes to sync"
);
}
#[test]
fn test_three_node_mesh_simulation() {
let mut node1 = MinimalNode::new(test_config_memory()).unwrap();
let mut node2 = MinimalNode::new(test_config_memory()).unwrap();
let mut node3 = MinimalNode::new(test_config_memory()).unwrap();
let addr1: SocketAddr = "192.168.1.1:5683".parse().unwrap();
let addr2: SocketAddr = "192.168.1.2:5683".parse().unwrap();
let addr3: SocketAddr = "192.168.1.3:5683".parse().unwrap();
node1.add_peer(addr2);
node2.add_peer(addr1);
node2.add_peer(addr3);
node3.add_peer(addr2);
assert_eq!(node1.stats().unwrap().peer_count, 1);
assert_eq!(node2.stats().unwrap().peer_count, 2);
assert_eq!(node3.stats().unwrap().peer_count, 1);
let data = serde_json::json!({"origin": "node1", "message": "hello mesh"});
let hash = node1.create_entry(data).unwrap();
let mut gossip1 = GossipManager::new(GossipConfig::default());
let mut gossip2 = GossipManager::new(GossipConfig::default());
let mut gossip3 = GossipManager::new(GossipConfig::default());
gossip1.announce(hash.clone());
gossip2.announce(hash.clone());
gossip3.announce(hash.clone());
assert!(gossip1.is_known(&hash));
assert!(gossip2.is_known(&hash));
assert!(gossip3.is_known(&hash));
}
#[test]
fn test_node_many_entries_performance() {
let config = test_config_memory();
let mut node = MinimalNode::new(config).unwrap();
let start = std::time::Instant::now();
for i in 0..100 {
let data = serde_json::json!({
"batch": "perf_test",
"index": i,
"data": format!("entry_data_{}", i)
});
node.create_entry(data).unwrap();
}
let duration = start.elapsed();
assert!(
duration < Duration::from_secs(5),
"Creating 100 entries took too long: {:?}",
duration
);
let stats = node.stats().unwrap();
assert!(stats.entries_count >= 100 || stats.actions_count >= 100);
}
#[test]
fn test_gossip_propagation_simulation() {
const NUM_NODES: usize = 10;
let mut gossips: Vec<_> = (0..NUM_NODES)
.map(|_| GossipManager::new(GossipConfig::default()))
.collect();
let original_hash = Hash::from_bytes(&[42; 32]);
gossips[0].announce(original_hash.clone());
for round in 0..NUM_NODES - 1 {
if gossips[round].is_known(&original_hash) {
gossips[round + 1].announce(original_hash.clone());
}
}
for (i, gossip) in gossips.iter().enumerate() {
assert!(
gossip.is_known(&original_hash),
"Node {} should know about the hash",
i
);
}
}
#[test]
fn test_gossip_concurrent_announcements() {
let mut gossip = GossipManager::new(GossipConfig::default());
let hashes: Vec<_> = (0..20).map(|i| Hash::from_bytes(&[i; 32])).collect();
for hash in &hashes {
gossip.announce(hash.clone());
}
for (i, hash) in hashes.iter().enumerate() {
assert!(gossip.is_known(hash), "Hash {} should be known", i);
}
let stats = gossip.stats();
assert_eq!(stats.pending_announcements, 20);
}
#[test]
fn test_star_topology_simulation() {
let mut hub = MinimalNode::new(test_config_memory()).unwrap();
let hub_addr: SocketAddr = "192.168.1.1:5683".parse().unwrap();
let spokes: Vec<_> = (0..5)
.map(|_| {
let mut node = MinimalNode::new(test_config_memory()).unwrap();
node.add_peer(hub_addr);
node
})
.collect();
for i in 2..7 {
let addr: SocketAddr = format!("192.168.1.{}:5683", i).parse().unwrap();
hub.add_peer(addr);
}
assert_eq!(hub.stats().unwrap().peer_count, 5);
for spoke in &spokes {
assert_eq!(spoke.stats().unwrap().peer_count, 1);
}
let data = serde_json::json!({"from": "hub", "broadcast": true});
let hash = hub.create_entry(data).unwrap();
let mut hub_gossip = GossipManager::new(GossipConfig::default());
hub_gossip.announce(hash.clone());
let mut spoke_gossips: Vec<_> = (0..5)
.map(|_| GossipManager::new(GossipConfig::default()))
.collect();
for gossip in &mut spoke_gossips {
gossip.announce(hash.clone());
}
assert!(hub_gossip.is_known(&hash));
for gossip in &spoke_gossips {
assert!(gossip.is_known(&hash));
}
}
#[test]
fn test_ring_topology_simulation() {
const RING_SIZE: usize = 6;
let nodes: Vec<_> = (0..RING_SIZE)
.map(|_| MinimalNode::new(test_config_memory()).unwrap())
.collect();
let mut nodes_with_peers: Vec<_> = nodes.into_iter().collect();
for i in 0..RING_SIZE {
let prev = if i == 0 { RING_SIZE - 1 } else { i - 1 };
let next = (i + 1) % RING_SIZE;
let prev_addr: SocketAddr = format!("192.168.1.{}:5683", prev + 1).parse().unwrap();
let next_addr: SocketAddr = format!("192.168.1.{}:5683", next + 1).parse().unwrap();
nodes_with_peers[i].add_peer(prev_addr);
nodes_with_peers[i].add_peer(next_addr);
}
for (i, node) in nodes_with_peers.iter().enumerate() {
assert_eq!(
node.stats().unwrap().peer_count,
2,
"Node {} should have exactly 2 peers",
i
);
}
}
#[test]
fn test_empty_peer_list_operations() {
let config = test_config_memory();
let node = MinimalNode::new(config).unwrap();
assert_eq!(node.stats().unwrap().peer_count, 0);
let gossip_stats = node.gossip_stats();
assert_eq!(gossip_stats.round, 0);
}
#[test]
fn test_gossip_empty_filter() {
let gossip = GossipManager::new(GossipConfig::default());
let filter = gossip.get_bloom_filter();
let test_hash = Hash::from_bytes(&[1; 32]);
let _ = filter.may_contain(&test_hash);
}
#[test]
fn test_sync_empty_peers() {
let sync = SyncManager::new(Duration::from_secs(60));
let stats = sync.stats();
assert_eq!(stats.peer_count, 0);
assert_eq!(stats.total_successful_syncs, 0);
assert_eq!(stats.total_failed_syncs, 0);
}
#[test]
fn test_iot_mode_multi_node() {
let config1 = Config::iot_mode();
let config2 = Config::iot_mode();
let node1 = MinimalNode::new(config1);
let node2 = MinimalNode::new(config2);
assert!(node1.is_ok());
assert!(node2.is_ok());
let n1 = node1.unwrap();
let n2 = node2.unwrap();
assert_ne!(n1.public_key().to_hex(), n2.public_key().to_hex());
}
#[test]
fn test_low_power_mode_multi_node() {
let config = Config::low_power();
assert!(config.gossip.loop_delay >= Duration::from_secs(5));
let node = MinimalNode::new(config);
assert!(node.is_ok());
}
#[test]
fn test_many_peers() {
let config = test_config_memory();
let mut node = MinimalNode::new(config).unwrap();
for i in 0..100 {
let addr: SocketAddr = format!("10.0.{}.{}:5683", i / 256, i % 256)
.parse()
.unwrap();
node.add_peer(addr);
}
assert_eq!(node.stats().unwrap().peer_count, 100);
}
#[test]
fn test_bloom_filter_many_hashes() {
let mut filter = BloomFilter::new();
for i in 0u16..1000 {
let mut bytes = [0u8; 32];
bytes[0] = (i >> 8) as u8;
bytes[1] = (i & 0xFF) as u8;
let hash = Hash::from_bytes(&bytes);
filter.insert(&hash);
}
let mut found = 0;
for i in 0u16..1000 {
let mut bytes = [0u8; 32];
bytes[0] = (i >> 8) as u8;
bytes[1] = (i & 0xFF) as u8;
let hash = Hash::from_bytes(&bytes);
if filter.may_contain(&hash) {
found += 1;
}
}
assert!(found >= 900, "Should find at least 90% of inserted hashes");
}