#![cfg(feature = "automerge-backend")]
use peat_protocol::discovery::peer::{
DiscoveryManager, DiscoveryStrategy, PeerInfo, StaticDiscovery,
};
#[tokio::test]
async fn test_static_discovery_from_memory() {
let peer1 = PeerInfo {
name: "Node Alpha".to_string(),
node_id: "a".repeat(64), addresses: vec!["192.168.1.10:5000".to_string()],
relay_url: None,
};
let peer2 = PeerInfo {
name: "Node Bravo".to_string(),
node_id: "b".repeat(64),
addresses: vec!["192.168.1.11:5000".to_string()],
relay_url: Some("https://relay.tactical.mil:3479".to_string()),
};
let mut discovery = StaticDiscovery::from_peers(vec![peer1.clone(), peer2.clone()]);
discovery.start().await.expect("Start should succeed");
let peers = discovery.discovered_peers().await;
assert_eq!(peers.len(), 2, "Should discover 2 peers");
assert_eq!(peers[0].name, "Node Alpha");
assert_eq!(peers[0].node_id, "a".repeat(64));
assert_eq!(peers[1].name, "Node Bravo");
assert_eq!(
peers[1].relay_url,
Some("https://relay.tactical.mil:3479".to_string())
);
}
#[tokio::test]
async fn test_discovery_manager_aggregation() {
let peer1 = PeerInfo {
name: "Node Alpha".to_string(),
node_id: "a".repeat(64),
addresses: vec!["192.168.1.10:5000".to_string()],
relay_url: None,
};
let peer2 = PeerInfo {
name: "Node Bravo".to_string(),
node_id: "b".repeat(64),
addresses: vec!["192.168.1.11:5000".to_string()],
relay_url: None,
};
let peer3 = PeerInfo {
name: "Node Charlie".to_string(),
node_id: "c".repeat(64),
addresses: vec!["192.168.1.12:5000".to_string()],
relay_url: None,
};
let peer1_duplicate = PeerInfo {
name: "Node Alpha (Duplicate)".to_string(),
node_id: "a".repeat(64), addresses: vec!["192.168.1.100:5000".to_string()],
relay_url: None,
};
let strategy1 = StaticDiscovery::from_peers(vec![peer1, peer2]);
let strategy2 = StaticDiscovery::from_peers(vec![peer3, peer1_duplicate]);
let mut manager = DiscoveryManager::new();
manager.add_strategy(Box::new(strategy1));
manager.add_strategy(Box::new(strategy2));
manager.start().await.expect("Manager start should succeed");
let peers = manager.get_peers().await;
assert_eq!(
peers.len(),
3,
"Should have 3 unique peers (deduplication by NodeId)"
);
let count = manager.peer_count().await;
assert_eq!(count, 3, "Peer count should match");
let node_ids: Vec<String> = peers.iter().map(|p| p.node_id.clone()).collect();
assert!(
node_ids.contains(&"a".repeat(64)),
"Should contain Node Alpha"
);
assert!(
node_ids.contains(&"b".repeat(64)),
"Should contain Node Bravo"
);
assert!(
node_ids.contains(&"c".repeat(64)),
"Should contain Node Charlie"
);
}
#[tokio::test]
async fn test_static_discovery_from_toml() {
use std::io::Write;
use tempfile::NamedTempFile;
let mut temp_file = NamedTempFile::new().expect("Failed to create temp file");
let toml_content = r#"
[[peers]]
name = "UAV Alpha"
node_id = "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
addresses = ["192.168.100.10:5000", "10.0.0.10:5000"]
relay_url = "https://relay.tactical.mil:3479"
[[peers]]
name = "UAV Bravo"
node_id = "b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3"
addresses = ["192.168.100.11:5000"]
"#;
temp_file
.write_all(toml_content.as_bytes())
.expect("Failed to write TOML");
temp_file.flush().expect("Failed to flush");
let mut discovery =
StaticDiscovery::from_file(temp_file.path()).expect("Failed to load from TOML");
discovery.start().await.expect("Start should succeed");
let peers = discovery.discovered_peers().await;
assert_eq!(peers.len(), 2, "Should load 2 peers from TOML");
assert_eq!(peers[0].name, "UAV Alpha");
assert_eq!(
peers[0].node_id,
"a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
);
assert_eq!(peers[0].addresses.len(), 2, "Should have 2 addresses");
assert_eq!(peers[0].addresses[0], "192.168.100.10:5000");
assert_eq!(
peers[0].relay_url,
Some("https://relay.tactical.mil:3479".to_string())
);
assert_eq!(peers[1].name, "UAV Bravo");
assert_eq!(peers[1].relay_url, None, "Should have no relay URL");
}
#[tokio::test]
async fn test_discovery_manager_empty() {
let mut manager = DiscoveryManager::new();
manager
.start()
.await
.expect("Should start even with no strategies");
let peers = manager.get_peers().await;
let count = manager.peer_count().await;
assert_eq!(peers.len(), 0, "Should have no peers");
assert_eq!(count, 0, "Peer count should be 0");
}
#[tokio::test]
async fn test_discovery_manager_default() {
let mut manager = DiscoveryManager::default();
let peer = PeerInfo {
name: "Test Node".to_string(),
node_id: "f".repeat(64),
addresses: vec!["10.0.0.1:5000".to_string()],
relay_url: None,
};
manager.add_strategy(Box::new(StaticDiscovery::from_peers(vec![peer])));
manager.start().await.expect("Should start");
let count = manager.peer_count().await;
assert_eq!(count, 1, "Should have 1 peer");
}
#[tokio::test]
async fn test_e2e_discovery_and_connection() {
use peat_protocol::network::IrohTransport;
use peat_protocol::storage::AutomergeStore;
use peat_protocol::sync::automerge::AutomergeIrohBackend;
use std::sync::Arc;
use tempfile::TempDir;
let temp_a = TempDir::new().expect("Failed to create temp dir");
let temp_b = TempDir::new().expect("Failed to create temp dir");
use peat_protocol::testing::E2EHarness;
let port_a = E2EHarness::allocate_tcp_port().expect("Failed to allocate port A");
let port_b = E2EHarness::allocate_tcp_port().expect("Failed to allocate port B");
println!(" Using TCP ports: {}, {}", port_a, port_b);
let addr_a: std::net::SocketAddr = format!("127.0.0.1:{}", port_a).parse().unwrap();
let addr_b: std::net::SocketAddr = format!("127.0.0.1:{}", port_b).parse().unwrap();
let transport_a = Arc::new(
IrohTransport::bind(addr_a)
.await
.expect("Failed to create transport A"),
);
let store_a = Arc::new(AutomergeStore::open(temp_a.path()).expect("Failed to create store A"));
let backend_a = Arc::new(AutomergeIrohBackend::from_parts(
Arc::clone(&store_a),
Arc::clone(&transport_a),
));
let transport_b = Arc::new(
IrohTransport::bind(addr_b)
.await
.expect("Failed to create transport B"),
);
let store_b = Arc::new(AutomergeStore::open(temp_b.path()).expect("Failed to create store B"));
let backend_b = Arc::new(AutomergeIrohBackend::from_parts(
Arc::clone(&store_b),
Arc::clone(&transport_b),
));
let endpoint_a = transport_a.endpoint_id();
let endpoint_b = transport_b.endpoint_id();
let addrs_a: Vec<String> = vec![addr_a.to_string()];
let addrs_b: Vec<String> = vec![addr_b.to_string()];
let peer_b_info = PeerInfo {
name: "Node B".to_string(),
node_id: hex::encode(endpoint_b.as_bytes()),
addresses: addrs_b.clone(),
relay_url: None,
};
backend_a
.add_discovery_strategy(Box::new(StaticDiscovery::from_peers(vec![peer_b_info])))
.await
.expect("Failed to add discovery strategy to Node A");
let peer_a_info = PeerInfo {
name: "Node A".to_string(),
node_id: hex::encode(endpoint_a.as_bytes()),
addresses: addrs_a.clone(),
relay_url: None,
};
backend_b
.add_discovery_strategy(Box::new(StaticDiscovery::from_peers(vec![peer_a_info])))
.await
.expect("Failed to add discovery strategy to Node B");
use peat_protocol::sync::traits::DataSyncBackend;
use peat_protocol::sync::types::{BackendConfig, TransportConfig};
use std::collections::HashMap;
let test_secret = peat_protocol::security::FormationKey::generate_secret();
let config_a = BackendConfig {
app_id: "test-app".to_string(),
persistence_dir: temp_a.path().to_path_buf(),
shared_key: Some(test_secret.clone()),
transport: TransportConfig::default(),
extra: HashMap::new(),
};
let config_b = BackendConfig {
app_id: "test-app".to_string(),
persistence_dir: temp_b.path().to_path_buf(),
shared_key: Some(test_secret),
transport: TransportConfig::default(),
extra: HashMap::new(),
};
backend_a
.initialize(config_a)
.await
.expect("Failed to initialize Node A");
backend_b
.initialize(config_b)
.await
.expect("Failed to initialize Node B");
println!("Waiting for nodes to discover and connect...");
tokio::time::sleep(std::time::Duration::from_secs(10)).await;
let peers_a = backend_a
.peer_discovery()
.discovered_peers()
.await
.expect("Failed to get peers from Node A");
let peers_b = backend_b
.peer_discovery()
.discovered_peers()
.await
.expect("Failed to get peers from Node B");
println!("Node A discovered {} peers", peers_a.len());
println!("Node B discovered {} peers", peers_b.len());
assert!(
!peers_a.is_empty(),
"Node A should have discovered at least one peer"
);
assert!(
!peers_b.is_empty(),
"Node B should have discovered at least one peer"
);
let connected_a = peers_a.iter().filter(|p| p.connected).count();
let connected_b = peers_b.iter().filter(|p| p.connected).count();
println!("Node A has {} connected peers", connected_a);
println!("Node B has {} connected peers", connected_b);
assert!(
connected_a > 0 || connected_b > 0,
"At least one node should have a connected peer"
);
let _ = backend_a.shutdown().await;
let _ = backend_b.shutdown().await;
}
#[tokio::test]
async fn test_mdns_zero_config_discovery() {
use peat_protocol::discovery::peer::MdnsDiscovery;
use peat_protocol::network::IrohTransport;
use peat_protocol::storage::AutomergeStore;
use peat_protocol::sync::automerge::AutomergeIrohBackend;
use std::sync::Arc;
use tempfile::TempDir;
let temp_a = TempDir::new().expect("Failed to create temp dir");
let temp_b = TempDir::new().expect("Failed to create temp dir");
let transport_a = Arc::new(
IrohTransport::new()
.await
.expect("Failed to create transport A"),
);
let store_a = Arc::new(AutomergeStore::open(temp_a.path()).expect("Failed to create store A"));
let backend_a = Arc::new(AutomergeIrohBackend::from_parts(
Arc::clone(&store_a),
Arc::clone(&transport_a),
));
let transport_b = Arc::new(
IrohTransport::new()
.await
.expect("Failed to create transport B"),
);
let store_b = Arc::new(AutomergeStore::open(temp_b.path()).expect("Failed to create store B"));
let backend_b = Arc::new(AutomergeIrohBackend::from_parts(
Arc::clone(&store_b),
Arc::clone(&transport_b),
));
let endpoint_a_ref = transport_a.endpoint();
let mdns_a = MdnsDiscovery::new(endpoint_a_ref.clone(), "UAV-Alpha".to_string())
.expect("Failed to create mDNS discovery for Node A");
backend_a
.add_discovery_strategy(Box::new(mdns_a))
.await
.expect("Failed to add mDNS discovery to Node A");
let endpoint_b_ref = transport_b.endpoint();
let mdns_b = MdnsDiscovery::new(endpoint_b_ref.clone(), "UAV-Bravo".to_string())
.expect("Failed to create mDNS discovery for Node B");
backend_b
.add_discovery_strategy(Box::new(mdns_b))
.await
.expect("Failed to add mDNS discovery to Node B");
use peat_protocol::sync::traits::DataSyncBackend;
use peat_protocol::sync::types::{BackendConfig, TransportConfig};
use std::collections::HashMap;
let test_secret = peat_protocol::security::FormationKey::generate_secret();
let config_a = BackendConfig {
app_id: "test-app-mdns".to_string(),
persistence_dir: temp_a.path().to_path_buf(),
shared_key: Some(test_secret.clone()),
transport: TransportConfig::default(),
extra: HashMap::new(),
};
let config_b = BackendConfig {
app_id: "test-app-mdns".to_string(),
persistence_dir: temp_b.path().to_path_buf(),
shared_key: Some(test_secret),
transport: TransportConfig::default(),
extra: HashMap::new(),
};
backend_a
.initialize(config_a)
.await
.expect("Failed to initialize Node A");
backend_b
.initialize(config_b)
.await
.expect("Failed to initialize Node B");
println!("Waiting for mDNS discovery and connection...");
tokio::time::sleep(std::time::Duration::from_secs(15)).await;
let peers_a = backend_a
.get_peer_discovery()
.discovered_peers()
.await
.expect("Failed to get peers from Node A");
let peers_b = backend_b
.get_peer_discovery()
.discovered_peers()
.await
.expect("Failed to get peers from Node B");
println!("Node A (UAV-Alpha) discovered {} peers", peers_a.len());
println!("Node B (UAV-Bravo) discovered {} peers", peers_b.len());
assert!(
!peers_a.is_empty(),
"Node A should have discovered at least one peer via mDNS"
);
assert!(
!peers_b.is_empty(),
"Node B should have discovered at least one peer via mDNS"
);
let peer_names_a: Vec<String> = peers_a.iter().map(|p| p.name.clone()).collect();
let peer_names_b: Vec<String> = peers_b.iter().map(|p| p.name.clone()).collect();
println!("Node A sees peers: {:?}", peer_names_a);
println!("Node B sees peers: {:?}", peer_names_b);
let _ = backend_a.shutdown().await;
let _ = backend_b.shutdown().await;
}