//! Scenario 2: several agents find each other, authenticate for real and //! exchange distinguishable messages. #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] mod common; use std::collections::HashSet; use std::sync::Arc; use common::{TestAgent, network, wait_event, wait_for_peers, wait_until}; use tsunagi::agent::Event; use tsunagi::dataplane::TestCapabilityPlugin; use tsunagi::discovery::SharedMemoryDiscovery; use tsunagi::proto::ControlMessage; #[tokio::test] async fn four_agents_form_a_mesh_and_exchange_distinguishable_messages() { let discovery = SharedMemoryDiscovery::new(); let (name, secret) = network("mesh-of-four"); let mut agents = Vec::new(); for index in 0..4 { let hostname = format!("host-{index}"); agents.push( TestAgent::spawn_with(move |cfg| cfg.with_hostname(hostname), &discovery) .await .unwrap(), ); } let mut network_ids = HashSet::new(); for agent in &agents { network_ids.insert(agent.agent.join_network(&name, &secret).await.unwrap()); } assert_eq!(network_ids.len(), 1, "one deterministic network space"); let network_id = network_ids.into_iter().next().unwrap(); // Full mesh: every agent must end up with the other three. for agent in &agents { wait_for_peers(&agent.agent, network_id, 3).await; } // Each peer announced its own hostname, so sessions are distinguishable. let status = agents[0].agent.network_status(network_id).await.unwrap(); let hostnames: HashSet = status .peers .iter() .filter_map(|peer| peer.hostname.clone()) .collect(); assert_eq!( hostnames.len(), 3, "three distinct hostnames: {hostnames:?}" ); // Distinguishable request/response: each peer echoes its own sequence. let mut events = agents[0].agent.subscribe(); for (index, peer) in agents.iter().skip(1).enumerate() { agents[0] .agent .send( network_id, peer.agent.endpoint_id(), ControlMessage::Ping { seq: index as u64 + 1, payload: format!("to-{index}").into_bytes(), }, ) .await .unwrap(); } let mut seen = HashSet::new(); while seen.len() < 3 { let (peer, seq, payload) = wait_event(&mut events, |event| match event { Event::MessageReceived { network, peer, message: ControlMessage::Pong { seq, payload }, } if *network == network_id => Some((*peer, *seq, payload.clone())), _ => None, }) .await; assert_eq!(payload, format!("to-{}", seq - 1).into_bytes()); seen.insert(peer); } assert_eq!(seen.len(), 3); for agent in agents { agent.agent.shutdown().await; } } #[tokio::test] async fn a_late_joiner_is_picked_up_by_the_existing_members() { let discovery = SharedMemoryDiscovery::new(); let (name, secret) = network("late-joiner"); let first = TestAgent::spawn(&discovery).await.unwrap(); let network_id = first.agent.join_network(&name, &secret).await.unwrap(); // Nobody else is there yet. That is "nobody found so far", not proof that // the network is empty, and the agent is ready regardless. let status = first.agent.network_status(network_id).await.unwrap(); assert!(status.peers.is_empty()); let second = TestAgent::spawn(&discovery).await.unwrap(); second.agent.join_network(&name, &secret).await.unwrap(); wait_for_peers(&first.agent, network_id, 1).await; wait_for_peers(&second.agent, network_id, 1).await; first.agent.shutdown().await; second.agent.shutdown().await; } #[tokio::test] async fn opaque_plugin_capabilities_cross_the_control_plane() { let discovery = SharedMemoryDiscovery::new(); let (name, secret) = network("capabilities"); // An explicitly test-only capability: nothing here advertises WireGuard. let plugin_a = Arc::new(TestCapabilityPlugin::new("test-ip", b"payload-a".to_vec())); let plugin_b = Arc::new(TestCapabilityPlugin::new("test-ip", b"payload-b".to_vec())); let a = TestAgent::spawn_with( { let plugin = Arc::clone(&plugin_a); move |cfg| cfg.with_plugin(plugin) }, &discovery, ) .await .unwrap(); let b = TestAgent::spawn_with( { let plugin = Arc::clone(&plugin_b); move |cfg| cfg.with_plugin(plugin) }, &discovery, ) .await .unwrap(); let network_id = a.agent.join_network(&name, &secret).await.unwrap(); b.agent.join_network(&name, &secret).await.unwrap(); wait_for_peers(&a.agent, network_id, 1).await; let observed = wait_until("plugin a sees b's capability", || { let plugin = Arc::clone(&plugin_a); async move { let seen = plugin.observed(); if seen.is_empty() { None } else { Some(seen) } } }) .await; let (seen_network, seen_peer, capability) = &observed[0]; assert_eq!(*seen_network, network_id); assert_eq!(*seen_peer, b.agent.endpoint_id()); assert_eq!(capability.protocol, "test-ip"); assert_eq!(capability.data, b"payload-b".to_vec()); assert!(capability.enabled); // The core carried the payload without interpreting it. let status = a.agent.network_status(network_id).await.unwrap(); assert_eq!(status.peers[0].capabilities[0].data, b"payload-b".to_vec()); a.agent.shutdown().await; b.agent.shutdown().await; }