1use crate::config::MeshConfig;
34use crate::hierarchy::HierarchyStrategy;
35use crate::routing::MeshRouter;
36use crate::transport::{MeshTransport, NodeId, TransportError, TransportManager};
37use std::fmt;
38use std::sync::{Arc, RwLock};
39use std::time::Instant;
40use tokio::sync::broadcast;
41use tokio_util::sync::CancellationToken;
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum MeshState {
48 Created,
50 Starting,
52 Running,
54 Stopping,
56 Stopped,
58}
59
60impl fmt::Display for MeshState {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 match self {
63 MeshState::Created => write!(f, "created"),
64 MeshState::Starting => write!(f, "starting"),
65 MeshState::Running => write!(f, "running"),
66 MeshState::Stopping => write!(f, "stopping"),
67 MeshState::Stopped => write!(f, "stopped"),
68 }
69 }
70}
71
72#[derive(Debug)]
76pub enum MeshError {
77 NotRunning,
79 AlreadyRunning,
81 InvalidConfig(String),
83 Transport(TransportError),
85 Other(String),
87}
88
89impl fmt::Display for MeshError {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 match self {
92 MeshError::NotRunning => write!(f, "mesh is not running"),
93 MeshError::AlreadyRunning => write!(f, "mesh is already running"),
94 MeshError::InvalidConfig(msg) => write!(f, "invalid configuration: {}", msg),
95 MeshError::Transport(err) => write!(f, "transport error: {}", err),
96 MeshError::Other(msg) => write!(f, "{}", msg),
97 }
98 }
99}
100
101impl std::error::Error for MeshError {
102 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
103 match self {
104 MeshError::Transport(err) => Some(err),
105 _ => None,
106 }
107 }
108}
109
110impl From<TransportError> for MeshError {
111 fn from(err: TransportError) -> Self {
112 MeshError::Transport(err)
113 }
114}
115
116#[derive(Debug, Clone)]
120pub enum PeatMeshEvent {
121 StateChanged(MeshState),
123 PeerJoined(NodeId),
125 PeerLeft(NodeId),
127 TopologyChanged(Box<crate::topology::TopologyEvent>),
129}
130
131#[derive(Debug, Clone)]
135pub struct MeshStatus {
136 pub state: MeshState,
138 pub peer_count: usize,
140 pub node_id: String,
142 pub uptime: std::time::Duration,
144}
145
146const EVENT_CHANNEL_CAPACITY: usize = 256;
149
150pub struct PeatMesh {
155 config: MeshConfig,
156 node_id: String,
157 state: RwLock<MeshState>,
158 transport: Option<Arc<dyn MeshTransport>>,
159 transport_manager: Option<TransportManager>,
160 hierarchy: Option<Arc<dyn HierarchyStrategy>>,
161 router: Option<MeshRouter>,
162 bandwidth: Option<crate::qos::BandwidthAllocation>,
164 preemption: Option<crate::qos::PreemptionController>,
165 device_keypair: Option<crate::security::DeviceKeypair>,
167 formation_key: Option<crate::security::FormationKey>,
168 discovery: RwLock<Option<Box<dyn crate::discovery::DiscoveryStrategy>>>,
170 beacon_broadcaster: Option<crate::beacon::BeaconBroadcaster>,
172 beacon_observer: Option<Arc<crate::beacon::BeaconObserver>>,
173 beacon_janitor: Option<crate::beacon::BeaconJanitor>,
174 topology_manager: Option<crate::topology::TopologyManager>,
176 event_tx: broadcast::Sender<PeatMeshEvent>,
177 #[cfg(feature = "broker")]
178 broker_event_tx: broadcast::Sender<crate::broker::state::MeshEvent>,
179 started_at: RwLock<Option<Instant>>,
180 cancellation_token: RwLock<CancellationToken>,
184}
185
186impl PeatMesh {
187 pub fn new(config: MeshConfig) -> Self {
191 let node_id = config
192 .node_id
193 .clone()
194 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
195 let (event_tx, _) = broadcast::channel(EVENT_CHANNEL_CAPACITY);
196 #[cfg(feature = "broker")]
197 let (broker_event_tx, _) = broadcast::channel(EVENT_CHANNEL_CAPACITY);
198 Self {
199 config,
200 node_id,
201 state: RwLock::new(MeshState::Created),
202 transport: None,
203 transport_manager: None,
204 hierarchy: None,
205 router: None,
206 bandwidth: None,
207 preemption: None,
208 device_keypair: None,
209 formation_key: None,
210 discovery: RwLock::new(None),
211 beacon_broadcaster: None,
212 beacon_observer: None,
213 beacon_janitor: None,
214 topology_manager: None,
215 event_tx,
216 #[cfg(feature = "broker")]
217 broker_event_tx,
218 started_at: RwLock::new(None),
219 cancellation_token: RwLock::new(CancellationToken::new()),
220 }
221 }
222
223 pub fn start(&self) -> Result<(), MeshError> {
225 let mut state = self.state.write().unwrap_or_else(|e| e.into_inner());
226 match *state {
227 MeshState::Created | MeshState::Stopped => {}
228 MeshState::Running | MeshState::Starting | MeshState::Stopping => {
229 return Err(MeshError::AlreadyRunning);
230 }
231 }
232
233 *state = MeshState::Starting;
234 let _ = self
235 .event_tx
236 .send(PeatMeshEvent::StateChanged(MeshState::Starting));
237
238 *self
241 .cancellation_token
242 .write()
243 .unwrap_or_else(|e| e.into_inner()) = CancellationToken::new();
244
245 *state = MeshState::Running;
246 *self.started_at.write().unwrap_or_else(|e| e.into_inner()) = Some(Instant::now());
247 let _ = self
248 .event_tx
249 .send(PeatMeshEvent::StateChanged(MeshState::Running));
250
251 #[cfg(feature = "broker")]
252 self.emit_broker_event(crate::broker::state::MeshEvent::TopologyChanged {
253 new_role: "standalone".to_string(),
254 peer_count: 0,
255 });
256
257 Ok(())
258 }
259
260 pub fn stop(&self) -> Result<(), MeshError> {
266 let mut state = self.state.write().unwrap_or_else(|e| e.into_inner());
267 match *state {
268 MeshState::Running => {}
269 _ => return Err(MeshError::NotRunning),
270 }
271
272 *state = MeshState::Stopping;
273 let _ = self
274 .event_tx
275 .send(PeatMeshEvent::StateChanged(MeshState::Stopping));
276
277 self.cancellation_token
279 .read()
280 .unwrap_or_else(|e| e.into_inner())
281 .cancel();
282
283 *state = MeshState::Stopped;
284 let _ = self
285 .event_tx
286 .send(PeatMeshEvent::StateChanged(MeshState::Stopped));
287
288 #[cfg(feature = "broker")]
289 self.emit_broker_event(crate::broker::state::MeshEvent::TopologyChanged {
290 new_role: "stopped".to_string(),
291 peer_count: 0,
292 });
293
294 Ok(())
295 }
296
297 pub fn state(&self) -> MeshState {
299 *self.state.read().unwrap_or_else(|e| e.into_inner())
300 }
301
302 pub fn status(&self) -> MeshStatus {
304 let state = *self.state.read().unwrap_or_else(|e| e.into_inner());
305 let uptime = self
306 .started_at
307 .read()
308 .unwrap()
309 .map(|t| t.elapsed())
310 .unwrap_or_default();
311 let peer_count = self.transport.as_ref().map(|t| t.peer_count()).unwrap_or(0);
312
313 MeshStatus {
314 state,
315 peer_count,
316 node_id: self.node_id.clone(),
317 uptime,
318 }
319 }
320
321 pub fn config(&self) -> &MeshConfig {
323 &self.config
324 }
325
326 pub fn node_id(&self) -> &str {
328 &self.node_id
329 }
330
331 pub fn subscribe_events(&self) -> broadcast::Receiver<PeatMeshEvent> {
333 self.event_tx.subscribe()
334 }
335
336 pub fn child_token(&self) -> CancellationToken {
342 self.cancellation_token
343 .read()
344 .unwrap_or_else(|e| e.into_inner())
345 .child_token()
346 }
347
348 pub fn set_transport(&mut self, transport: Arc<dyn MeshTransport>) {
350 self.transport = Some(transport);
351 }
352
353 pub fn set_transport_manager(&mut self, tm: TransportManager) {
355 self.transport_manager = Some(tm);
356 }
357
358 pub fn transport_manager(&self) -> Option<&TransportManager> {
360 self.transport_manager.as_ref()
361 }
362
363 pub fn set_hierarchy(&mut self, hierarchy: Arc<dyn HierarchyStrategy>) {
365 self.hierarchy = Some(hierarchy);
366 }
367
368 pub fn transport(&self) -> Option<&Arc<dyn MeshTransport>> {
370 self.transport.as_ref()
371 }
372
373 pub fn hierarchy(&self) -> Option<&Arc<dyn HierarchyStrategy>> {
375 self.hierarchy.as_ref()
376 }
377
378 pub fn router(&self) -> Option<&MeshRouter> {
380 self.router.as_ref()
381 }
382
383 pub fn set_bandwidth(&mut self, bw: crate::qos::BandwidthAllocation) {
387 self.bandwidth = Some(bw);
388 }
389
390 pub fn bandwidth(&self) -> Option<&crate::qos::BandwidthAllocation> {
392 self.bandwidth.as_ref()
393 }
394
395 pub fn set_preemption(&mut self, pc: crate::qos::PreemptionController) {
397 self.preemption = Some(pc);
398 }
399
400 pub fn preemption(&self) -> Option<&crate::qos::PreemptionController> {
402 self.preemption.as_ref()
403 }
404
405 pub fn set_device_keypair(&mut self, kp: crate::security::DeviceKeypair) {
409 self.device_keypair = Some(kp);
410 }
411
412 pub fn device_keypair(&self) -> Option<&crate::security::DeviceKeypair> {
414 self.device_keypair.as_ref()
415 }
416
417 pub fn set_formation_key(&mut self, fk: crate::security::FormationKey) {
419 self.formation_key = Some(fk);
420 }
421
422 pub fn formation_key(&self) -> Option<&crate::security::FormationKey> {
424 self.formation_key.as_ref()
425 }
426
427 pub fn set_discovery(&self, strategy: Box<dyn crate::discovery::DiscoveryStrategy>) {
435 *self.discovery.write().unwrap_or_else(|e| e.into_inner()) = Some(strategy);
436 }
437
438 pub fn discovery(&self) -> &RwLock<Option<Box<dyn crate::discovery::DiscoveryStrategy>>> {
440 &self.discovery
441 }
442
443 pub fn set_beacon_broadcaster(&mut self, bb: crate::beacon::BeaconBroadcaster) {
447 self.beacon_broadcaster = Some(bb);
448 }
449
450 pub fn beacon_broadcaster(&self) -> Option<&crate::beacon::BeaconBroadcaster> {
452 self.beacon_broadcaster.as_ref()
453 }
454
455 pub fn set_beacon_observer(&mut self, bo: Arc<crate::beacon::BeaconObserver>) {
457 self.beacon_observer = Some(bo);
458 }
459
460 pub fn beacon_observer(&self) -> Option<&Arc<crate::beacon::BeaconObserver>> {
462 self.beacon_observer.as_ref()
463 }
464
465 pub fn set_beacon_janitor(&mut self, bj: crate::beacon::BeaconJanitor) {
467 self.beacon_janitor = Some(bj);
468 }
469
470 pub fn beacon_janitor(&self) -> Option<&crate::beacon::BeaconJanitor> {
472 self.beacon_janitor.as_ref()
473 }
474
475 pub fn set_topology_manager(&mut self, tm: crate::topology::TopologyManager) {
479 self.topology_manager = Some(tm);
480 }
481
482 pub fn topology_manager(&self) -> Option<&crate::topology::TopologyManager> {
484 self.topology_manager.as_ref()
485 }
486
487 #[cfg(feature = "broker")]
489 pub fn emit_mesh_event(&self, event: crate::broker::state::MeshEvent) {
490 let _ = self.broker_event_tx.send(event);
491 }
492
493 #[cfg(feature = "broker")]
494 fn emit_broker_event(&self, event: crate::broker::state::MeshEvent) {
495 let _ = self.broker_event_tx.send(event);
496 }
497}
498
499#[cfg(feature = "broker")]
502#[async_trait::async_trait]
503impl crate::broker::state::MeshBrokerState for PeatMesh {
504 fn node_info(&self) -> crate::broker::state::MeshNodeInfo {
505 let uptime = self
506 .started_at
507 .read()
508 .unwrap()
509 .map(|t| t.elapsed().as_secs())
510 .unwrap_or(0);
511 crate::broker::state::MeshNodeInfo {
512 node_id: self.node_id.clone(),
513 uptime_secs: uptime,
514 version: env!("CARGO_PKG_VERSION").to_string(),
515 }
516 }
517
518 async fn list_peers(&self) -> Vec<crate::broker::state::PeerSummary> {
519 let Some(transport) = &self.transport else {
520 return vec![];
521 };
522 transport
523 .connected_peers()
524 .into_iter()
525 .map(|peer_id| {
526 let health = transport.get_peer_health(&peer_id);
527 crate::broker::state::PeerSummary {
528 id: peer_id.to_string(),
529 connected: true,
530 state: health
531 .as_ref()
532 .map(|h| h.state.to_string())
533 .unwrap_or_else(|| "unknown".to_string()),
534 rtt_ms: health.map(|h| h.rtt_ms as u64),
535 }
536 })
537 .collect()
538 }
539
540 async fn get_peer(&self, id: &str) -> Option<crate::broker::state::PeerSummary> {
541 let transport = self.transport.as_ref()?;
542 let node_id = NodeId::new(id.to_string());
543 if transport.is_connected(&node_id) {
544 let health = transport.get_peer_health(&node_id);
545 Some(crate::broker::state::PeerSummary {
546 id: id.to_string(),
547 connected: true,
548 state: health
549 .as_ref()
550 .map(|h| h.state.to_string())
551 .unwrap_or_else(|| "unknown".to_string()),
552 rtt_ms: health.map(|h| h.rtt_ms as u64),
553 })
554 } else {
555 None
556 }
557 }
558
559 fn topology(&self) -> crate::broker::state::TopologySummary {
560 let peer_count = self.transport.as_ref().map(|t| t.peer_count()).unwrap_or(0);
561 crate::broker::state::TopologySummary {
562 peer_count,
563 role: "standalone".to_string(),
564 hierarchy_level: 0,
565 }
566 }
567
568 fn subscribe_events(&self) -> broadcast::Receiver<crate::broker::state::MeshEvent> {
569 self.broker_event_tx.subscribe()
570 }
571}
572
573pub struct PeatMeshBuilder {
577 config: MeshConfig,
578 transport: Option<Arc<dyn MeshTransport>>,
579 transport_manager: Option<TransportManager>,
580 hierarchy: Option<Arc<dyn HierarchyStrategy>>,
581 router: Option<MeshRouter>,
582 bandwidth: Option<crate::qos::BandwidthAllocation>,
583 preemption: Option<crate::qos::PreemptionController>,
584 device_keypair: Option<crate::security::DeviceKeypair>,
585 formation_key: Option<crate::security::FormationKey>,
586 discovery: Option<Box<dyn crate::discovery::DiscoveryStrategy>>,
587 beacon_broadcaster: Option<crate::beacon::BeaconBroadcaster>,
588 beacon_observer: Option<Arc<crate::beacon::BeaconObserver>>,
589 beacon_janitor: Option<crate::beacon::BeaconJanitor>,
590 topology_manager: Option<crate::topology::TopologyManager>,
591}
592
593impl PeatMeshBuilder {
594 pub fn new(config: MeshConfig) -> Self {
596 Self {
597 config,
598 transport: None,
599 transport_manager: None,
600 hierarchy: None,
601 router: None,
602 bandwidth: None,
603 preemption: None,
604 device_keypair: None,
605 formation_key: None,
606 discovery: None,
607 beacon_broadcaster: None,
608 beacon_observer: None,
609 beacon_janitor: None,
610 topology_manager: None,
611 }
612 }
613
614 pub fn with_transport(mut self, transport: Arc<dyn MeshTransport>) -> Self {
616 self.transport = Some(transport);
617 self
618 }
619
620 pub fn with_transport_manager(mut self, tm: TransportManager) -> Self {
622 self.transport_manager = Some(tm);
623 self
624 }
625
626 pub fn with_hierarchy(mut self, hierarchy: Arc<dyn HierarchyStrategy>) -> Self {
628 self.hierarchy = Some(hierarchy);
629 self
630 }
631
632 pub fn with_router(mut self, router: MeshRouter) -> Self {
634 self.router = Some(router);
635 self
636 }
637
638 pub fn with_bandwidth(mut self, bw: crate::qos::BandwidthAllocation) -> Self {
640 self.bandwidth = Some(bw);
641 self
642 }
643
644 pub fn with_preemption(mut self, pc: crate::qos::PreemptionController) -> Self {
646 self.preemption = Some(pc);
647 self
648 }
649
650 pub fn with_device_keypair(mut self, kp: crate::security::DeviceKeypair) -> Self {
652 self.device_keypair = Some(kp);
653 self
654 }
655
656 pub fn with_device_keypair_from_seed(
660 mut self,
661 seed: &[u8],
662 context: &str,
663 ) -> Result<Self, MeshError> {
664 let kp = crate::security::DeviceKeypair::from_seed(seed, context)
665 .map_err(|e| MeshError::InvalidConfig(e.to_string()))?;
666 self.device_keypair = Some(kp);
667 Ok(self)
668 }
669
670 pub fn with_formation_key(mut self, fk: crate::security::FormationKey) -> Self {
672 self.formation_key = Some(fk);
673 self
674 }
675
676 pub fn with_discovery(
678 mut self,
679 strategy: Box<dyn crate::discovery::DiscoveryStrategy>,
680 ) -> Self {
681 self.discovery = Some(strategy);
682 self
683 }
684
685 pub fn with_beacon_broadcaster(mut self, bb: crate::beacon::BeaconBroadcaster) -> Self {
687 self.beacon_broadcaster = Some(bb);
688 self
689 }
690
691 pub fn with_beacon_observer(mut self, bo: Arc<crate::beacon::BeaconObserver>) -> Self {
693 self.beacon_observer = Some(bo);
694 self
695 }
696
697 pub fn with_beacon_janitor(mut self, bj: crate::beacon::BeaconJanitor) -> Self {
699 self.beacon_janitor = Some(bj);
700 self
701 }
702
703 pub fn with_topology_manager(mut self, tm: crate::topology::TopologyManager) -> Self {
705 self.topology_manager = Some(tm);
706 self
707 }
708
709 pub fn build(self) -> PeatMesh {
711 let node_id = self
712 .config
713 .node_id
714 .clone()
715 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
716 let (event_tx, _) = broadcast::channel(EVENT_CHANNEL_CAPACITY);
717 #[cfg(feature = "broker")]
718 let (broker_event_tx, _) = broadcast::channel(EVENT_CHANNEL_CAPACITY);
719
720 PeatMesh {
721 config: self.config,
722 node_id,
723 state: RwLock::new(MeshState::Created),
724 transport: self.transport,
725 transport_manager: self.transport_manager,
726 hierarchy: self.hierarchy,
727 router: self.router,
728 bandwidth: self.bandwidth,
729 preemption: self.preemption,
730 device_keypair: self.device_keypair,
731 formation_key: self.formation_key,
732 discovery: RwLock::new(self.discovery),
733 beacon_broadcaster: self.beacon_broadcaster,
734 beacon_observer: self.beacon_observer,
735 beacon_janitor: self.beacon_janitor,
736 topology_manager: self.topology_manager,
737 event_tx,
738 #[cfg(feature = "broker")]
739 broker_event_tx,
740 started_at: RwLock::new(None),
741 cancellation_token: RwLock::new(CancellationToken::new()),
742 }
743 }
744}
745
746#[cfg(test)]
749mod tests {
750 use super::*;
751 use crate::config::MeshDiscoveryConfig;
752 use crate::transport::PeerEventReceiver;
753 use async_trait::async_trait;
754 use std::time::Duration;
755
756 struct MockTransport {
759 peers: Vec<NodeId>,
760 }
761
762 impl MockTransport {
763 fn new(peers: Vec<NodeId>) -> Self {
764 Self { peers }
765 }
766
767 fn empty() -> Self {
768 Self { peers: vec![] }
769 }
770 }
771
772 #[async_trait]
773 impl MeshTransport for MockTransport {
774 async fn start(&self) -> crate::transport::Result<()> {
775 Ok(())
776 }
777 async fn stop(&self) -> crate::transport::Result<()> {
778 Ok(())
779 }
780 async fn connect(
781 &self,
782 _peer_id: &NodeId,
783 ) -> crate::transport::Result<Box<dyn crate::transport::MeshConnection>> {
784 Err(TransportError::NotStarted)
785 }
786 async fn disconnect(&self, _peer_id: &NodeId) -> crate::transport::Result<()> {
787 Ok(())
788 }
789 fn get_connection(
790 &self,
791 _peer_id: &NodeId,
792 ) -> Option<Box<dyn crate::transport::MeshConnection>> {
793 None
794 }
795 fn peer_count(&self) -> usize {
796 self.peers.len()
797 }
798 fn connected_peers(&self) -> Vec<NodeId> {
799 self.peers.clone()
800 }
801 fn subscribe_peer_events(&self) -> PeerEventReceiver {
802 let (_tx, rx) = tokio::sync::mpsc::channel(1);
803 rx
804 }
805 }
806
807 #[test]
810 fn test_new_with_default_config() {
811 let mesh = PeatMesh::new(MeshConfig::default());
812 assert_eq!(mesh.state(), MeshState::Created);
813 assert!(!mesh.node_id().is_empty());
814 }
815
816 #[test]
817 fn test_new_with_explicit_node_id() {
818 let cfg = MeshConfig {
819 node_id: Some("my-node".to_string()),
820 ..Default::default()
821 };
822 let mesh = PeatMesh::new(cfg);
823 assert_eq!(mesh.node_id(), "my-node");
824 }
825
826 #[test]
827 fn test_new_auto_generates_uuid_node_id() {
828 let mesh = PeatMesh::new(MeshConfig::default());
829 assert_eq!(mesh.node_id().len(), 36);
831 assert_eq!(mesh.node_id().chars().filter(|&c| c == '-').count(), 4);
832 }
833
834 #[test]
837 fn test_start_transitions_to_running() {
838 let mesh = PeatMesh::new(MeshConfig::default());
839 assert!(mesh.start().is_ok());
840 assert_eq!(mesh.state(), MeshState::Running);
841 }
842
843 #[test]
844 fn test_start_when_already_running_returns_error() {
845 let mesh = PeatMesh::new(MeshConfig::default());
846 mesh.start().unwrap();
847 let err = mesh.start().unwrap_err();
848 assert!(matches!(err, MeshError::AlreadyRunning));
849 }
850
851 #[test]
852 fn test_stop_transitions_to_stopped() {
853 let mesh = PeatMesh::new(MeshConfig::default());
854 mesh.start().unwrap();
855 assert!(mesh.stop().is_ok());
856 assert_eq!(mesh.state(), MeshState::Stopped);
857 }
858
859 #[test]
860 fn test_stop_when_not_running_returns_error() {
861 let mesh = PeatMesh::new(MeshConfig::default());
862 let err = mesh.stop().unwrap_err();
863 assert!(matches!(err, MeshError::NotRunning));
864 }
865
866 #[test]
867 fn test_restart_after_stop() {
868 let mesh = PeatMesh::new(MeshConfig::default());
869 mesh.start().unwrap();
870 mesh.stop().unwrap();
871 assert!(mesh.start().is_ok());
872 assert_eq!(mesh.state(), MeshState::Running);
873 }
874
875 #[test]
876 fn test_stop_when_created_returns_error() {
877 let mesh = PeatMesh::new(MeshConfig::default());
878 assert!(matches!(mesh.stop().unwrap_err(), MeshError::NotRunning));
879 }
880
881 #[test]
882 fn test_stop_when_already_stopped_returns_error() {
883 let mesh = PeatMesh::new(MeshConfig::default());
884 mesh.start().unwrap();
885 mesh.stop().unwrap();
886 assert!(matches!(mesh.stop().unwrap_err(), MeshError::NotRunning));
887 }
888
889 #[test]
892 fn test_status_before_start() {
893 let cfg = MeshConfig {
894 node_id: Some("status-node".to_string()),
895 ..Default::default()
896 };
897 let mesh = PeatMesh::new(cfg);
898 let status = mesh.status();
899 assert_eq!(status.state, MeshState::Created);
900 assert_eq!(status.peer_count, 0);
901 assert_eq!(status.node_id, "status-node");
902 assert_eq!(status.uptime, Duration::ZERO);
903 }
904
905 #[test]
906 fn test_status_while_running() {
907 let mesh = PeatMesh::new(MeshConfig {
908 node_id: Some("running-node".to_string()),
909 ..Default::default()
910 });
911 mesh.start().unwrap();
912 let status = mesh.status();
913 assert_eq!(status.state, MeshState::Running);
914 assert_eq!(status.node_id, "running-node");
915 assert!(status.uptime <= Duration::from_secs(1));
917 }
918
919 #[test]
920 fn test_status_peer_count_with_transport() {
921 let peers = vec![NodeId::new("p1".into()), NodeId::new("p2".into())];
922 let mut mesh = PeatMesh::new(MeshConfig::default());
923 mesh.set_transport(Arc::new(MockTransport::new(peers)));
924 let status = mesh.status();
925 assert_eq!(status.peer_count, 2);
926 }
927
928 #[test]
931 fn test_config_accessor() {
932 let cfg = MeshConfig {
933 node_id: Some("cfg-test".to_string()),
934 discovery: MeshDiscoveryConfig {
935 mdns_enabled: false,
936 ..Default::default()
937 },
938 ..Default::default()
939 };
940 let mesh = PeatMesh::new(cfg);
941 assert_eq!(mesh.config().node_id.as_deref(), Some("cfg-test"));
942 assert!(!mesh.config().discovery.mdns_enabled);
943 }
944
945 #[test]
948 fn test_subscribe_events_receives_state_changes() {
949 let mesh = PeatMesh::new(MeshConfig::default());
950 let mut rx = mesh.subscribe_events();
951
952 mesh.start().unwrap();
953
954 let evt1 = rx.try_recv().unwrap();
956 assert!(matches!(
957 evt1,
958 PeatMeshEvent::StateChanged(MeshState::Starting)
959 ));
960 let evt2 = rx.try_recv().unwrap();
961 assert!(matches!(
962 evt2,
963 PeatMeshEvent::StateChanged(MeshState::Running)
964 ));
965 }
966
967 #[test]
968 fn test_subscribe_events_receives_stop_events() {
969 let mesh = PeatMesh::new(MeshConfig::default());
970 let mut rx = mesh.subscribe_events();
971
972 mesh.start().unwrap();
973 let _ = rx.try_recv();
975 let _ = rx.try_recv();
976
977 mesh.stop().unwrap();
978
979 let evt1 = rx.try_recv().unwrap();
980 assert!(matches!(
981 evt1,
982 PeatMeshEvent::StateChanged(MeshState::Stopping)
983 ));
984 let evt2 = rx.try_recv().unwrap();
985 assert!(matches!(
986 evt2,
987 PeatMeshEvent::StateChanged(MeshState::Stopped)
988 ));
989 }
990
991 #[test]
992 fn test_multiple_subscribers() {
993 let mesh = PeatMesh::new(MeshConfig::default());
994 let mut rx1 = mesh.subscribe_events();
995 let mut rx2 = mesh.subscribe_events();
996
997 mesh.start().unwrap();
998
999 assert!(rx1.try_recv().is_ok());
1001 assert!(rx2.try_recv().is_ok());
1002 }
1003
1004 #[test]
1007 fn test_set_transport() {
1008 let mut mesh = PeatMesh::new(MeshConfig::default());
1009 assert!(mesh.transport().is_none());
1010
1011 mesh.set_transport(Arc::new(MockTransport::empty()));
1012 assert!(mesh.transport().is_some());
1013 }
1014
1015 #[test]
1016 fn test_set_hierarchy() {
1017 use crate::beacon::HierarchyLevel;
1018 use crate::hierarchy::{NodeRole, StaticHierarchyStrategy};
1019
1020 let mut mesh = PeatMesh::new(MeshConfig::default());
1021 assert!(mesh.hierarchy().is_none());
1022
1023 let strategy = StaticHierarchyStrategy {
1024 assigned_level: HierarchyLevel::Platoon,
1025 assigned_role: NodeRole::Leader,
1026 };
1027 mesh.set_hierarchy(Arc::new(strategy));
1028 assert!(mesh.hierarchy().is_some());
1029 }
1030
1031 #[test]
1032 fn test_router_initially_none() {
1033 let mesh = PeatMesh::new(MeshConfig::default());
1034 assert!(mesh.router().is_none());
1035 }
1036
1037 #[test]
1040 fn test_mesh_state_display() {
1041 assert_eq!(MeshState::Created.to_string(), "created");
1042 assert_eq!(MeshState::Starting.to_string(), "starting");
1043 assert_eq!(MeshState::Running.to_string(), "running");
1044 assert_eq!(MeshState::Stopping.to_string(), "stopping");
1045 assert_eq!(MeshState::Stopped.to_string(), "stopped");
1046 }
1047
1048 #[test]
1049 fn test_mesh_state_equality() {
1050 assert_eq!(MeshState::Created, MeshState::Created);
1051 assert_ne!(MeshState::Created, MeshState::Running);
1052 }
1053
1054 #[test]
1055 fn test_mesh_state_clone_copy() {
1056 let s = MeshState::Running;
1057 let copied = s;
1058 assert_eq!(s, copied);
1060 }
1061
1062 #[test]
1063 fn test_mesh_state_debug() {
1064 let debug = format!("{:?}", MeshState::Running);
1065 assert!(debug.contains("Running"));
1066 }
1067
1068 #[test]
1071 fn test_mesh_error_display_not_running() {
1072 let err = MeshError::NotRunning;
1073 assert_eq!(err.to_string(), "mesh is not running");
1074 }
1075
1076 #[test]
1077 fn test_mesh_error_display_already_running() {
1078 let err = MeshError::AlreadyRunning;
1079 assert_eq!(err.to_string(), "mesh is already running");
1080 }
1081
1082 #[test]
1083 fn test_mesh_error_display_invalid_config() {
1084 let err = MeshError::InvalidConfig("bad value".to_string());
1085 assert_eq!(err.to_string(), "invalid configuration: bad value");
1086 }
1087
1088 #[test]
1089 fn test_mesh_error_display_transport() {
1090 let terr = TransportError::NotStarted;
1091 let err = MeshError::Transport(terr);
1092 assert!(err.to_string().contains("Transport not started"));
1093 }
1094
1095 #[test]
1096 fn test_mesh_error_display_other() {
1097 let err = MeshError::Other("something went wrong".to_string());
1098 assert_eq!(err.to_string(), "something went wrong");
1099 }
1100
1101 #[test]
1102 fn test_mesh_error_source_transport() {
1103 use std::error::Error;
1104 let terr = TransportError::ConnectionFailed("timeout".into());
1105 let err = MeshError::Transport(terr);
1106 assert!(err.source().is_some());
1107 }
1108
1109 #[test]
1110 fn test_mesh_error_source_none_for_others() {
1111 use std::error::Error;
1112 assert!(MeshError::NotRunning.source().is_none());
1113 assert!(MeshError::AlreadyRunning.source().is_none());
1114 assert!(MeshError::InvalidConfig("x".into()).source().is_none());
1115 assert!(MeshError::Other("x".into()).source().is_none());
1116 }
1117
1118 #[test]
1119 fn test_mesh_error_from_transport_error() {
1120 let terr = TransportError::NotStarted;
1121 let err: MeshError = terr.into();
1122 assert!(matches!(err, MeshError::Transport(_)));
1123 }
1124
1125 #[test]
1126 fn test_mesh_error_debug() {
1127 let err = MeshError::NotRunning;
1128 let debug = format!("{:?}", err);
1129 assert!(debug.contains("NotRunning"));
1130 }
1131
1132 #[test]
1135 fn test_event_state_changed() {
1136 let evt = PeatMeshEvent::StateChanged(MeshState::Running);
1137 let debug = format!("{:?}", evt);
1138 assert!(debug.contains("Running"));
1139 }
1140
1141 #[test]
1142 fn test_event_peer_joined() {
1143 let evt = PeatMeshEvent::PeerJoined(NodeId::new("peer-1".into()));
1144 let cloned = evt.clone();
1145 let debug = format!("{:?}", cloned);
1146 assert!(debug.contains("peer-1"));
1147 }
1148
1149 #[test]
1150 fn test_event_peer_left() {
1151 let evt = PeatMeshEvent::PeerLeft(NodeId::new("peer-2".into()));
1152 let cloned = evt.clone();
1153 let debug = format!("{:?}", cloned);
1154 assert!(debug.contains("peer-2"));
1155 }
1156
1157 #[test]
1158 fn test_event_topology_changed() {
1159 let topo_evt = crate::topology::TopologyEvent::PeerLost {
1160 lost_peer_id: "gone".to_string(),
1161 };
1162 let evt = PeatMeshEvent::TopologyChanged(Box::new(topo_evt));
1163 let cloned = evt.clone();
1164 let debug = format!("{:?}", cloned);
1165 assert!(debug.contains("gone"));
1166 }
1167
1168 #[test]
1171 fn test_mesh_status_debug() {
1172 let status = MeshStatus {
1173 state: MeshState::Running,
1174 peer_count: 5,
1175 node_id: "n1".to_string(),
1176 uptime: Duration::from_secs(120),
1177 };
1178 let debug = format!("{:?}", status);
1179 assert!(debug.contains("Running"));
1180 assert!(debug.contains("n1"));
1181 }
1182
1183 #[test]
1184 fn test_mesh_status_clone() {
1185 let status = MeshStatus {
1186 state: MeshState::Stopped,
1187 peer_count: 0,
1188 node_id: "n2".to_string(),
1189 uptime: Duration::ZERO,
1190 };
1191 let cloned = status.clone();
1192 assert_eq!(cloned.state, MeshState::Stopped);
1193 assert_eq!(cloned.node_id, "n2");
1194 }
1195
1196 #[test]
1199 fn test_builder_minimal() {
1200 let mesh = PeatMeshBuilder::new(MeshConfig::default()).build();
1201 assert_eq!(mesh.state(), MeshState::Created);
1202 assert!(mesh.transport().is_none());
1203 assert!(mesh.hierarchy().is_none());
1204 assert!(mesh.router().is_none());
1205 }
1206
1207 #[test]
1208 fn test_builder_with_node_id() {
1209 let cfg = MeshConfig {
1210 node_id: Some("builder-node".to_string()),
1211 ..Default::default()
1212 };
1213 let mesh = PeatMeshBuilder::new(cfg).build();
1214 assert_eq!(mesh.node_id(), "builder-node");
1215 }
1216
1217 #[test]
1218 fn test_builder_with_transport() {
1219 let mesh = PeatMeshBuilder::new(MeshConfig::default())
1220 .with_transport(Arc::new(MockTransport::empty()))
1221 .build();
1222 assert!(mesh.transport().is_some());
1223 }
1224
1225 #[test]
1226 fn test_builder_with_hierarchy() {
1227 use crate::beacon::HierarchyLevel;
1228 use crate::hierarchy::{NodeRole, StaticHierarchyStrategy};
1229
1230 let strategy = StaticHierarchyStrategy {
1231 assigned_level: HierarchyLevel::Squad,
1232 assigned_role: NodeRole::Member,
1233 };
1234 let mesh = PeatMeshBuilder::new(MeshConfig::default())
1235 .with_hierarchy(Arc::new(strategy))
1236 .build();
1237 assert!(mesh.hierarchy().is_some());
1238 }
1239
1240 #[test]
1241 fn test_builder_with_router() {
1242 let router = MeshRouter::with_node_id("test");
1243 let mesh = PeatMeshBuilder::new(MeshConfig::default())
1244 .with_router(router)
1245 .build();
1246 assert!(mesh.router().is_some());
1247 }
1248
1249 #[test]
1250 fn test_builder_all_subsystems() {
1251 use crate::beacon::HierarchyLevel;
1252 use crate::hierarchy::{NodeRole, StaticHierarchyStrategy};
1253
1254 let strategy = StaticHierarchyStrategy {
1255 assigned_level: HierarchyLevel::Platoon,
1256 assigned_role: NodeRole::Leader,
1257 };
1258 let peers = vec![NodeId::new("p1".into())];
1259 let router = MeshRouter::with_node_id("full");
1260
1261 let mesh = PeatMeshBuilder::new(MeshConfig {
1262 node_id: Some("full-node".to_string()),
1263 ..Default::default()
1264 })
1265 .with_transport(Arc::new(MockTransport::new(peers)))
1266 .with_hierarchy(Arc::new(strategy))
1267 .with_router(router)
1268 .build();
1269
1270 assert_eq!(mesh.node_id(), "full-node");
1271 assert!(mesh.transport().is_some());
1272 assert!(mesh.hierarchy().is_some());
1273 assert!(mesh.router().is_some());
1274 assert_eq!(mesh.status().peer_count, 1);
1275 }
1276
1277 #[test]
1278 fn test_builder_lifecycle() {
1279 let mesh = PeatMeshBuilder::new(MeshConfig::default()).build();
1280 assert!(mesh.start().is_ok());
1281 assert_eq!(mesh.state(), MeshState::Running);
1282 assert!(mesh.stop().is_ok());
1283 assert_eq!(mesh.state(), MeshState::Stopped);
1284 }
1285
1286 #[test]
1289 fn test_transport_manager_initially_none() {
1290 let mesh = PeatMesh::new(MeshConfig::default());
1291 assert!(mesh.transport_manager().is_none());
1292 }
1293
1294 #[test]
1295 fn test_set_transport_manager() {
1296 use crate::transport::TransportManagerConfig;
1297 let mut mesh = PeatMesh::new(MeshConfig::default());
1298 let tm = TransportManager::new(TransportManagerConfig::default());
1299 mesh.set_transport_manager(tm);
1300 assert!(mesh.transport_manager().is_some());
1301 }
1302
1303 #[test]
1304 fn test_builder_with_transport_manager() {
1305 use crate::transport::TransportManagerConfig;
1306 let tm = TransportManager::new(TransportManagerConfig::default());
1307 let mesh = PeatMeshBuilder::new(MeshConfig::default())
1308 .with_transport_manager(tm)
1309 .build();
1310 assert!(mesh.transport_manager().is_some());
1311 }
1312
1313 #[test]
1314 fn test_builder_full_with_transport_manager() {
1315 use crate::beacon::HierarchyLevel;
1316 use crate::hierarchy::{NodeRole, StaticHierarchyStrategy};
1317 use crate::transport::TransportManagerConfig;
1318
1319 let strategy = StaticHierarchyStrategy {
1320 assigned_level: HierarchyLevel::Platoon,
1321 assigned_role: NodeRole::Leader,
1322 };
1323 let peers = vec![NodeId::new("p1".into())];
1324 let router = MeshRouter::with_node_id("full");
1325 let tm = TransportManager::new(TransportManagerConfig::default());
1326
1327 let mesh = PeatMeshBuilder::new(MeshConfig {
1328 node_id: Some("full-tm-node".to_string()),
1329 ..Default::default()
1330 })
1331 .with_transport(Arc::new(MockTransport::new(peers)))
1332 .with_transport_manager(tm)
1333 .with_hierarchy(Arc::new(strategy))
1334 .with_router(router)
1335 .build();
1336
1337 assert_eq!(mesh.node_id(), "full-tm-node");
1338 assert!(mesh.transport().is_some());
1339 assert!(mesh.transport_manager().is_some());
1340 assert!(mesh.hierarchy().is_some());
1341 assert!(mesh.router().is_some());
1342 }
1343
1344 #[test]
1347 fn test_bandwidth_initially_none() {
1348 let mesh = PeatMesh::new(MeshConfig::default());
1349 assert!(mesh.bandwidth().is_none());
1350 }
1351
1352 #[test]
1353 fn test_set_bandwidth() {
1354 let mut mesh = PeatMesh::new(MeshConfig::default());
1355 mesh.set_bandwidth(crate::qos::BandwidthAllocation::new(1_000_000));
1356 assert!(mesh.bandwidth().is_some());
1357 }
1358
1359 #[test]
1360 fn test_preemption_initially_none() {
1361 let mesh = PeatMesh::new(MeshConfig::default());
1362 assert!(mesh.preemption().is_none());
1363 }
1364
1365 #[test]
1366 fn test_set_preemption() {
1367 let mut mesh = PeatMesh::new(MeshConfig::default());
1368 mesh.set_preemption(crate::qos::PreemptionController::new());
1369 assert!(mesh.preemption().is_some());
1370 }
1371
1372 #[test]
1373 fn test_builder_with_bandwidth() {
1374 let mesh = PeatMeshBuilder::new(MeshConfig::default())
1375 .with_bandwidth(crate::qos::BandwidthAllocation::default_tactical())
1376 .build();
1377 assert!(mesh.bandwidth().is_some());
1378 }
1379
1380 #[test]
1381 fn test_builder_with_preemption() {
1382 let mesh = PeatMeshBuilder::new(MeshConfig::default())
1383 .with_preemption(crate::qos::PreemptionController::new())
1384 .build();
1385 assert!(mesh.preemption().is_some());
1386 }
1387
1388 #[test]
1391 fn test_device_keypair_initially_none() {
1392 let mesh = PeatMesh::new(MeshConfig::default());
1393 assert!(mesh.device_keypair().is_none());
1394 }
1395
1396 #[test]
1397 fn test_set_device_keypair() {
1398 let mut mesh = PeatMesh::new(MeshConfig::default());
1399 mesh.set_device_keypair(crate::security::DeviceKeypair::generate());
1400 assert!(mesh.device_keypair().is_some());
1401 }
1402
1403 #[test]
1404 fn test_formation_key_initially_none() {
1405 let mesh = PeatMesh::new(MeshConfig::default());
1406 assert!(mesh.formation_key().is_none());
1407 }
1408
1409 #[test]
1410 fn test_set_formation_key() {
1411 let mut mesh = PeatMesh::new(MeshConfig::default());
1412 mesh.set_formation_key(crate::security::FormationKey::new(
1413 "test-formation",
1414 &[0u8; 32],
1415 ));
1416 assert!(mesh.formation_key().is_some());
1417 }
1418
1419 #[test]
1420 fn test_builder_with_device_keypair() {
1421 let mesh = PeatMeshBuilder::new(MeshConfig::default())
1422 .with_device_keypair(crate::security::DeviceKeypair::generate())
1423 .build();
1424 assert!(mesh.device_keypair().is_some());
1425 }
1426
1427 #[test]
1428 fn test_builder_with_device_keypair_from_seed() {
1429 let mesh = PeatMeshBuilder::new(MeshConfig::default())
1430 .with_device_keypair_from_seed(b"k8s-secret", "pod-1")
1431 .unwrap()
1432 .build();
1433 assert!(mesh.device_keypair().is_some());
1434
1435 let mesh2 = PeatMeshBuilder::new(MeshConfig::default())
1437 .with_device_keypair_from_seed(b"k8s-secret", "pod-1")
1438 .unwrap()
1439 .build();
1440 assert_eq!(
1441 mesh.device_keypair().unwrap().device_id(),
1442 mesh2.device_keypair().unwrap().device_id()
1443 );
1444 }
1445
1446 #[test]
1447 fn test_builder_with_formation_key() {
1448 let mesh = PeatMeshBuilder::new(MeshConfig::default())
1449 .with_formation_key(crate::security::FormationKey::new("f1", &[1u8; 32]))
1450 .build();
1451 assert!(mesh.formation_key().is_some());
1452 }
1453
1454 #[test]
1457 fn test_discovery_initially_none() {
1458 let mesh = PeatMesh::new(MeshConfig::default());
1459 assert!(mesh
1460 .discovery()
1461 .read()
1462 .unwrap_or_else(|e| e.into_inner())
1463 .is_none());
1464 }
1465
1466 #[test]
1467 fn test_set_discovery() {
1468 let mesh = PeatMesh::new(MeshConfig::default());
1469 let strategy = crate::discovery::HybridDiscovery::new();
1470 mesh.set_discovery(Box::new(strategy));
1471 assert!(mesh
1472 .discovery()
1473 .read()
1474 .unwrap_or_else(|e| e.into_inner())
1475 .is_some());
1476 }
1477
1478 #[test]
1479 fn test_builder_with_discovery() {
1480 let strategy = crate::discovery::HybridDiscovery::new();
1481 let mesh = PeatMeshBuilder::new(MeshConfig::default())
1482 .with_discovery(Box::new(strategy))
1483 .build();
1484 assert!(mesh
1485 .discovery()
1486 .read()
1487 .unwrap_or_else(|e| e.into_inner())
1488 .is_some());
1489 }
1490
1491 fn mock_storage() -> Arc<dyn crate::beacon::BeaconStorage> {
1494 Arc::new(crate::beacon::MockBeaconStorage::new())
1495 }
1496
1497 #[test]
1498 fn test_beacon_broadcaster_initially_none() {
1499 let mesh = PeatMesh::new(MeshConfig::default());
1500 assert!(mesh.beacon_broadcaster().is_none());
1501 }
1502
1503 #[test]
1504 fn test_set_beacon_broadcaster() {
1505 use crate::beacon::{BeaconBroadcaster, GeoPosition, HierarchyLevel};
1506
1507 let mut mesh = PeatMesh::new(MeshConfig::default());
1508 let bb = BeaconBroadcaster::new(
1509 mock_storage(),
1510 "test-node".to_string(),
1511 GeoPosition {
1512 lat: 0.0,
1513 lon: 0.0,
1514 alt: None,
1515 },
1516 HierarchyLevel::Squad,
1517 None,
1518 Duration::from_secs(5),
1519 );
1520 mesh.set_beacon_broadcaster(bb);
1521 assert!(mesh.beacon_broadcaster().is_some());
1522 }
1523
1524 #[test]
1525 fn test_beacon_observer_initially_none() {
1526 let mesh = PeatMesh::new(MeshConfig::default());
1527 assert!(mesh.beacon_observer().is_none());
1528 }
1529
1530 #[test]
1531 fn test_set_beacon_observer() {
1532 use crate::beacon::BeaconObserver;
1533
1534 let mut mesh = PeatMesh::new(MeshConfig::default());
1535 let bo = Arc::new(BeaconObserver::new(mock_storage(), "s00000".to_string()));
1536 mesh.set_beacon_observer(bo);
1537 assert!(mesh.beacon_observer().is_some());
1538 }
1539
1540 #[test]
1541 fn test_beacon_janitor_initially_none() {
1542 let mesh = PeatMesh::new(MeshConfig::default());
1543 assert!(mesh.beacon_janitor().is_none());
1544 }
1545
1546 #[test]
1547 fn test_set_beacon_janitor() {
1548 use crate::beacon::BeaconJanitor;
1549 use std::collections::HashMap;
1550
1551 let mut mesh = PeatMesh::new(MeshConfig::default());
1552 let nearby = Arc::new(tokio::sync::RwLock::new(HashMap::new()));
1553 let bj = BeaconJanitor::new(nearby, Duration::from_secs(60), Duration::from_secs(10));
1554 mesh.set_beacon_janitor(bj);
1555 assert!(mesh.beacon_janitor().is_some());
1556 }
1557
1558 #[test]
1559 fn test_builder_with_beacon_broadcaster() {
1560 use crate::beacon::{BeaconBroadcaster, GeoPosition, HierarchyLevel};
1561
1562 let bb = BeaconBroadcaster::new(
1563 mock_storage(),
1564 "builder-node".to_string(),
1565 GeoPosition {
1566 lat: 1.0,
1567 lon: 2.0,
1568 alt: None,
1569 },
1570 HierarchyLevel::Platoon,
1571 None,
1572 Duration::from_secs(5),
1573 );
1574 let mesh = PeatMeshBuilder::new(MeshConfig::default())
1575 .with_beacon_broadcaster(bb)
1576 .build();
1577 assert!(mesh.beacon_broadcaster().is_some());
1578 }
1579
1580 #[test]
1581 fn test_builder_with_beacon_observer() {
1582 use crate::beacon::BeaconObserver;
1583
1584 let bo = Arc::new(BeaconObserver::new(mock_storage(), "s00000".to_string()));
1585 let mesh = PeatMeshBuilder::new(MeshConfig::default())
1586 .with_beacon_observer(bo)
1587 .build();
1588 assert!(mesh.beacon_observer().is_some());
1589 }
1590
1591 #[test]
1592 fn test_builder_with_beacon_janitor() {
1593 use crate::beacon::BeaconJanitor;
1594 use std::collections::HashMap;
1595
1596 let nearby = Arc::new(tokio::sync::RwLock::new(HashMap::new()));
1597 let bj = BeaconJanitor::new(nearby, Duration::from_secs(60), Duration::from_secs(10));
1598 let mesh = PeatMeshBuilder::new(MeshConfig::default())
1599 .with_beacon_janitor(bj)
1600 .build();
1601 assert!(mesh.beacon_janitor().is_some());
1602 }
1603
1604 #[test]
1607 fn test_topology_manager_initially_none() {
1608 let mesh = PeatMesh::new(MeshConfig::default());
1609 assert!(mesh.topology_manager().is_none());
1610 }
1611
1612 #[test]
1613 fn test_set_topology_manager() {
1614 use crate::beacon::{BeaconObserver, GeoPosition, HierarchyLevel};
1615 use crate::topology::{TopologyBuilder, TopologyConfig, TopologyManager};
1616
1617 let mut mesh = PeatMesh::new(MeshConfig::default());
1618 let observer = Arc::new(BeaconObserver::new(mock_storage(), "s00000".to_string()));
1619 let builder = TopologyBuilder::new(
1620 TopologyConfig::default(),
1621 "topo-node".to_string(),
1622 GeoPosition {
1623 lat: 0.0,
1624 lon: 0.0,
1625 alt: None,
1626 },
1627 HierarchyLevel::Squad,
1628 None,
1629 observer,
1630 );
1631 let transport: Arc<dyn MeshTransport> = Arc::new(MockTransport::empty());
1632 let tm = TopologyManager::new(builder, transport);
1633 mesh.set_topology_manager(tm);
1634 assert!(mesh.topology_manager().is_some());
1635 }
1636
1637 #[test]
1638 fn test_builder_with_topology_manager() {
1639 use crate::beacon::{BeaconObserver, GeoPosition, HierarchyLevel};
1640 use crate::topology::{TopologyBuilder, TopologyConfig, TopologyManager};
1641
1642 let observer = Arc::new(BeaconObserver::new(mock_storage(), "s00000".to_string()));
1643 let builder = TopologyBuilder::new(
1644 TopologyConfig::default(),
1645 "topo-builder-node".to_string(),
1646 GeoPosition {
1647 lat: 0.0,
1648 lon: 0.0,
1649 alt: None,
1650 },
1651 HierarchyLevel::Squad,
1652 None,
1653 observer,
1654 );
1655 let transport: Arc<dyn MeshTransport> = Arc::new(MockTransport::empty());
1656 let tm = TopologyManager::new(builder, transport);
1657 let mesh = PeatMeshBuilder::new(MeshConfig::default())
1658 .with_topology_manager(tm)
1659 .build();
1660 assert!(mesh.topology_manager().is_some());
1661 }
1662
1663 #[test]
1666 fn test_child_token_not_cancelled_while_running() {
1667 let mesh = PeatMesh::new(MeshConfig::default());
1668 mesh.start().unwrap();
1669 let token = mesh.child_token();
1670 assert!(!token.is_cancelled());
1671 }
1672
1673 #[test]
1674 fn test_child_token_cancelled_after_stop() {
1675 let mesh = PeatMesh::new(MeshConfig::default());
1676 mesh.start().unwrap();
1677 let token = mesh.child_token();
1678 mesh.stop().unwrap();
1679 assert!(token.is_cancelled());
1680 }
1681
1682 #[test]
1683 fn test_child_token_reset_on_restart() {
1684 let mesh = PeatMesh::new(MeshConfig::default());
1685 mesh.start().unwrap();
1686 let first_token = mesh.child_token();
1687 mesh.stop().unwrap();
1688 assert!(first_token.is_cancelled());
1689
1690 mesh.start().unwrap();
1692 let second_token = mesh.child_token();
1693 assert!(!second_token.is_cancelled());
1694
1695 assert!(first_token.is_cancelled());
1697 }
1698}
1699
1700#[cfg(all(test, feature = "broker"))]
1703mod broker_tests {
1704 use super::*;
1705 use crate::broker::state::MeshBrokerState;
1706 use crate::config::MeshConfig;
1707
1708 #[test]
1709 fn test_broker_node_info() {
1710 let mesh = PeatMesh::new(MeshConfig {
1711 node_id: Some("broker-node".to_string()),
1712 ..Default::default()
1713 });
1714 let info = mesh.node_info();
1715 assert_eq!(info.node_id, "broker-node");
1716 assert_eq!(info.uptime_secs, 0);
1717 assert!(!info.version.is_empty());
1718 }
1719
1720 #[test]
1721 fn test_broker_node_info_with_uptime() {
1722 let mesh = PeatMesh::new(MeshConfig {
1723 node_id: Some("uptime-node".to_string()),
1724 ..Default::default()
1725 });
1726 mesh.start().unwrap();
1727 let info = mesh.node_info();
1728 assert_eq!(info.node_id, "uptime-node");
1729 }
1731
1732 #[tokio::test]
1733 async fn test_broker_list_peers_no_transport() {
1734 let mesh = PeatMesh::new(MeshConfig::default());
1735 let peers = mesh.list_peers().await;
1736 assert!(peers.is_empty());
1737 }
1738
1739 #[tokio::test]
1740 async fn test_broker_get_peer_no_transport() {
1741 let mesh = PeatMesh::new(MeshConfig::default());
1742 let peer = mesh.get_peer("unknown").await;
1743 assert!(peer.is_none());
1744 }
1745
1746 #[test]
1747 fn test_broker_topology() {
1748 let mesh = PeatMesh::new(MeshConfig::default());
1749 let topo = mesh.topology();
1750 assert_eq!(topo.peer_count, 0);
1751 assert_eq!(topo.role, "standalone");
1752 assert_eq!(topo.hierarchy_level, 0);
1753 }
1754
1755 #[test]
1756 fn test_broker_subscribe_events() {
1757 let mesh = PeatMesh::new(MeshConfig::default());
1758 let _rx = MeshBrokerState::subscribe_events(&mesh);
1759 }
1761
1762 #[test]
1763 fn test_broker_event_bridge() {
1764 use crate::broker::state::MeshEvent;
1765
1766 let mesh = PeatMesh::new(MeshConfig::default());
1767 let mut rx = MeshBrokerState::subscribe_events(&mesh);
1768
1769 mesh.emit_mesh_event(MeshEvent::PeerConnected {
1771 peer_id: "test-peer".into(),
1772 });
1773
1774 let event = rx.try_recv().unwrap();
1776 assert!(matches!(
1777 event,
1778 MeshEvent::PeerConnected { ref peer_id } if peer_id == "test-peer"
1779 ));
1780 }
1781
1782 #[test]
1783 fn test_broker_event_bridge_start_emits_topology() {
1784 use crate::broker::state::MeshEvent;
1785
1786 let mesh = PeatMesh::new(MeshConfig::default());
1787 let mut rx = MeshBrokerState::subscribe_events(&mesh);
1788
1789 mesh.start().unwrap();
1790
1791 let event = rx.try_recv().unwrap();
1792 assert!(matches!(
1793 event,
1794 MeshEvent::TopologyChanged { ref new_role, peer_count: 0 } if new_role == "standalone"
1795 ));
1796 }
1797
1798 #[test]
1799 fn test_broker_event_bridge_stop_emits_topology() {
1800 use crate::broker::state::MeshEvent;
1801
1802 let mesh = PeatMesh::new(MeshConfig::default());
1803 mesh.start().unwrap();
1804
1805 let mut rx = MeshBrokerState::subscribe_events(&mesh);
1806 mesh.stop().unwrap();
1807
1808 let event = rx.try_recv().unwrap();
1809 assert!(matches!(
1810 event,
1811 MeshEvent::TopologyChanged { ref new_role, peer_count: 0 } if new_role == "stopped"
1812 ));
1813 }
1814}