Skip to main content

hive_mesh/
mesh.rs

1//! HiveMesh facade — unified entry point for the mesh networking library.
2//!
3//! Provides [`HiveMesh`] as the single entry point that composes transport,
4//! topology, routing, hierarchy, and (optionally) the HTTP/WS broker into a
5//! cohesive mesh networking stack.
6
7use crate::config::MeshConfig;
8use crate::hierarchy::HierarchyStrategy;
9use crate::routing::MeshRouter;
10use crate::transport::{MeshTransport, NodeId, TransportError, TransportManager};
11use std::fmt;
12use std::sync::{Arc, RwLock};
13use std::time::Instant;
14use tokio::sync::broadcast;
15
16// ─── Lifecycle state ─────────────────────────────────────────────────────────
17
18/// Lifecycle state of the mesh.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum MeshState {
21    /// Mesh created but not yet started.
22    Created,
23    /// Mesh is in the process of starting.
24    Starting,
25    /// Mesh is running and accepting connections.
26    Running,
27    /// Mesh is in the process of stopping.
28    Stopping,
29    /// Mesh has been stopped.
30    Stopped,
31}
32
33impl fmt::Display for MeshState {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        match self {
36            MeshState::Created => write!(f, "created"),
37            MeshState::Starting => write!(f, "starting"),
38            MeshState::Running => write!(f, "running"),
39            MeshState::Stopping => write!(f, "stopping"),
40            MeshState::Stopped => write!(f, "stopped"),
41        }
42    }
43}
44
45// ─── Error type ──────────────────────────────────────────────────────────────
46
47/// Unified error type for mesh operations.
48#[derive(Debug)]
49pub enum MeshError {
50    /// Operation requires the mesh to be running.
51    NotRunning,
52    /// Mesh is already running or starting.
53    AlreadyRunning,
54    /// Invalid configuration.
55    InvalidConfig(String),
56    /// Underlying transport error.
57    Transport(TransportError),
58    /// Catch-all for other errors.
59    Other(String),
60}
61
62impl fmt::Display for MeshError {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        match self {
65            MeshError::NotRunning => write!(f, "mesh is not running"),
66            MeshError::AlreadyRunning => write!(f, "mesh is already running"),
67            MeshError::InvalidConfig(msg) => write!(f, "invalid configuration: {}", msg),
68            MeshError::Transport(err) => write!(f, "transport error: {}", err),
69            MeshError::Other(msg) => write!(f, "{}", msg),
70        }
71    }
72}
73
74impl std::error::Error for MeshError {
75    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
76        match self {
77            MeshError::Transport(err) => Some(err),
78            _ => None,
79        }
80    }
81}
82
83impl From<TransportError> for MeshError {
84    fn from(err: TransportError) -> Self {
85        MeshError::Transport(err)
86    }
87}
88
89// ─── Events ──────────────────────────────────────────────────────────────────
90
91/// Mesh-wide events broadcast to subscribers.
92#[derive(Debug, Clone)]
93pub enum HiveMeshEvent {
94    /// Mesh lifecycle state changed.
95    StateChanged(MeshState),
96    /// A new peer joined the mesh.
97    PeerJoined(NodeId),
98    /// A peer left the mesh.
99    PeerLeft(NodeId),
100    /// Topology changed.
101    TopologyChanged(Box<crate::topology::TopologyEvent>),
102}
103
104// ─── Status snapshot ─────────────────────────────────────────────────────────
105
106/// Point-in-time snapshot of mesh status.
107#[derive(Debug, Clone)]
108pub struct MeshStatus {
109    /// Current lifecycle state.
110    pub state: MeshState,
111    /// Number of connected peers.
112    pub peer_count: usize,
113    /// This node's identifier.
114    pub node_id: String,
115    /// Time since the mesh was started.
116    pub uptime: std::time::Duration,
117}
118
119// ─── HiveMesh facade ────────────────────────────────────────────────────────
120
121const EVENT_CHANNEL_CAPACITY: usize = 256;
122
123/// Unified mesh facade composing all subsystems.
124///
125/// Create with [`HiveMesh::new`] for simple use or [`HiveMeshBuilder`] for
126/// advanced construction with pre-configured subsystems.
127pub struct HiveMesh {
128    config: MeshConfig,
129    node_id: String,
130    state: RwLock<MeshState>,
131    transport: Option<Arc<dyn MeshTransport>>,
132    transport_manager: Option<TransportManager>,
133    hierarchy: Option<Arc<dyn HierarchyStrategy>>,
134    router: Option<MeshRouter>,
135    // ── QoS ──
136    bandwidth: Option<crate::qos::BandwidthAllocation>,
137    preemption: Option<crate::qos::PreemptionController>,
138    // ── Security ──
139    device_keypair: Option<crate::security::DeviceKeypair>,
140    formation_key: Option<crate::security::FormationKey>,
141    // ── Discovery ──
142    discovery: RwLock<Option<Box<dyn crate::discovery::DiscoveryStrategy>>>,
143    // ── Beacon ──
144    beacon_broadcaster: Option<crate::beacon::BeaconBroadcaster>,
145    beacon_observer: Option<Arc<crate::beacon::BeaconObserver>>,
146    beacon_janitor: Option<crate::beacon::BeaconJanitor>,
147    // ── Topology ──
148    topology_manager: Option<crate::topology::TopologyManager>,
149    event_tx: broadcast::Sender<HiveMeshEvent>,
150    #[cfg(feature = "broker")]
151    broker_event_tx: broadcast::Sender<crate::broker::state::MeshEvent>,
152    started_at: RwLock<Option<Instant>>,
153}
154
155impl HiveMesh {
156    /// Create a new HiveMesh with the given configuration.
157    ///
158    /// If `config.node_id` is `None`, a UUID v4 is generated automatically.
159    pub fn new(config: MeshConfig) -> Self {
160        let node_id = config
161            .node_id
162            .clone()
163            .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
164        let (event_tx, _) = broadcast::channel(EVENT_CHANNEL_CAPACITY);
165        #[cfg(feature = "broker")]
166        let (broker_event_tx, _) = broadcast::channel(EVENT_CHANNEL_CAPACITY);
167        Self {
168            config,
169            node_id,
170            state: RwLock::new(MeshState::Created),
171            transport: None,
172            transport_manager: None,
173            hierarchy: None,
174            router: None,
175            bandwidth: None,
176            preemption: None,
177            device_keypair: None,
178            formation_key: None,
179            discovery: RwLock::new(None),
180            beacon_broadcaster: None,
181            beacon_observer: None,
182            beacon_janitor: None,
183            topology_manager: None,
184            event_tx,
185            #[cfg(feature = "broker")]
186            broker_event_tx,
187            started_at: RwLock::new(None),
188        }
189    }
190
191    /// Start the mesh (Created/Stopped → Starting → Running).
192    pub fn start(&self) -> Result<(), MeshError> {
193        let mut state = self.state.write().unwrap();
194        match *state {
195            MeshState::Created | MeshState::Stopped => {}
196            MeshState::Running | MeshState::Starting | MeshState::Stopping => {
197                return Err(MeshError::AlreadyRunning);
198            }
199        }
200
201        *state = MeshState::Starting;
202        let _ = self
203            .event_tx
204            .send(HiveMeshEvent::StateChanged(MeshState::Starting));
205
206        *state = MeshState::Running;
207        *self.started_at.write().unwrap() = Some(Instant::now());
208        let _ = self
209            .event_tx
210            .send(HiveMeshEvent::StateChanged(MeshState::Running));
211
212        #[cfg(feature = "broker")]
213        self.emit_broker_event(crate::broker::state::MeshEvent::TopologyChanged {
214            new_role: "standalone".to_string(),
215            peer_count: 0,
216        });
217
218        Ok(())
219    }
220
221    /// Stop the mesh (Running → Stopping → Stopped).
222    pub fn stop(&self) -> Result<(), MeshError> {
223        let mut state = self.state.write().unwrap();
224        match *state {
225            MeshState::Running => {}
226            _ => return Err(MeshError::NotRunning),
227        }
228
229        *state = MeshState::Stopping;
230        let _ = self
231            .event_tx
232            .send(HiveMeshEvent::StateChanged(MeshState::Stopping));
233
234        *state = MeshState::Stopped;
235        let _ = self
236            .event_tx
237            .send(HiveMeshEvent::StateChanged(MeshState::Stopped));
238
239        #[cfg(feature = "broker")]
240        self.emit_broker_event(crate::broker::state::MeshEvent::TopologyChanged {
241            new_role: "stopped".to_string(),
242            peer_count: 0,
243        });
244
245        Ok(())
246    }
247
248    /// Get the current lifecycle state.
249    pub fn state(&self) -> MeshState {
250        *self.state.read().unwrap()
251    }
252
253    /// Get a point-in-time status snapshot.
254    pub fn status(&self) -> MeshStatus {
255        let state = *self.state.read().unwrap();
256        let uptime = self
257            .started_at
258            .read()
259            .unwrap()
260            .map(|t| t.elapsed())
261            .unwrap_or_default();
262        let peer_count = self.transport.as_ref().map(|t| t.peer_count()).unwrap_or(0);
263
264        MeshStatus {
265            state,
266            peer_count,
267            node_id: self.node_id.clone(),
268            uptime,
269        }
270    }
271
272    /// Get the mesh configuration.
273    pub fn config(&self) -> &MeshConfig {
274        &self.config
275    }
276
277    /// Get the node ID.
278    pub fn node_id(&self) -> &str {
279        &self.node_id
280    }
281
282    /// Subscribe to mesh-wide events.
283    pub fn subscribe_events(&self) -> broadcast::Receiver<HiveMeshEvent> {
284        self.event_tx.subscribe()
285    }
286
287    /// Set the transport layer.
288    pub fn set_transport(&mut self, transport: Arc<dyn MeshTransport>) {
289        self.transport = Some(transport);
290    }
291
292    /// Set the multi-transport manager for PACE-based transport selection.
293    pub fn set_transport_manager(&mut self, tm: TransportManager) {
294        self.transport_manager = Some(tm);
295    }
296
297    /// Get a reference to the transport manager, if set.
298    pub fn transport_manager(&self) -> Option<&TransportManager> {
299        self.transport_manager.as_ref()
300    }
301
302    /// Set the hierarchy strategy.
303    pub fn set_hierarchy(&mut self, hierarchy: Arc<dyn HierarchyStrategy>) {
304        self.hierarchy = Some(hierarchy);
305    }
306
307    /// Get a reference to the transport, if set.
308    pub fn transport(&self) -> Option<&Arc<dyn MeshTransport>> {
309        self.transport.as_ref()
310    }
311
312    /// Get a reference to the hierarchy strategy, if set.
313    pub fn hierarchy(&self) -> Option<&Arc<dyn HierarchyStrategy>> {
314        self.hierarchy.as_ref()
315    }
316
317    /// Get a reference to the router, if set.
318    pub fn router(&self) -> Option<&MeshRouter> {
319        self.router.as_ref()
320    }
321
322    // ── QoS policies ────────────────────────────────────────────
323
324    /// Set the bandwidth allocation policy.
325    pub fn set_bandwidth(&mut self, bw: crate::qos::BandwidthAllocation) {
326        self.bandwidth = Some(bw);
327    }
328
329    /// Get a reference to the bandwidth allocation, if set.
330    pub fn bandwidth(&self) -> Option<&crate::qos::BandwidthAllocation> {
331        self.bandwidth.as_ref()
332    }
333
334    /// Set the preemption controller.
335    pub fn set_preemption(&mut self, pc: crate::qos::PreemptionController) {
336        self.preemption = Some(pc);
337    }
338
339    /// Get a reference to the preemption controller, if set.
340    pub fn preemption(&self) -> Option<&crate::qos::PreemptionController> {
341        self.preemption.as_ref()
342    }
343
344    // ── Security primitives ─────────────────────────────────────
345
346    /// Set the device keypair (Ed25519).
347    pub fn set_device_keypair(&mut self, kp: crate::security::DeviceKeypair) {
348        self.device_keypair = Some(kp);
349    }
350
351    /// Get a reference to the device keypair, if set.
352    pub fn device_keypair(&self) -> Option<&crate::security::DeviceKeypair> {
353        self.device_keypair.as_ref()
354    }
355
356    /// Set the formation key (HMAC-SHA256).
357    pub fn set_formation_key(&mut self, fk: crate::security::FormationKey) {
358        self.formation_key = Some(fk);
359    }
360
361    /// Get a reference to the formation key, if set.
362    pub fn formation_key(&self) -> Option<&crate::security::FormationKey> {
363        self.formation_key.as_ref()
364    }
365
366    // ── Discovery ───────────────────────────────────────────────
367
368    /// Set the discovery strategy.
369    ///
370    /// Takes `&self` (not `&mut self`) because the field uses interior
371    /// mutability (`RwLock`) — `DiscoveryStrategy::start()` requires
372    /// `&mut self`.
373    pub fn set_discovery(&self, strategy: Box<dyn crate::discovery::DiscoveryStrategy>) {
374        *self.discovery.write().unwrap() = Some(strategy);
375    }
376
377    /// Get a reference to the discovery RwLock.
378    pub fn discovery(&self) -> &RwLock<Option<Box<dyn crate::discovery::DiscoveryStrategy>>> {
379        &self.discovery
380    }
381
382    // ── Beacon ──────────────────────────────────────────────────
383
384    /// Set the beacon broadcaster.
385    pub fn set_beacon_broadcaster(&mut self, bb: crate::beacon::BeaconBroadcaster) {
386        self.beacon_broadcaster = Some(bb);
387    }
388
389    /// Get a reference to the beacon broadcaster, if set.
390    pub fn beacon_broadcaster(&self) -> Option<&crate::beacon::BeaconBroadcaster> {
391        self.beacon_broadcaster.as_ref()
392    }
393
394    /// Set the beacon observer (Arc-wrapped for sharing with TopologyBuilder).
395    pub fn set_beacon_observer(&mut self, bo: Arc<crate::beacon::BeaconObserver>) {
396        self.beacon_observer = Some(bo);
397    }
398
399    /// Get a reference to the beacon observer, if set.
400    pub fn beacon_observer(&self) -> Option<&Arc<crate::beacon::BeaconObserver>> {
401        self.beacon_observer.as_ref()
402    }
403
404    /// Set the beacon janitor.
405    pub fn set_beacon_janitor(&mut self, bj: crate::beacon::BeaconJanitor) {
406        self.beacon_janitor = Some(bj);
407    }
408
409    /// Get a reference to the beacon janitor, if set.
410    pub fn beacon_janitor(&self) -> Option<&crate::beacon::BeaconJanitor> {
411        self.beacon_janitor.as_ref()
412    }
413
414    // ── Topology ────────────────────────────────────────────────
415
416    /// Set the topology manager.
417    pub fn set_topology_manager(&mut self, tm: crate::topology::TopologyManager) {
418        self.topology_manager = Some(tm);
419    }
420
421    /// Get a reference to the topology manager, if set.
422    pub fn topology_manager(&self) -> Option<&crate::topology::TopologyManager> {
423        self.topology_manager.as_ref()
424    }
425
426    /// Emit a broker event for WebSocket subscribers.
427    #[cfg(feature = "broker")]
428    pub fn emit_mesh_event(&self, event: crate::broker::state::MeshEvent) {
429        let _ = self.broker_event_tx.send(event);
430    }
431
432    #[cfg(feature = "broker")]
433    fn emit_broker_event(&self, event: crate::broker::state::MeshEvent) {
434        let _ = self.broker_event_tx.send(event);
435    }
436}
437
438// ─── Feature-gated MeshBrokerState impl ──────────────────────────────────────
439
440#[cfg(feature = "broker")]
441#[async_trait::async_trait]
442impl crate::broker::state::MeshBrokerState for HiveMesh {
443    fn node_info(&self) -> crate::broker::state::MeshNodeInfo {
444        let uptime = self
445            .started_at
446            .read()
447            .unwrap()
448            .map(|t| t.elapsed().as_secs())
449            .unwrap_or(0);
450        crate::broker::state::MeshNodeInfo {
451            node_id: self.node_id.clone(),
452            uptime_secs: uptime,
453            version: env!("CARGO_PKG_VERSION").to_string(),
454        }
455    }
456
457    async fn list_peers(&self) -> Vec<crate::broker::state::PeerSummary> {
458        let Some(transport) = &self.transport else {
459            return vec![];
460        };
461        transport
462            .connected_peers()
463            .into_iter()
464            .map(|peer_id| {
465                let health = transport.get_peer_health(&peer_id);
466                crate::broker::state::PeerSummary {
467                    id: peer_id.to_string(),
468                    connected: true,
469                    state: health
470                        .as_ref()
471                        .map(|h| h.state.to_string())
472                        .unwrap_or_else(|| "unknown".to_string()),
473                    rtt_ms: health.map(|h| h.rtt_ms as u64),
474                }
475            })
476            .collect()
477    }
478
479    async fn get_peer(&self, id: &str) -> Option<crate::broker::state::PeerSummary> {
480        let transport = self.transport.as_ref()?;
481        let node_id = NodeId::new(id.to_string());
482        if transport.is_connected(&node_id) {
483            let health = transport.get_peer_health(&node_id);
484            Some(crate::broker::state::PeerSummary {
485                id: id.to_string(),
486                connected: true,
487                state: health
488                    .as_ref()
489                    .map(|h| h.state.to_string())
490                    .unwrap_or_else(|| "unknown".to_string()),
491                rtt_ms: health.map(|h| h.rtt_ms as u64),
492            })
493        } else {
494            None
495        }
496    }
497
498    fn topology(&self) -> crate::broker::state::TopologySummary {
499        let peer_count = self.transport.as_ref().map(|t| t.peer_count()).unwrap_or(0);
500        crate::broker::state::TopologySummary {
501            peer_count,
502            role: "standalone".to_string(),
503            hierarchy_level: 0,
504        }
505    }
506
507    fn subscribe_events(&self) -> broadcast::Receiver<crate::broker::state::MeshEvent> {
508        self.broker_event_tx.subscribe()
509    }
510}
511
512// ─── Builder ─────────────────────────────────────────────────────────────────
513
514/// Builder for constructing a [`HiveMesh`] with pre-configured subsystems.
515pub struct HiveMeshBuilder {
516    config: MeshConfig,
517    transport: Option<Arc<dyn MeshTransport>>,
518    transport_manager: Option<TransportManager>,
519    hierarchy: Option<Arc<dyn HierarchyStrategy>>,
520    router: Option<MeshRouter>,
521    bandwidth: Option<crate::qos::BandwidthAllocation>,
522    preemption: Option<crate::qos::PreemptionController>,
523    device_keypair: Option<crate::security::DeviceKeypair>,
524    formation_key: Option<crate::security::FormationKey>,
525    discovery: Option<Box<dyn crate::discovery::DiscoveryStrategy>>,
526    beacon_broadcaster: Option<crate::beacon::BeaconBroadcaster>,
527    beacon_observer: Option<Arc<crate::beacon::BeaconObserver>>,
528    beacon_janitor: Option<crate::beacon::BeaconJanitor>,
529    topology_manager: Option<crate::topology::TopologyManager>,
530}
531
532impl HiveMeshBuilder {
533    /// Create a new builder with the given configuration.
534    pub fn new(config: MeshConfig) -> Self {
535        Self {
536            config,
537            transport: None,
538            transport_manager: None,
539            hierarchy: None,
540            router: None,
541            bandwidth: None,
542            preemption: None,
543            device_keypair: None,
544            formation_key: None,
545            discovery: None,
546            beacon_broadcaster: None,
547            beacon_observer: None,
548            beacon_janitor: None,
549            topology_manager: None,
550        }
551    }
552
553    /// Set a single transport layer.
554    pub fn with_transport(mut self, transport: Arc<dyn MeshTransport>) -> Self {
555        self.transport = Some(transport);
556        self
557    }
558
559    /// Set the multi-transport manager for PACE-based transport selection.
560    pub fn with_transport_manager(mut self, tm: TransportManager) -> Self {
561        self.transport_manager = Some(tm);
562        self
563    }
564
565    /// Set the hierarchy strategy.
566    pub fn with_hierarchy(mut self, hierarchy: Arc<dyn HierarchyStrategy>) -> Self {
567        self.hierarchy = Some(hierarchy);
568        self
569    }
570
571    /// Set the router.
572    pub fn with_router(mut self, router: MeshRouter) -> Self {
573        self.router = Some(router);
574        self
575    }
576
577    /// Set the bandwidth allocation policy.
578    pub fn with_bandwidth(mut self, bw: crate::qos::BandwidthAllocation) -> Self {
579        self.bandwidth = Some(bw);
580        self
581    }
582
583    /// Set the preemption controller.
584    pub fn with_preemption(mut self, pc: crate::qos::PreemptionController) -> Self {
585        self.preemption = Some(pc);
586        self
587    }
588
589    /// Set the device keypair.
590    pub fn with_device_keypair(mut self, kp: crate::security::DeviceKeypair) -> Self {
591        self.device_keypair = Some(kp);
592        self
593    }
594
595    /// Derive a deterministic device keypair from a seed and context.
596    ///
597    /// Convenience wrapper around [`crate::security::DeviceKeypair::from_seed`].
598    pub fn with_device_keypair_from_seed(
599        mut self,
600        seed: &[u8],
601        context: &str,
602    ) -> Result<Self, MeshError> {
603        let kp = crate::security::DeviceKeypair::from_seed(seed, context)
604            .map_err(|e| MeshError::InvalidConfig(e.to_string()))?;
605        self.device_keypair = Some(kp);
606        Ok(self)
607    }
608
609    /// Set the formation key.
610    pub fn with_formation_key(mut self, fk: crate::security::FormationKey) -> Self {
611        self.formation_key = Some(fk);
612        self
613    }
614
615    /// Set the discovery strategy.
616    pub fn with_discovery(
617        mut self,
618        strategy: Box<dyn crate::discovery::DiscoveryStrategy>,
619    ) -> Self {
620        self.discovery = Some(strategy);
621        self
622    }
623
624    /// Set the beacon broadcaster.
625    pub fn with_beacon_broadcaster(mut self, bb: crate::beacon::BeaconBroadcaster) -> Self {
626        self.beacon_broadcaster = Some(bb);
627        self
628    }
629
630    /// Set the beacon observer.
631    pub fn with_beacon_observer(mut self, bo: Arc<crate::beacon::BeaconObserver>) -> Self {
632        self.beacon_observer = Some(bo);
633        self
634    }
635
636    /// Set the beacon janitor.
637    pub fn with_beacon_janitor(mut self, bj: crate::beacon::BeaconJanitor) -> Self {
638        self.beacon_janitor = Some(bj);
639        self
640    }
641
642    /// Set the topology manager.
643    pub fn with_topology_manager(mut self, tm: crate::topology::TopologyManager) -> Self {
644        self.topology_manager = Some(tm);
645        self
646    }
647
648    /// Build the [`HiveMesh`] instance.
649    pub fn build(self) -> HiveMesh {
650        let node_id = self
651            .config
652            .node_id
653            .clone()
654            .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
655        let (event_tx, _) = broadcast::channel(EVENT_CHANNEL_CAPACITY);
656        #[cfg(feature = "broker")]
657        let (broker_event_tx, _) = broadcast::channel(EVENT_CHANNEL_CAPACITY);
658
659        HiveMesh {
660            config: self.config,
661            node_id,
662            state: RwLock::new(MeshState::Created),
663            transport: self.transport,
664            transport_manager: self.transport_manager,
665            hierarchy: self.hierarchy,
666            router: self.router,
667            bandwidth: self.bandwidth,
668            preemption: self.preemption,
669            device_keypair: self.device_keypair,
670            formation_key: self.formation_key,
671            discovery: RwLock::new(self.discovery),
672            beacon_broadcaster: self.beacon_broadcaster,
673            beacon_observer: self.beacon_observer,
674            beacon_janitor: self.beacon_janitor,
675            topology_manager: self.topology_manager,
676            event_tx,
677            #[cfg(feature = "broker")]
678            broker_event_tx,
679            started_at: RwLock::new(None),
680        }
681    }
682}
683
684// ─── Tests ───────────────────────────────────────────────────────────────────
685
686#[cfg(test)]
687mod tests {
688    use super::*;
689    use crate::config::MeshDiscoveryConfig;
690    use crate::transport::PeerEventReceiver;
691    use async_trait::async_trait;
692    use std::time::Duration;
693
694    // ── Mock transport for testing ───────────────────────────────
695
696    struct MockTransport {
697        peers: Vec<NodeId>,
698    }
699
700    impl MockTransport {
701        fn new(peers: Vec<NodeId>) -> Self {
702            Self { peers }
703        }
704
705        fn empty() -> Self {
706            Self { peers: vec![] }
707        }
708    }
709
710    #[async_trait]
711    impl MeshTransport for MockTransport {
712        async fn start(&self) -> crate::transport::Result<()> {
713            Ok(())
714        }
715        async fn stop(&self) -> crate::transport::Result<()> {
716            Ok(())
717        }
718        async fn connect(
719            &self,
720            _peer_id: &NodeId,
721        ) -> crate::transport::Result<Box<dyn crate::transport::MeshConnection>> {
722            Err(TransportError::NotStarted)
723        }
724        async fn disconnect(&self, _peer_id: &NodeId) -> crate::transport::Result<()> {
725            Ok(())
726        }
727        fn get_connection(
728            &self,
729            _peer_id: &NodeId,
730        ) -> Option<Box<dyn crate::transport::MeshConnection>> {
731            None
732        }
733        fn peer_count(&self) -> usize {
734            self.peers.len()
735        }
736        fn connected_peers(&self) -> Vec<NodeId> {
737            self.peers.clone()
738        }
739        fn subscribe_peer_events(&self) -> PeerEventReceiver {
740            let (_tx, rx) = tokio::sync::mpsc::channel(1);
741            rx
742        }
743    }
744
745    // ── HiveMesh::new ────────────────────────────────────────────
746
747    #[test]
748    fn test_new_with_default_config() {
749        let mesh = HiveMesh::new(MeshConfig::default());
750        assert_eq!(mesh.state(), MeshState::Created);
751        assert!(!mesh.node_id().is_empty());
752    }
753
754    #[test]
755    fn test_new_with_explicit_node_id() {
756        let cfg = MeshConfig {
757            node_id: Some("my-node".to_string()),
758            ..Default::default()
759        };
760        let mesh = HiveMesh::new(cfg);
761        assert_eq!(mesh.node_id(), "my-node");
762    }
763
764    #[test]
765    fn test_new_auto_generates_uuid_node_id() {
766        let mesh = HiveMesh::new(MeshConfig::default());
767        // UUID v4 format: 8-4-4-4-12 hex digits
768        assert_eq!(mesh.node_id().len(), 36);
769        assert_eq!(mesh.node_id().chars().filter(|&c| c == '-').count(), 4);
770    }
771
772    // ── Lifecycle: start / stop ──────────────────────────────────
773
774    #[test]
775    fn test_start_transitions_to_running() {
776        let mesh = HiveMesh::new(MeshConfig::default());
777        assert!(mesh.start().is_ok());
778        assert_eq!(mesh.state(), MeshState::Running);
779    }
780
781    #[test]
782    fn test_start_when_already_running_returns_error() {
783        let mesh = HiveMesh::new(MeshConfig::default());
784        mesh.start().unwrap();
785        let err = mesh.start().unwrap_err();
786        assert!(matches!(err, MeshError::AlreadyRunning));
787    }
788
789    #[test]
790    fn test_stop_transitions_to_stopped() {
791        let mesh = HiveMesh::new(MeshConfig::default());
792        mesh.start().unwrap();
793        assert!(mesh.stop().is_ok());
794        assert_eq!(mesh.state(), MeshState::Stopped);
795    }
796
797    #[test]
798    fn test_stop_when_not_running_returns_error() {
799        let mesh = HiveMesh::new(MeshConfig::default());
800        let err = mesh.stop().unwrap_err();
801        assert!(matches!(err, MeshError::NotRunning));
802    }
803
804    #[test]
805    fn test_restart_after_stop() {
806        let mesh = HiveMesh::new(MeshConfig::default());
807        mesh.start().unwrap();
808        mesh.stop().unwrap();
809        assert!(mesh.start().is_ok());
810        assert_eq!(mesh.state(), MeshState::Running);
811    }
812
813    #[test]
814    fn test_stop_when_created_returns_error() {
815        let mesh = HiveMesh::new(MeshConfig::default());
816        assert!(matches!(mesh.stop().unwrap_err(), MeshError::NotRunning));
817    }
818
819    #[test]
820    fn test_stop_when_already_stopped_returns_error() {
821        let mesh = HiveMesh::new(MeshConfig::default());
822        mesh.start().unwrap();
823        mesh.stop().unwrap();
824        assert!(matches!(mesh.stop().unwrap_err(), MeshError::NotRunning));
825    }
826
827    // ── Status ───────────────────────────────────────────────────
828
829    #[test]
830    fn test_status_before_start() {
831        let cfg = MeshConfig {
832            node_id: Some("status-node".to_string()),
833            ..Default::default()
834        };
835        let mesh = HiveMesh::new(cfg);
836        let status = mesh.status();
837        assert_eq!(status.state, MeshState::Created);
838        assert_eq!(status.peer_count, 0);
839        assert_eq!(status.node_id, "status-node");
840        assert_eq!(status.uptime, Duration::ZERO);
841    }
842
843    #[test]
844    fn test_status_while_running() {
845        let mesh = HiveMesh::new(MeshConfig {
846            node_id: Some("running-node".to_string()),
847            ..Default::default()
848        });
849        mesh.start().unwrap();
850        let status = mesh.status();
851        assert_eq!(status.state, MeshState::Running);
852        assert_eq!(status.node_id, "running-node");
853        // Uptime should be non-zero (or at least zero on a very fast machine)
854        assert!(status.uptime <= Duration::from_secs(1));
855    }
856
857    #[test]
858    fn test_status_peer_count_with_transport() {
859        let peers = vec![NodeId::new("p1".into()), NodeId::new("p2".into())];
860        let mut mesh = HiveMesh::new(MeshConfig::default());
861        mesh.set_transport(Arc::new(MockTransport::new(peers)));
862        let status = mesh.status();
863        assert_eq!(status.peer_count, 2);
864    }
865
866    // ── Config accessor ──────────────────────────────────────────
867
868    #[test]
869    fn test_config_accessor() {
870        let cfg = MeshConfig {
871            node_id: Some("cfg-test".to_string()),
872            discovery: MeshDiscoveryConfig {
873                mdns_enabled: false,
874                ..Default::default()
875            },
876            ..Default::default()
877        };
878        let mesh = HiveMesh::new(cfg);
879        assert_eq!(mesh.config().node_id.as_deref(), Some("cfg-test"));
880        assert!(!mesh.config().discovery.mdns_enabled);
881    }
882
883    // ── Event subscription ───────────────────────────────────────
884
885    #[test]
886    fn test_subscribe_events_receives_state_changes() {
887        let mesh = HiveMesh::new(MeshConfig::default());
888        let mut rx = mesh.subscribe_events();
889
890        mesh.start().unwrap();
891
892        // Should receive Starting then Running
893        let evt1 = rx.try_recv().unwrap();
894        assert!(matches!(
895            evt1,
896            HiveMeshEvent::StateChanged(MeshState::Starting)
897        ));
898        let evt2 = rx.try_recv().unwrap();
899        assert!(matches!(
900            evt2,
901            HiveMeshEvent::StateChanged(MeshState::Running)
902        ));
903    }
904
905    #[test]
906    fn test_subscribe_events_receives_stop_events() {
907        let mesh = HiveMesh::new(MeshConfig::default());
908        let mut rx = mesh.subscribe_events();
909
910        mesh.start().unwrap();
911        // Drain start events
912        let _ = rx.try_recv();
913        let _ = rx.try_recv();
914
915        mesh.stop().unwrap();
916
917        let evt1 = rx.try_recv().unwrap();
918        assert!(matches!(
919            evt1,
920            HiveMeshEvent::StateChanged(MeshState::Stopping)
921        ));
922        let evt2 = rx.try_recv().unwrap();
923        assert!(matches!(
924            evt2,
925            HiveMeshEvent::StateChanged(MeshState::Stopped)
926        ));
927    }
928
929    #[test]
930    fn test_multiple_subscribers() {
931        let mesh = HiveMesh::new(MeshConfig::default());
932        let mut rx1 = mesh.subscribe_events();
933        let mut rx2 = mesh.subscribe_events();
934
935        mesh.start().unwrap();
936
937        // Both receivers should get events
938        assert!(rx1.try_recv().is_ok());
939        assert!(rx2.try_recv().is_ok());
940    }
941
942    // ── set_transport / set_hierarchy ────────────────────────────
943
944    #[test]
945    fn test_set_transport() {
946        let mut mesh = HiveMesh::new(MeshConfig::default());
947        assert!(mesh.transport().is_none());
948
949        mesh.set_transport(Arc::new(MockTransport::empty()));
950        assert!(mesh.transport().is_some());
951    }
952
953    #[test]
954    fn test_set_hierarchy() {
955        use crate::beacon::HierarchyLevel;
956        use crate::hierarchy::{NodeRole, StaticHierarchyStrategy};
957
958        let mut mesh = HiveMesh::new(MeshConfig::default());
959        assert!(mesh.hierarchy().is_none());
960
961        let strategy = StaticHierarchyStrategy {
962            assigned_level: HierarchyLevel::Platoon,
963            assigned_role: NodeRole::Leader,
964        };
965        mesh.set_hierarchy(Arc::new(strategy));
966        assert!(mesh.hierarchy().is_some());
967    }
968
969    #[test]
970    fn test_router_initially_none() {
971        let mesh = HiveMesh::new(MeshConfig::default());
972        assert!(mesh.router().is_none());
973    }
974
975    // ── MeshState ────────────────────────────────────────────────
976
977    #[test]
978    fn test_mesh_state_display() {
979        assert_eq!(MeshState::Created.to_string(), "created");
980        assert_eq!(MeshState::Starting.to_string(), "starting");
981        assert_eq!(MeshState::Running.to_string(), "running");
982        assert_eq!(MeshState::Stopping.to_string(), "stopping");
983        assert_eq!(MeshState::Stopped.to_string(), "stopped");
984    }
985
986    #[test]
987    fn test_mesh_state_equality() {
988        assert_eq!(MeshState::Created, MeshState::Created);
989        assert_ne!(MeshState::Created, MeshState::Running);
990    }
991
992    #[test]
993    fn test_mesh_state_clone_copy() {
994        let s = MeshState::Running;
995        let copied = s;
996        // Verify Copy semantics: original is still usable after copy
997        assert_eq!(s, copied);
998    }
999
1000    #[test]
1001    fn test_mesh_state_debug() {
1002        let debug = format!("{:?}", MeshState::Running);
1003        assert!(debug.contains("Running"));
1004    }
1005
1006    // ── MeshError ────────────────────────────────────────────────
1007
1008    #[test]
1009    fn test_mesh_error_display_not_running() {
1010        let err = MeshError::NotRunning;
1011        assert_eq!(err.to_string(), "mesh is not running");
1012    }
1013
1014    #[test]
1015    fn test_mesh_error_display_already_running() {
1016        let err = MeshError::AlreadyRunning;
1017        assert_eq!(err.to_string(), "mesh is already running");
1018    }
1019
1020    #[test]
1021    fn test_mesh_error_display_invalid_config() {
1022        let err = MeshError::InvalidConfig("bad value".to_string());
1023        assert_eq!(err.to_string(), "invalid configuration: bad value");
1024    }
1025
1026    #[test]
1027    fn test_mesh_error_display_transport() {
1028        let terr = TransportError::NotStarted;
1029        let err = MeshError::Transport(terr);
1030        assert!(err.to_string().contains("Transport not started"));
1031    }
1032
1033    #[test]
1034    fn test_mesh_error_display_other() {
1035        let err = MeshError::Other("something went wrong".to_string());
1036        assert_eq!(err.to_string(), "something went wrong");
1037    }
1038
1039    #[test]
1040    fn test_mesh_error_source_transport() {
1041        use std::error::Error;
1042        let terr = TransportError::ConnectionFailed("timeout".into());
1043        let err = MeshError::Transport(terr);
1044        assert!(err.source().is_some());
1045    }
1046
1047    #[test]
1048    fn test_mesh_error_source_none_for_others() {
1049        use std::error::Error;
1050        assert!(MeshError::NotRunning.source().is_none());
1051        assert!(MeshError::AlreadyRunning.source().is_none());
1052        assert!(MeshError::InvalidConfig("x".into()).source().is_none());
1053        assert!(MeshError::Other("x".into()).source().is_none());
1054    }
1055
1056    #[test]
1057    fn test_mesh_error_from_transport_error() {
1058        let terr = TransportError::NotStarted;
1059        let err: MeshError = terr.into();
1060        assert!(matches!(err, MeshError::Transport(_)));
1061    }
1062
1063    #[test]
1064    fn test_mesh_error_debug() {
1065        let err = MeshError::NotRunning;
1066        let debug = format!("{:?}", err);
1067        assert!(debug.contains("NotRunning"));
1068    }
1069
1070    // ── HiveMeshEvent ────────────────────────────────────────────
1071
1072    #[test]
1073    fn test_event_state_changed() {
1074        let evt = HiveMeshEvent::StateChanged(MeshState::Running);
1075        let debug = format!("{:?}", evt);
1076        assert!(debug.contains("Running"));
1077    }
1078
1079    #[test]
1080    fn test_event_peer_joined() {
1081        let evt = HiveMeshEvent::PeerJoined(NodeId::new("peer-1".into()));
1082        let cloned = evt.clone();
1083        let debug = format!("{:?}", cloned);
1084        assert!(debug.contains("peer-1"));
1085    }
1086
1087    #[test]
1088    fn test_event_peer_left() {
1089        let evt = HiveMeshEvent::PeerLeft(NodeId::new("peer-2".into()));
1090        let cloned = evt.clone();
1091        let debug = format!("{:?}", cloned);
1092        assert!(debug.contains("peer-2"));
1093    }
1094
1095    #[test]
1096    fn test_event_topology_changed() {
1097        let topo_evt = crate::topology::TopologyEvent::PeerLost {
1098            lost_peer_id: "gone".to_string(),
1099        };
1100        let evt = HiveMeshEvent::TopologyChanged(Box::new(topo_evt));
1101        let cloned = evt.clone();
1102        let debug = format!("{:?}", cloned);
1103        assert!(debug.contains("gone"));
1104    }
1105
1106    // ── MeshStatus ───────────────────────────────────────────────
1107
1108    #[test]
1109    fn test_mesh_status_debug() {
1110        let status = MeshStatus {
1111            state: MeshState::Running,
1112            peer_count: 5,
1113            node_id: "n1".to_string(),
1114            uptime: Duration::from_secs(120),
1115        };
1116        let debug = format!("{:?}", status);
1117        assert!(debug.contains("Running"));
1118        assert!(debug.contains("n1"));
1119    }
1120
1121    #[test]
1122    fn test_mesh_status_clone() {
1123        let status = MeshStatus {
1124            state: MeshState::Stopped,
1125            peer_count: 0,
1126            node_id: "n2".to_string(),
1127            uptime: Duration::ZERO,
1128        };
1129        let cloned = status.clone();
1130        assert_eq!(cloned.state, MeshState::Stopped);
1131        assert_eq!(cloned.node_id, "n2");
1132    }
1133
1134    // ── HiveMeshBuilder ──────────────────────────────────────────
1135
1136    #[test]
1137    fn test_builder_minimal() {
1138        let mesh = HiveMeshBuilder::new(MeshConfig::default()).build();
1139        assert_eq!(mesh.state(), MeshState::Created);
1140        assert!(mesh.transport().is_none());
1141        assert!(mesh.hierarchy().is_none());
1142        assert!(mesh.router().is_none());
1143    }
1144
1145    #[test]
1146    fn test_builder_with_node_id() {
1147        let cfg = MeshConfig {
1148            node_id: Some("builder-node".to_string()),
1149            ..Default::default()
1150        };
1151        let mesh = HiveMeshBuilder::new(cfg).build();
1152        assert_eq!(mesh.node_id(), "builder-node");
1153    }
1154
1155    #[test]
1156    fn test_builder_with_transport() {
1157        let mesh = HiveMeshBuilder::new(MeshConfig::default())
1158            .with_transport(Arc::new(MockTransport::empty()))
1159            .build();
1160        assert!(mesh.transport().is_some());
1161    }
1162
1163    #[test]
1164    fn test_builder_with_hierarchy() {
1165        use crate::beacon::HierarchyLevel;
1166        use crate::hierarchy::{NodeRole, StaticHierarchyStrategy};
1167
1168        let strategy = StaticHierarchyStrategy {
1169            assigned_level: HierarchyLevel::Squad,
1170            assigned_role: NodeRole::Member,
1171        };
1172        let mesh = HiveMeshBuilder::new(MeshConfig::default())
1173            .with_hierarchy(Arc::new(strategy))
1174            .build();
1175        assert!(mesh.hierarchy().is_some());
1176    }
1177
1178    #[test]
1179    fn test_builder_with_router() {
1180        let router = MeshRouter::with_node_id("test");
1181        let mesh = HiveMeshBuilder::new(MeshConfig::default())
1182            .with_router(router)
1183            .build();
1184        assert!(mesh.router().is_some());
1185    }
1186
1187    #[test]
1188    fn test_builder_all_subsystems() {
1189        use crate::beacon::HierarchyLevel;
1190        use crate::hierarchy::{NodeRole, StaticHierarchyStrategy};
1191
1192        let strategy = StaticHierarchyStrategy {
1193            assigned_level: HierarchyLevel::Platoon,
1194            assigned_role: NodeRole::Leader,
1195        };
1196        let peers = vec![NodeId::new("p1".into())];
1197        let router = MeshRouter::with_node_id("full");
1198
1199        let mesh = HiveMeshBuilder::new(MeshConfig {
1200            node_id: Some("full-node".to_string()),
1201            ..Default::default()
1202        })
1203        .with_transport(Arc::new(MockTransport::new(peers)))
1204        .with_hierarchy(Arc::new(strategy))
1205        .with_router(router)
1206        .build();
1207
1208        assert_eq!(mesh.node_id(), "full-node");
1209        assert!(mesh.transport().is_some());
1210        assert!(mesh.hierarchy().is_some());
1211        assert!(mesh.router().is_some());
1212        assert_eq!(mesh.status().peer_count, 1);
1213    }
1214
1215    #[test]
1216    fn test_builder_lifecycle() {
1217        let mesh = HiveMeshBuilder::new(MeshConfig::default()).build();
1218        assert!(mesh.start().is_ok());
1219        assert_eq!(mesh.state(), MeshState::Running);
1220        assert!(mesh.stop().is_ok());
1221        assert_eq!(mesh.state(), MeshState::Stopped);
1222    }
1223
1224    // ── TransportManager integration ──────────────────────────────
1225
1226    #[test]
1227    fn test_transport_manager_initially_none() {
1228        let mesh = HiveMesh::new(MeshConfig::default());
1229        assert!(mesh.transport_manager().is_none());
1230    }
1231
1232    #[test]
1233    fn test_set_transport_manager() {
1234        use crate::transport::TransportManagerConfig;
1235        let mut mesh = HiveMesh::new(MeshConfig::default());
1236        let tm = TransportManager::new(TransportManagerConfig::default());
1237        mesh.set_transport_manager(tm);
1238        assert!(mesh.transport_manager().is_some());
1239    }
1240
1241    #[test]
1242    fn test_builder_with_transport_manager() {
1243        use crate::transport::TransportManagerConfig;
1244        let tm = TransportManager::new(TransportManagerConfig::default());
1245        let mesh = HiveMeshBuilder::new(MeshConfig::default())
1246            .with_transport_manager(tm)
1247            .build();
1248        assert!(mesh.transport_manager().is_some());
1249    }
1250
1251    #[test]
1252    fn test_builder_full_with_transport_manager() {
1253        use crate::beacon::HierarchyLevel;
1254        use crate::hierarchy::{NodeRole, StaticHierarchyStrategy};
1255        use crate::transport::TransportManagerConfig;
1256
1257        let strategy = StaticHierarchyStrategy {
1258            assigned_level: HierarchyLevel::Platoon,
1259            assigned_role: NodeRole::Leader,
1260        };
1261        let peers = vec![NodeId::new("p1".into())];
1262        let router = MeshRouter::with_node_id("full");
1263        let tm = TransportManager::new(TransportManagerConfig::default());
1264
1265        let mesh = HiveMeshBuilder::new(MeshConfig {
1266            node_id: Some("full-tm-node".to_string()),
1267            ..Default::default()
1268        })
1269        .with_transport(Arc::new(MockTransport::new(peers)))
1270        .with_transport_manager(tm)
1271        .with_hierarchy(Arc::new(strategy))
1272        .with_router(router)
1273        .build();
1274
1275        assert_eq!(mesh.node_id(), "full-tm-node");
1276        assert!(mesh.transport().is_some());
1277        assert!(mesh.transport_manager().is_some());
1278        assert!(mesh.hierarchy().is_some());
1279        assert!(mesh.router().is_some());
1280    }
1281
1282    // ── Gap 5: QoS policies ────────────────────────────────────────
1283
1284    #[test]
1285    fn test_bandwidth_initially_none() {
1286        let mesh = HiveMesh::new(MeshConfig::default());
1287        assert!(mesh.bandwidth().is_none());
1288    }
1289
1290    #[test]
1291    fn test_set_bandwidth() {
1292        let mut mesh = HiveMesh::new(MeshConfig::default());
1293        mesh.set_bandwidth(crate::qos::BandwidthAllocation::new(1_000_000));
1294        assert!(mesh.bandwidth().is_some());
1295    }
1296
1297    #[test]
1298    fn test_preemption_initially_none() {
1299        let mesh = HiveMesh::new(MeshConfig::default());
1300        assert!(mesh.preemption().is_none());
1301    }
1302
1303    #[test]
1304    fn test_set_preemption() {
1305        let mut mesh = HiveMesh::new(MeshConfig::default());
1306        mesh.set_preemption(crate::qos::PreemptionController::new());
1307        assert!(mesh.preemption().is_some());
1308    }
1309
1310    #[test]
1311    fn test_builder_with_bandwidth() {
1312        let mesh = HiveMeshBuilder::new(MeshConfig::default())
1313            .with_bandwidth(crate::qos::BandwidthAllocation::default_tactical())
1314            .build();
1315        assert!(mesh.bandwidth().is_some());
1316    }
1317
1318    #[test]
1319    fn test_builder_with_preemption() {
1320        let mesh = HiveMeshBuilder::new(MeshConfig::default())
1321            .with_preemption(crate::qos::PreemptionController::new())
1322            .build();
1323        assert!(mesh.preemption().is_some());
1324    }
1325
1326    // ── Gap 6: Security primitives ─────────────────────────────────
1327
1328    #[test]
1329    fn test_device_keypair_initially_none() {
1330        let mesh = HiveMesh::new(MeshConfig::default());
1331        assert!(mesh.device_keypair().is_none());
1332    }
1333
1334    #[test]
1335    fn test_set_device_keypair() {
1336        let mut mesh = HiveMesh::new(MeshConfig::default());
1337        mesh.set_device_keypair(crate::security::DeviceKeypair::generate());
1338        assert!(mesh.device_keypair().is_some());
1339    }
1340
1341    #[test]
1342    fn test_formation_key_initially_none() {
1343        let mesh = HiveMesh::new(MeshConfig::default());
1344        assert!(mesh.formation_key().is_none());
1345    }
1346
1347    #[test]
1348    fn test_set_formation_key() {
1349        let mut mesh = HiveMesh::new(MeshConfig::default());
1350        mesh.set_formation_key(crate::security::FormationKey::new(
1351            "test-formation",
1352            &[0u8; 32],
1353        ));
1354        assert!(mesh.formation_key().is_some());
1355    }
1356
1357    #[test]
1358    fn test_builder_with_device_keypair() {
1359        let mesh = HiveMeshBuilder::new(MeshConfig::default())
1360            .with_device_keypair(crate::security::DeviceKeypair::generate())
1361            .build();
1362        assert!(mesh.device_keypair().is_some());
1363    }
1364
1365    #[test]
1366    fn test_builder_with_device_keypair_from_seed() {
1367        let mesh = HiveMeshBuilder::new(MeshConfig::default())
1368            .with_device_keypair_from_seed(b"k8s-secret", "pod-1")
1369            .unwrap()
1370            .build();
1371        assert!(mesh.device_keypair().is_some());
1372
1373        // Same seed+context should produce the same device ID
1374        let mesh2 = HiveMeshBuilder::new(MeshConfig::default())
1375            .with_device_keypair_from_seed(b"k8s-secret", "pod-1")
1376            .unwrap()
1377            .build();
1378        assert_eq!(
1379            mesh.device_keypair().unwrap().device_id(),
1380            mesh2.device_keypair().unwrap().device_id()
1381        );
1382    }
1383
1384    #[test]
1385    fn test_builder_with_formation_key() {
1386        let mesh = HiveMeshBuilder::new(MeshConfig::default())
1387            .with_formation_key(crate::security::FormationKey::new("f1", &[1u8; 32]))
1388            .build();
1389        assert!(mesh.formation_key().is_some());
1390    }
1391
1392    // ── Gap 3: Discovery strategy ──────────────────────────────────
1393
1394    #[test]
1395    fn test_discovery_initially_none() {
1396        let mesh = HiveMesh::new(MeshConfig::default());
1397        assert!(mesh.discovery().read().unwrap().is_none());
1398    }
1399
1400    #[test]
1401    fn test_set_discovery() {
1402        let mesh = HiveMesh::new(MeshConfig::default());
1403        let strategy = crate::discovery::HybridDiscovery::new();
1404        mesh.set_discovery(Box::new(strategy));
1405        assert!(mesh.discovery().read().unwrap().is_some());
1406    }
1407
1408    #[test]
1409    fn test_builder_with_discovery() {
1410        let strategy = crate::discovery::HybridDiscovery::new();
1411        let mesh = HiveMeshBuilder::new(MeshConfig::default())
1412            .with_discovery(Box::new(strategy))
1413            .build();
1414        assert!(mesh.discovery().read().unwrap().is_some());
1415    }
1416
1417    // ── Gap 2: Beacon system ───────────────────────────────────────
1418
1419    fn mock_storage() -> Arc<dyn crate::beacon::BeaconStorage> {
1420        Arc::new(crate::beacon::MockBeaconStorage::new())
1421    }
1422
1423    #[test]
1424    fn test_beacon_broadcaster_initially_none() {
1425        let mesh = HiveMesh::new(MeshConfig::default());
1426        assert!(mesh.beacon_broadcaster().is_none());
1427    }
1428
1429    #[test]
1430    fn test_set_beacon_broadcaster() {
1431        use crate::beacon::{BeaconBroadcaster, GeoPosition, HierarchyLevel};
1432
1433        let mut mesh = HiveMesh::new(MeshConfig::default());
1434        let bb = BeaconBroadcaster::new(
1435            mock_storage(),
1436            "test-node".to_string(),
1437            GeoPosition {
1438                lat: 0.0,
1439                lon: 0.0,
1440                alt: None,
1441            },
1442            HierarchyLevel::Squad,
1443            None,
1444            Duration::from_secs(5),
1445        );
1446        mesh.set_beacon_broadcaster(bb);
1447        assert!(mesh.beacon_broadcaster().is_some());
1448    }
1449
1450    #[test]
1451    fn test_beacon_observer_initially_none() {
1452        let mesh = HiveMesh::new(MeshConfig::default());
1453        assert!(mesh.beacon_observer().is_none());
1454    }
1455
1456    #[test]
1457    fn test_set_beacon_observer() {
1458        use crate::beacon::BeaconObserver;
1459
1460        let mut mesh = HiveMesh::new(MeshConfig::default());
1461        let bo = Arc::new(BeaconObserver::new(mock_storage(), "s00000".to_string()));
1462        mesh.set_beacon_observer(bo);
1463        assert!(mesh.beacon_observer().is_some());
1464    }
1465
1466    #[test]
1467    fn test_beacon_janitor_initially_none() {
1468        let mesh = HiveMesh::new(MeshConfig::default());
1469        assert!(mesh.beacon_janitor().is_none());
1470    }
1471
1472    #[test]
1473    fn test_set_beacon_janitor() {
1474        use crate::beacon::BeaconJanitor;
1475        use std::collections::HashMap;
1476
1477        let mut mesh = HiveMesh::new(MeshConfig::default());
1478        let nearby = Arc::new(tokio::sync::RwLock::new(HashMap::new()));
1479        let bj = BeaconJanitor::new(nearby, Duration::from_secs(60), Duration::from_secs(10));
1480        mesh.set_beacon_janitor(bj);
1481        assert!(mesh.beacon_janitor().is_some());
1482    }
1483
1484    #[test]
1485    fn test_builder_with_beacon_broadcaster() {
1486        use crate::beacon::{BeaconBroadcaster, GeoPosition, HierarchyLevel};
1487
1488        let bb = BeaconBroadcaster::new(
1489            mock_storage(),
1490            "builder-node".to_string(),
1491            GeoPosition {
1492                lat: 1.0,
1493                lon: 2.0,
1494                alt: None,
1495            },
1496            HierarchyLevel::Platoon,
1497            None,
1498            Duration::from_secs(5),
1499        );
1500        let mesh = HiveMeshBuilder::new(MeshConfig::default())
1501            .with_beacon_broadcaster(bb)
1502            .build();
1503        assert!(mesh.beacon_broadcaster().is_some());
1504    }
1505
1506    #[test]
1507    fn test_builder_with_beacon_observer() {
1508        use crate::beacon::BeaconObserver;
1509
1510        let bo = Arc::new(BeaconObserver::new(mock_storage(), "s00000".to_string()));
1511        let mesh = HiveMeshBuilder::new(MeshConfig::default())
1512            .with_beacon_observer(bo)
1513            .build();
1514        assert!(mesh.beacon_observer().is_some());
1515    }
1516
1517    #[test]
1518    fn test_builder_with_beacon_janitor() {
1519        use crate::beacon::BeaconJanitor;
1520        use std::collections::HashMap;
1521
1522        let nearby = Arc::new(tokio::sync::RwLock::new(HashMap::new()));
1523        let bj = BeaconJanitor::new(nearby, Duration::from_secs(60), Duration::from_secs(10));
1524        let mesh = HiveMeshBuilder::new(MeshConfig::default())
1525            .with_beacon_janitor(bj)
1526            .build();
1527        assert!(mesh.beacon_janitor().is_some());
1528    }
1529
1530    // ── Gap 1: Topology manager ────────────────────────────────────
1531
1532    #[test]
1533    fn test_topology_manager_initially_none() {
1534        let mesh = HiveMesh::new(MeshConfig::default());
1535        assert!(mesh.topology_manager().is_none());
1536    }
1537
1538    #[test]
1539    fn test_set_topology_manager() {
1540        use crate::beacon::{BeaconObserver, GeoPosition, HierarchyLevel};
1541        use crate::topology::{TopologyBuilder, TopologyConfig, TopologyManager};
1542
1543        let mut mesh = HiveMesh::new(MeshConfig::default());
1544        let observer = Arc::new(BeaconObserver::new(mock_storage(), "s00000".to_string()));
1545        let builder = TopologyBuilder::new(
1546            TopologyConfig::default(),
1547            "topo-node".to_string(),
1548            GeoPosition {
1549                lat: 0.0,
1550                lon: 0.0,
1551                alt: None,
1552            },
1553            HierarchyLevel::Squad,
1554            None,
1555            observer,
1556        );
1557        let transport: Arc<dyn MeshTransport> = Arc::new(MockTransport::empty());
1558        let tm = TopologyManager::new(builder, transport);
1559        mesh.set_topology_manager(tm);
1560        assert!(mesh.topology_manager().is_some());
1561    }
1562
1563    #[test]
1564    fn test_builder_with_topology_manager() {
1565        use crate::beacon::{BeaconObserver, GeoPosition, HierarchyLevel};
1566        use crate::topology::{TopologyBuilder, TopologyConfig, TopologyManager};
1567
1568        let observer = Arc::new(BeaconObserver::new(mock_storage(), "s00000".to_string()));
1569        let builder = TopologyBuilder::new(
1570            TopologyConfig::default(),
1571            "topo-builder-node".to_string(),
1572            GeoPosition {
1573                lat: 0.0,
1574                lon: 0.0,
1575                alt: None,
1576            },
1577            HierarchyLevel::Squad,
1578            None,
1579            observer,
1580        );
1581        let transport: Arc<dyn MeshTransport> = Arc::new(MockTransport::empty());
1582        let tm = TopologyManager::new(builder, transport);
1583        let mesh = HiveMeshBuilder::new(MeshConfig::default())
1584            .with_topology_manager(tm)
1585            .build();
1586        assert!(mesh.topology_manager().is_some());
1587    }
1588}
1589
1590// ─── Broker feature tests ────────────────────────────────────────────────────
1591
1592#[cfg(all(test, feature = "broker"))]
1593mod broker_tests {
1594    use super::*;
1595    use crate::broker::state::MeshBrokerState;
1596    use crate::config::MeshConfig;
1597
1598    #[test]
1599    fn test_broker_node_info() {
1600        let mesh = HiveMesh::new(MeshConfig {
1601            node_id: Some("broker-node".to_string()),
1602            ..Default::default()
1603        });
1604        let info = mesh.node_info();
1605        assert_eq!(info.node_id, "broker-node");
1606        assert_eq!(info.uptime_secs, 0);
1607        assert!(!info.version.is_empty());
1608    }
1609
1610    #[test]
1611    fn test_broker_node_info_with_uptime() {
1612        let mesh = HiveMesh::new(MeshConfig {
1613            node_id: Some("uptime-node".to_string()),
1614            ..Default::default()
1615        });
1616        mesh.start().unwrap();
1617        let info = mesh.node_info();
1618        assert_eq!(info.node_id, "uptime-node");
1619        // uptime_secs might be 0 on a fast machine, that's OK
1620    }
1621
1622    #[tokio::test]
1623    async fn test_broker_list_peers_no_transport() {
1624        let mesh = HiveMesh::new(MeshConfig::default());
1625        let peers = mesh.list_peers().await;
1626        assert!(peers.is_empty());
1627    }
1628
1629    #[tokio::test]
1630    async fn test_broker_get_peer_no_transport() {
1631        let mesh = HiveMesh::new(MeshConfig::default());
1632        let peer = mesh.get_peer("unknown").await;
1633        assert!(peer.is_none());
1634    }
1635
1636    #[test]
1637    fn test_broker_topology() {
1638        let mesh = HiveMesh::new(MeshConfig::default());
1639        let topo = mesh.topology();
1640        assert_eq!(topo.peer_count, 0);
1641        assert_eq!(topo.role, "standalone");
1642        assert_eq!(topo.hierarchy_level, 0);
1643    }
1644
1645    #[test]
1646    fn test_broker_subscribe_events() {
1647        let mesh = HiveMesh::new(MeshConfig::default());
1648        let _rx = MeshBrokerState::subscribe_events(&mesh);
1649        // Receiver is valid (won't panic)
1650    }
1651
1652    #[test]
1653    fn test_broker_event_bridge() {
1654        use crate::broker::state::MeshEvent;
1655
1656        let mesh = HiveMesh::new(MeshConfig::default());
1657        let mut rx = MeshBrokerState::subscribe_events(&mesh);
1658
1659        // Emit a broker event via the public API
1660        mesh.emit_mesh_event(MeshEvent::PeerConnected {
1661            peer_id: "test-peer".into(),
1662        });
1663
1664        // Receiver should get it
1665        let event = rx.try_recv().unwrap();
1666        assert!(matches!(
1667            event,
1668            MeshEvent::PeerConnected { ref peer_id } if peer_id == "test-peer"
1669        ));
1670    }
1671
1672    #[test]
1673    fn test_broker_event_bridge_start_emits_topology() {
1674        use crate::broker::state::MeshEvent;
1675
1676        let mesh = HiveMesh::new(MeshConfig::default());
1677        let mut rx = MeshBrokerState::subscribe_events(&mesh);
1678
1679        mesh.start().unwrap();
1680
1681        let event = rx.try_recv().unwrap();
1682        assert!(matches!(
1683            event,
1684            MeshEvent::TopologyChanged { ref new_role, peer_count: 0 } if new_role == "standalone"
1685        ));
1686    }
1687
1688    #[test]
1689    fn test_broker_event_bridge_stop_emits_topology() {
1690        use crate::broker::state::MeshEvent;
1691
1692        let mesh = HiveMesh::new(MeshConfig::default());
1693        mesh.start().unwrap();
1694
1695        let mut rx = MeshBrokerState::subscribe_events(&mesh);
1696        mesh.stop().unwrap();
1697
1698        let event = rx.try_recv().unwrap();
1699        assert!(matches!(
1700            event,
1701            MeshEvent::TopologyChanged { ref new_role, peer_count: 0 } if new_role == "stopped"
1702        ));
1703    }
1704}