1use std::collections::{HashMap, VecDeque};
17
18#[derive(Clone, Debug, PartialEq, Eq, Hash)]
24pub enum EventTopic {
25 PeerConnected,
27 PeerDisconnected,
29 BlockReceived,
31 BlockRequested,
33 DhtLookup,
35 GossipMessage,
37 Custom(String),
39}
40
41impl EventTopic {
42 pub fn as_str(&self) -> &str {
44 match self {
45 Self::PeerConnected => "PeerConnected",
46 Self::PeerDisconnected => "PeerDisconnected",
47 Self::BlockReceived => "BlockReceived",
48 Self::BlockRequested => "BlockRequested",
49 Self::DhtLookup => "DhtLookup",
50 Self::GossipMessage => "GossipMessage",
51 Self::Custom(tag) => tag.as_str(),
52 }
53 }
54}
55
56impl std::fmt::Display for EventTopic {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 f.write_str(self.as_str())
59 }
60}
61
62#[derive(Clone, Debug, PartialEq, Eq)]
69pub struct NebNetworkEvent {
70 pub id: u64,
72 pub topic: EventTopic,
74 pub payload: Vec<u8>,
76 pub source_peer: Option<String>,
78 pub timestamp: u64,
81}
82
83#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
92pub struct SubscriberId(pub u64);
93
94impl std::fmt::Display for SubscriberId {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 write!(f, "Sub({})", self.0)
97 }
98}
99
100#[derive(Clone, Debug)]
106pub enum EventFilter {
107 All,
109 TopicIn(Vec<EventTopic>),
111 FromPeer(String),
113 PayloadSizeAbove(usize),
115}
116
117#[derive(Clone, Debug)]
123pub struct NebSubscription {
124 pub id: SubscriberId,
126 pub filter: EventFilter,
128 pub events_received: u64,
131 pub last_event_at: u64,
134}
135
136#[derive(Clone, Debug)]
142pub struct EventBusConfig {
143 pub max_subscribers: usize,
146 pub replay_buffer_size: usize,
149 pub max_payload_bytes: usize,
152}
153
154impl Default for EventBusConfig {
155 fn default() -> Self {
156 Self {
157 max_subscribers: 1_000,
158 replay_buffer_size: 10_000,
159 max_payload_bytes: 1_048_576, }
161 }
162}
163
164#[derive(Clone, Debug, PartialEq, Eq)]
170pub enum BusError {
171 SubscriberNotFound(u64),
173 PayloadTooLarge {
175 size: usize,
177 max: usize,
179 },
180 MaxSubscribersReached,
182}
183
184impl std::fmt::Display for BusError {
185 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186 match self {
187 Self::SubscriberNotFound(id) => {
188 write!(f, "subscriber {id} not found")
189 }
190 Self::PayloadTooLarge { size, max } => {
191 write!(f, "payload too large: {size} bytes (max {max})")
192 }
193 Self::MaxSubscribersReached => {
194 write!(f, "maximum number of subscribers reached")
195 }
196 }
197 }
198}
199
200impl std::error::Error for BusError {}
201
202#[derive(Clone, Debug)]
208pub struct EventBusStats {
209 pub subscriber_count: usize,
211 pub replay_buffer_size: usize,
213 pub total_published: u64,
215 pub total_delivered: u64,
217 pub avg_delivery_per_event: f64,
220}
221
222pub struct NetworkEventBus {
261 pub config: EventBusConfig,
263 pub subscriptions: HashMap<u64, NebSubscription>,
265 pub replay_buffer: VecDeque<NebNetworkEvent>,
267 pub next_event_id: u64,
269 pub next_sub_id: u64,
271 pub total_published: u64,
273 pub total_delivered: u64,
275}
276
277impl NetworkEventBus {
278 pub fn new(config: EventBusConfig) -> Self {
284 Self {
287 replay_buffer: VecDeque::with_capacity(config.replay_buffer_size.min(4096)),
288 config,
289 subscriptions: HashMap::new(),
290 next_event_id: 1,
291 next_sub_id: 1,
292 total_published: 0,
293 total_delivered: 0,
294 }
295 }
296
297 pub fn matches_filter(filter: &EventFilter, event: &NebNetworkEvent) -> bool {
303 match filter {
304 EventFilter::All => true,
305 EventFilter::TopicIn(topics) => topics.contains(&event.topic),
306 EventFilter::FromPeer(peer) => event.source_peer.as_deref() == Some(peer.as_str()),
307 EventFilter::PayloadSizeAbove(n) => event.payload.len() > *n,
308 }
309 }
310
311 pub fn subscribe(&mut self, filter: EventFilter) -> SubscriberId {
321 if self.subscriptions.len() >= self.config.max_subscribers {
322 return SubscriberId(0);
323 }
324 let id = self.next_sub_id;
325 self.next_sub_id = self.next_sub_id.saturating_add(1);
326 let sub = NebSubscription {
327 id: SubscriberId(id),
328 filter,
329 events_received: 0,
330 last_event_at: 0,
331 };
332 self.subscriptions.insert(id, sub);
333 SubscriberId(id)
334 }
335
336 pub fn unsubscribe(&mut self, id: SubscriberId) -> bool {
341 self.subscriptions.remove(&id.0).is_some()
342 }
343
344 pub fn subscriber_stats(&self, id: SubscriberId) -> Option<&NebSubscription> {
347 self.subscriptions.get(&id.0)
348 }
349
350 pub fn publish(
364 &mut self,
365 topic: EventTopic,
366 payload: Vec<u8>,
367 source_peer: Option<String>,
368 now: u64,
369 ) -> Result<u64, BusError> {
370 if payload.len() > self.config.max_payload_bytes {
372 return Err(BusError::PayloadTooLarge {
373 size: payload.len(),
374 max: self.config.max_payload_bytes,
375 });
376 }
377
378 let event_id = self.next_event_id;
380 self.next_event_id = self.next_event_id.saturating_add(1);
381
382 let event = NebNetworkEvent {
383 id: event_id,
384 topic,
385 payload,
386 source_peer,
387 timestamp: now,
388 };
389
390 let mut delivered_count: u64 = 0;
392 for sub in self.subscriptions.values_mut() {
393 if Self::matches_filter(&sub.filter, &event) {
394 sub.events_received = sub.events_received.saturating_add(1);
395 sub.last_event_at = now;
396 delivered_count = delivered_count.saturating_add(1);
397 }
398 }
399 self.total_delivered = self.total_delivered.saturating_add(delivered_count);
400
401 if self.config.replay_buffer_size > 0 {
404 if self.replay_buffer.len() >= self.config.replay_buffer_size {
405 self.replay_buffer.pop_front();
406 }
407 self.replay_buffer.push_back(event);
408 }
409
410 self.total_published = self.total_published.saturating_add(1);
411 Ok(event_id)
412 }
413
414 pub fn replay_for_subscriber(
426 &self,
427 id: SubscriberId,
428 since_event_id: u64,
429 ) -> Result<Vec<&NebNetworkEvent>, BusError> {
430 let sub = self
431 .subscriptions
432 .get(&id.0)
433 .ok_or(BusError::SubscriberNotFound(id.0))?;
434
435 let events = self
436 .replay_buffer
437 .iter()
438 .filter(|e| e.id > since_event_id && Self::matches_filter(&sub.filter, e))
439 .collect();
440
441 Ok(events)
442 }
443
444 pub fn drain_events_for(&mut self, id: SubscriberId) -> Result<Vec<NebNetworkEvent>, BusError> {
452 let filter = {
455 let sub = self
456 .subscriptions
457 .get(&id.0)
458 .ok_or(BusError::SubscriberNotFound(id.0))?;
459 sub.filter.clone()
460 };
461
462 let events: Vec<NebNetworkEvent> = self
463 .replay_buffer
464 .iter()
465 .filter(|e| Self::matches_filter(&filter, e))
466 .cloned()
467 .collect();
468
469 Ok(events)
470 }
471
472 pub fn event_bus_stats(&self) -> EventBusStats {
478 let avg_delivery_per_event = if self.total_published == 0 {
479 0.0_f64
480 } else {
481 self.total_delivered as f64 / self.total_published as f64
482 };
483
484 EventBusStats {
485 subscriber_count: self.subscriptions.len(),
486 replay_buffer_size: self.replay_buffer.len(),
487 total_published: self.total_published,
488 total_delivered: self.total_delivered,
489 avg_delivery_per_event,
490 }
491 }
492}
493
494impl Default for NetworkEventBus {
495 fn default() -> Self {
496 Self::new(EventBusConfig::default())
497 }
498}
499
500#[cfg(test)]
505mod tests {
506 use super::{
507 BusError, EventBusConfig, EventFilter, EventTopic, NebSubscription, NetworkEventBus,
508 SubscriberId,
509 };
510
511 fn make_bus() -> NetworkEventBus {
516 NetworkEventBus::new(EventBusConfig::default())
517 }
518
519 fn make_small_bus(max_subs: usize, buf: usize, max_bytes: usize) -> NetworkEventBus {
520 NetworkEventBus::new(EventBusConfig {
521 max_subscribers: max_subs,
522 replay_buffer_size: buf,
523 max_payload_bytes: max_bytes,
524 })
525 }
526
527 fn publish_peer_connected(bus: &mut NetworkEventBus, peer: &str, now: u64) -> u64 {
528 bus.publish(
529 EventTopic::PeerConnected,
530 peer.as_bytes().to_vec(),
531 Some(peer.to_string()),
532 now,
533 )
534 .expect("publish failed")
535 }
536
537 #[test]
542 fn test_event_topic_as_str_named_variants() {
543 assert_eq!(EventTopic::PeerConnected.as_str(), "PeerConnected");
544 assert_eq!(EventTopic::PeerDisconnected.as_str(), "PeerDisconnected");
545 assert_eq!(EventTopic::BlockReceived.as_str(), "BlockReceived");
546 assert_eq!(EventTopic::BlockRequested.as_str(), "BlockRequested");
547 assert_eq!(EventTopic::DhtLookup.as_str(), "DhtLookup");
548 assert_eq!(EventTopic::GossipMessage.as_str(), "GossipMessage");
549 }
550
551 #[test]
552 fn test_event_topic_custom_as_str() {
553 let tag = "my-custom-event";
554 let topic = EventTopic::Custom(tag.to_string());
555 assert_eq!(topic.as_str(), tag);
556 }
557
558 #[test]
559 fn test_event_topic_display() {
560 let s = format!("{}", EventTopic::BlockReceived);
561 assert_eq!(s, "BlockReceived");
562 }
563
564 #[test]
565 fn test_event_topic_equality() {
566 assert_eq!(EventTopic::PeerConnected, EventTopic::PeerConnected);
567 assert_ne!(EventTopic::PeerConnected, EventTopic::PeerDisconnected);
568 assert_eq!(
569 EventTopic::Custom("x".to_string()),
570 EventTopic::Custom("x".to_string())
571 );
572 assert_ne!(
573 EventTopic::Custom("x".to_string()),
574 EventTopic::Custom("y".to_string())
575 );
576 }
577
578 #[test]
583 fn test_subscriber_id_display() {
584 let s = format!("{}", SubscriberId(42));
585 assert_eq!(s, "Sub(42)");
586 }
587
588 #[test]
589 fn test_subscriber_id_equality() {
590 assert_eq!(SubscriberId(1), SubscriberId(1));
591 assert_ne!(SubscriberId(1), SubscriberId(2));
592 }
593
594 #[test]
599 fn test_subscribe_returns_non_zero_id() {
600 let mut bus = make_bus();
601 let id = bus.subscribe(EventFilter::All);
602 assert_ne!(
603 id,
604 SubscriberId(0),
605 "sentinel value should not be returned on success"
606 );
607 }
608
609 #[test]
610 fn test_subscribe_ids_are_monotonically_increasing() {
611 let mut bus = make_bus();
612 let id1 = bus.subscribe(EventFilter::All);
613 let id2 = bus.subscribe(EventFilter::All);
614 let id3 = bus.subscribe(EventFilter::All);
615 assert!(id1.0 < id2.0);
616 assert!(id2.0 < id3.0);
617 }
618
619 #[test]
620 fn test_subscribe_at_capacity_returns_sentinel() {
621 let mut bus = make_small_bus(2, 100, 1024);
622 let id1 = bus.subscribe(EventFilter::All);
623 let id2 = bus.subscribe(EventFilter::All);
624 let id3 = bus.subscribe(EventFilter::All);
626 assert_ne!(id1, SubscriberId(0));
627 assert_ne!(id2, SubscriberId(0));
628 assert_eq!(
629 id3,
630 SubscriberId(0),
631 "should return sentinel when at capacity"
632 );
633 }
634
635 #[test]
636 fn test_subscribe_after_unsubscribe_frees_slot() {
637 let mut bus = make_small_bus(2, 100, 1024);
638 let id1 = bus.subscribe(EventFilter::All);
639 let _id2 = bus.subscribe(EventFilter::All);
640 assert!(bus.unsubscribe(id1));
642 let id3 = bus.subscribe(EventFilter::All);
644 assert_ne!(id3, SubscriberId(0));
645 }
646
647 #[test]
648 fn test_unsubscribe_unknown_id_returns_false() {
649 let mut bus = make_bus();
650 assert!(!bus.unsubscribe(SubscriberId(999)));
651 }
652
653 #[test]
654 fn test_unsubscribe_twice_returns_false_second_time() {
655 let mut bus = make_bus();
656 let id = bus.subscribe(EventFilter::All);
657 assert!(bus.unsubscribe(id));
658 assert!(!bus.unsubscribe(id));
659 }
660
661 fn make_event(
666 id: u64,
667 topic: EventTopic,
668 payload: Vec<u8>,
669 source_peer: Option<&str>,
670 ) -> super::NebNetworkEvent {
671 super::NebNetworkEvent {
672 id,
673 topic,
674 payload,
675 source_peer: source_peer.map(|s| s.to_string()),
676 timestamp: 0,
677 }
678 }
679
680 #[test]
681 fn test_filter_all_matches_any_event() {
682 let event = make_event(1, EventTopic::BlockReceived, vec![1, 2, 3], None);
683 assert!(NetworkEventBus::matches_filter(&EventFilter::All, &event));
684 }
685
686 #[test]
687 fn test_filter_topic_in_matches_included_topic() {
688 let event = make_event(1, EventTopic::PeerConnected, vec![], None);
689 let filter = EventFilter::TopicIn(vec![EventTopic::PeerConnected, EventTopic::DhtLookup]);
690 assert!(NetworkEventBus::matches_filter(&filter, &event));
691 }
692
693 #[test]
694 fn test_filter_topic_in_rejects_excluded_topic() {
695 let event = make_event(1, EventTopic::GossipMessage, vec![], None);
696 let filter = EventFilter::TopicIn(vec![EventTopic::PeerConnected]);
697 assert!(!NetworkEventBus::matches_filter(&filter, &event));
698 }
699
700 #[test]
701 fn test_filter_from_peer_matches_correct_peer() {
702 let event = make_event(1, EventTopic::BlockReceived, vec![], Some("alice"));
703 let filter = EventFilter::FromPeer("alice".to_string());
704 assert!(NetworkEventBus::matches_filter(&filter, &event));
705 }
706
707 #[test]
708 fn test_filter_from_peer_rejects_wrong_peer() {
709 let event = make_event(1, EventTopic::BlockReceived, vec![], Some("bob"));
710 let filter = EventFilter::FromPeer("alice".to_string());
711 assert!(!NetworkEventBus::matches_filter(&filter, &event));
712 }
713
714 #[test]
715 fn test_filter_from_peer_rejects_no_peer() {
716 let event = make_event(1, EventTopic::BlockReceived, vec![], None);
717 let filter = EventFilter::FromPeer("alice".to_string());
718 assert!(!NetworkEventBus::matches_filter(&filter, &event));
719 }
720
721 #[test]
722 fn test_filter_payload_size_above_matches_larger_payload() {
723 let event = make_event(1, EventTopic::BlockReceived, vec![0u8; 100], None);
724 let filter = EventFilter::PayloadSizeAbove(50);
725 assert!(NetworkEventBus::matches_filter(&filter, &event));
726 }
727
728 #[test]
729 fn test_filter_payload_size_above_rejects_equal_size() {
730 let event = make_event(1, EventTopic::BlockReceived, vec![0u8; 50], None);
732 let filter = EventFilter::PayloadSizeAbove(50);
733 assert!(!NetworkEventBus::matches_filter(&filter, &event));
734 }
735
736 #[test]
737 fn test_filter_payload_size_above_rejects_smaller_payload() {
738 let event = make_event(1, EventTopic::BlockReceived, vec![0u8; 10], None);
739 let filter = EventFilter::PayloadSizeAbove(50);
740 assert!(!NetworkEventBus::matches_filter(&filter, &event));
741 }
742
743 #[test]
748 fn test_publish_returns_monotonically_increasing_ids() {
749 let mut bus = make_bus();
750 let id1 = bus
751 .publish(EventTopic::PeerConnected, vec![], None, 1)
752 .expect("publish failed");
753 let id2 = bus
754 .publish(EventTopic::PeerDisconnected, vec![], None, 2)
755 .expect("publish failed");
756 assert!(id1 < id2);
757 }
758
759 #[test]
760 fn test_publish_rejects_oversized_payload() {
761 let mut bus = make_small_bus(100, 100, 10);
762 let result = bus.publish(EventTopic::BlockReceived, vec![0u8; 11], None, 0);
763 assert_eq!(result, Err(BusError::PayloadTooLarge { size: 11, max: 10 }));
764 }
765
766 #[test]
767 fn test_publish_accepts_max_size_payload() {
768 let mut bus = make_small_bus(100, 100, 10);
769 let result = bus.publish(EventTopic::BlockReceived, vec![0u8; 10], None, 0);
770 assert!(result.is_ok());
771 }
772
773 #[test]
774 fn test_publish_increments_total_published() {
775 let mut bus = make_bus();
776 bus.publish(EventTopic::PeerConnected, vec![], None, 0)
777 .expect("publish failed");
778 bus.publish(EventTopic::PeerConnected, vec![], None, 1)
779 .expect("publish failed");
780 assert_eq!(bus.total_published, 2);
781 }
782
783 #[test]
784 fn test_publish_increments_delivered_for_matching_subscribers() {
785 let mut bus = make_bus();
786 let _sub = bus.subscribe(EventFilter::All);
787 bus.publish(EventTopic::PeerConnected, vec![], None, 0)
788 .expect("publish failed");
789 bus.publish(EventTopic::PeerConnected, vec![], None, 1)
790 .expect("publish failed");
791 assert_eq!(bus.total_delivered, 2);
792 }
793
794 #[test]
795 fn test_publish_does_not_count_non_matching_subscribers() {
796 let mut bus = make_bus();
797 let _sub = bus.subscribe(EventFilter::TopicIn(vec![EventTopic::DhtLookup]));
799 bus.publish(EventTopic::PeerConnected, vec![], None, 0)
801 .expect("publish failed");
802 assert_eq!(bus.total_delivered, 0);
803 }
804
805 #[test]
806 fn test_publish_updates_subscriber_events_received() {
807 let mut bus = make_bus();
808 let id = bus.subscribe(EventFilter::All);
809 publish_peer_connected(&mut bus, "alice", 100);
810 publish_peer_connected(&mut bus, "bob", 200);
811 let stats = bus.subscriber_stats(id).expect("stats missing");
812 assert_eq!(stats.events_received, 2);
813 }
814
815 #[test]
816 fn test_publish_updates_subscriber_last_event_at() {
817 let mut bus = make_bus();
818 let id = bus.subscribe(EventFilter::All);
819 publish_peer_connected(&mut bus, "alice", 100);
820 publish_peer_connected(&mut bus, "bob", 200);
821 let stats = bus.subscriber_stats(id).expect("stats missing");
822 assert_eq!(stats.last_event_at, 200);
823 }
824
825 #[test]
826 fn test_publish_fills_replay_buffer() {
827 let mut bus = make_small_bus(100, 5, 1024);
828 for i in 0..5u64 {
829 bus.publish(EventTopic::PeerConnected, vec![], None, i)
830 .expect("publish failed");
831 }
832 assert_eq!(bus.replay_buffer.len(), 5);
833 }
834
835 #[test]
836 fn test_publish_evicts_oldest_when_buffer_full() {
837 let mut bus = make_small_bus(100, 3, 1024);
838 let id1 = bus
839 .publish(EventTopic::PeerConnected, vec![], None, 1)
840 .expect("publish failed");
841 bus.publish(EventTopic::PeerConnected, vec![], None, 2)
842 .expect("publish failed");
843 bus.publish(EventTopic::PeerConnected, vec![], None, 3)
844 .expect("publish failed");
845 bus.publish(EventTopic::PeerConnected, vec![], None, 4)
847 .expect("publish failed");
848
849 assert_eq!(bus.replay_buffer.len(), 3);
850 let oldest_in_buffer = bus.replay_buffer.front().expect("buffer empty").id;
852 assert_ne!(oldest_in_buffer, id1);
853 }
854
855 #[test]
860 fn test_replay_for_subscriber_unknown_id_returns_error() {
861 let bus = make_bus();
862 let result = bus.replay_for_subscriber(SubscriberId(999), 0);
863 assert_eq!(result, Err(BusError::SubscriberNotFound(999)));
864 }
865
866 #[test]
867 fn test_replay_for_subscriber_returns_events_after_given_id() {
868 let mut bus = make_bus();
869 let sub = bus.subscribe(EventFilter::All);
870 let id1 = publish_peer_connected(&mut bus, "a", 1);
872 let _id2 = publish_peer_connected(&mut bus, "b", 2);
873 let _id3 = publish_peer_connected(&mut bus, "c", 3);
874 let replayed = bus.replay_for_subscriber(sub, id1).expect("replay failed");
876 assert_eq!(replayed.len(), 2);
877 }
878
879 #[test]
880 fn test_replay_for_subscriber_returns_empty_when_up_to_date() {
881 let mut bus = make_bus();
882 let sub = bus.subscribe(EventFilter::All);
883 let id1 = publish_peer_connected(&mut bus, "a", 1);
884 let replayed = bus.replay_for_subscriber(sub, id1).expect("replay failed");
885 assert!(replayed.is_empty());
886 }
887
888 #[test]
889 fn test_replay_for_subscriber_respects_filter() {
890 let mut bus = make_bus();
891 let sub = bus.subscribe(EventFilter::TopicIn(vec![EventTopic::BlockReceived]));
892 bus.publish(EventTopic::PeerConnected, vec![], None, 1)
894 .expect("publish failed");
895 bus.publish(EventTopic::BlockReceived, vec![], None, 2)
896 .expect("publish failed");
897 bus.publish(EventTopic::PeerConnected, vec![], None, 3)
898 .expect("publish failed");
899 let replayed = bus.replay_for_subscriber(sub, 0).expect("replay failed");
901 assert_eq!(replayed.len(), 1);
902 assert_eq!(replayed[0].topic, EventTopic::BlockReceived);
903 }
904
905 #[test]
910 fn test_drain_events_for_unknown_id_returns_error() {
911 let mut bus = make_bus();
912 let result = bus.drain_events_for(SubscriberId(999));
913 assert_eq!(result, Err(BusError::SubscriberNotFound(999)));
914 }
915
916 #[test]
917 fn test_drain_events_for_returns_matching_events() {
918 let mut bus = make_bus();
919 let sub = bus.subscribe(EventFilter::All);
920 publish_peer_connected(&mut bus, "x", 1);
921 publish_peer_connected(&mut bus, "y", 2);
922 let drained = bus.drain_events_for(sub).expect("drain failed");
923 assert_eq!(drained.len(), 2);
924 }
925
926 #[test]
927 fn test_drain_events_for_does_not_remove_from_buffer() {
928 let mut bus = make_bus();
929 let sub = bus.subscribe(EventFilter::All);
930 publish_peer_connected(&mut bus, "x", 1);
931 let _first = bus.drain_events_for(sub).expect("drain failed");
932 let second = bus.drain_events_for(sub).expect("drain failed");
934 assert_eq!(second.len(), 1);
935 }
936
937 #[test]
938 fn test_drain_events_for_respects_filter() {
939 let mut bus = make_bus();
940 let sub = bus.subscribe(EventFilter::FromPeer("alice".to_string()));
941 bus.publish(
942 EventTopic::PeerConnected,
943 vec![],
944 Some("alice".to_string()),
945 1,
946 )
947 .expect("publish failed");
948 bus.publish(
949 EventTopic::PeerConnected,
950 vec![],
951 Some("bob".to_string()),
952 2,
953 )
954 .expect("publish failed");
955 let drained = bus.drain_events_for(sub).expect("drain failed");
956 assert_eq!(drained.len(), 1);
957 assert_eq!(drained[0].source_peer.as_deref(), Some("alice"));
958 }
959
960 #[test]
965 fn test_subscriber_stats_none_for_unknown_id() {
966 let bus = make_bus();
967 assert!(bus.subscriber_stats(SubscriberId(42)).is_none());
968 }
969
970 #[test]
971 fn test_subscriber_stats_some_for_known_id() {
972 let mut bus = make_bus();
973 let id = bus.subscribe(EventFilter::All);
974 assert!(bus.subscriber_stats(id).is_some());
975 }
976
977 #[test]
978 fn test_subscriber_stats_initial_values() {
979 let mut bus = make_bus();
980 let id = bus.subscribe(EventFilter::All);
981 let stats: &NebSubscription = bus.subscriber_stats(id).expect("stats missing");
982 assert_eq!(stats.events_received, 0);
983 assert_eq!(stats.last_event_at, 0);
984 assert_eq!(stats.id, id);
985 }
986
987 #[test]
992 fn test_event_bus_stats_initial_state() {
993 let bus = make_bus();
994 let stats = bus.event_bus_stats();
995 assert_eq!(stats.subscriber_count, 0);
996 assert_eq!(stats.replay_buffer_size, 0);
997 assert_eq!(stats.total_published, 0);
998 assert_eq!(stats.total_delivered, 0);
999 assert_eq!(stats.avg_delivery_per_event, 0.0);
1000 }
1001
1002 #[test]
1003 fn test_event_bus_stats_counts_subscribers() {
1004 let mut bus = make_bus();
1005 bus.subscribe(EventFilter::All);
1006 bus.subscribe(EventFilter::All);
1007 let stats = bus.event_bus_stats();
1008 assert_eq!(stats.subscriber_count, 2);
1009 }
1010
1011 #[test]
1012 fn test_event_bus_stats_counts_buffer_size() {
1013 let mut bus = make_bus();
1014 publish_peer_connected(&mut bus, "a", 1);
1015 publish_peer_connected(&mut bus, "b", 2);
1016 let stats = bus.event_bus_stats();
1017 assert_eq!(stats.replay_buffer_size, 2);
1018 }
1019
1020 #[test]
1021 fn test_event_bus_stats_avg_delivery_per_event() {
1022 let mut bus = make_bus();
1023 bus.subscribe(EventFilter::All);
1025 bus.subscribe(EventFilter::TopicIn(vec![EventTopic::PeerConnected]));
1026 bus.publish(EventTopic::PeerConnected, vec![], None, 1)
1028 .expect("publish failed");
1029 let stats = bus.event_bus_stats();
1030 assert_eq!(stats.total_published, 1);
1031 assert_eq!(stats.total_delivered, 2);
1032 assert!((stats.avg_delivery_per_event - 2.0_f64).abs() < f64::EPSILON);
1033 }
1034
1035 #[test]
1036 fn test_event_bus_stats_avg_delivery_zero_when_no_events() {
1037 let bus = make_bus();
1038 let stats = bus.event_bus_stats();
1039 assert_eq!(stats.avg_delivery_per_event, 0.0);
1040 }
1041
1042 #[test]
1047 fn test_bus_error_subscriber_not_found_display() {
1048 let e = BusError::SubscriberNotFound(7);
1049 assert!(e.to_string().contains("7"));
1050 }
1051
1052 #[test]
1053 fn test_bus_error_payload_too_large_display() {
1054 let e = BusError::PayloadTooLarge {
1055 size: 2000,
1056 max: 1000,
1057 };
1058 let s = e.to_string();
1059 assert!(s.contains("2000"));
1060 assert!(s.contains("1000"));
1061 }
1062
1063 #[test]
1064 fn test_bus_error_max_subscribers_reached_display() {
1065 let e = BusError::MaxSubscribersReached;
1066 assert!(!e.to_string().is_empty());
1067 }
1068
1069 #[test]
1070 fn test_bus_error_equality() {
1071 assert_eq!(
1072 BusError::SubscriberNotFound(1),
1073 BusError::SubscriberNotFound(1)
1074 );
1075 assert_ne!(
1076 BusError::SubscriberNotFound(1),
1077 BusError::SubscriberNotFound(2)
1078 );
1079 assert_eq!(
1080 BusError::PayloadTooLarge { size: 5, max: 4 },
1081 BusError::PayloadTooLarge { size: 5, max: 4 }
1082 );
1083 assert_eq!(
1084 BusError::MaxSubscribersReached,
1085 BusError::MaxSubscribersReached
1086 );
1087 }
1088
1089 #[test]
1094 fn test_default_bus_uses_default_config() {
1095 let bus = NetworkEventBus::default();
1096 assert_eq!(bus.config.max_subscribers, 1_000);
1097 assert_eq!(bus.config.replay_buffer_size, 10_000);
1098 assert_eq!(bus.config.max_payload_bytes, 1_048_576);
1099 }
1100
1101 #[test]
1106 fn test_multiple_subscribers_with_different_filters() {
1107 let mut bus = make_bus();
1108 let sub_all = bus.subscribe(EventFilter::All);
1109 let sub_dht = bus.subscribe(EventFilter::TopicIn(vec![EventTopic::DhtLookup]));
1110
1111 bus.publish(EventTopic::PeerConnected, vec![], None, 1)
1112 .expect("publish failed");
1113 bus.publish(EventTopic::DhtLookup, vec![], None, 2)
1114 .expect("publish failed");
1115
1116 let all_stats = bus.subscriber_stats(sub_all).expect("stats");
1117 let dht_stats = bus.subscriber_stats(sub_dht).expect("stats");
1118 assert_eq!(all_stats.events_received, 2);
1119 assert_eq!(dht_stats.events_received, 1);
1120 }
1121
1122 #[test]
1123 fn test_custom_topic_filtering() {
1124 let mut bus = make_bus();
1125 let sub = bus.subscribe(EventFilter::TopicIn(vec![EventTopic::Custom(
1126 "app::hello".to_string(),
1127 )]));
1128 bus.publish(
1129 EventTopic::Custom("app::hello".to_string()),
1130 vec![],
1131 None,
1132 1,
1133 )
1134 .expect("publish failed");
1135 bus.publish(
1136 EventTopic::Custom("app::world".to_string()),
1137 vec![],
1138 None,
1139 2,
1140 )
1141 .expect("publish failed");
1142 let stats = bus.subscriber_stats(sub).expect("stats");
1143 assert_eq!(stats.events_received, 1);
1144 }
1145
1146 #[test]
1147 fn test_publish_rejected_payload_not_counted() {
1148 let mut bus = make_small_bus(100, 100, 5);
1149 let _ = bus.publish(EventTopic::BlockReceived, vec![0u8; 10], None, 0);
1150 assert_eq!(bus.total_published, 0);
1151 }
1152
1153 #[test]
1154 fn test_subscriber_receives_zero_events_when_filter_never_matches() {
1155 let mut bus = make_bus();
1156 let sub = bus.subscribe(EventFilter::FromPeer("ghost".to_string()));
1157 for i in 0..10u64 {
1158 bus.publish(
1159 EventTopic::PeerConnected,
1160 vec![],
1161 Some("real-peer".to_string()),
1162 i,
1163 )
1164 .expect("publish failed");
1165 }
1166 let stats = bus.subscriber_stats(sub).expect("stats");
1167 assert_eq!(stats.events_received, 0);
1168 }
1169
1170 #[test]
1171 fn test_replay_buffer_size_with_zero_capacity() {
1172 let mut bus = make_small_bus(100, 0, 1024);
1174 let sub = bus.subscribe(EventFilter::All);
1175 bus.publish(EventTopic::PeerConnected, vec![], None, 1)
1176 .expect("publish failed");
1177 let replayed = bus.replay_for_subscriber(sub, 0).expect("replay failed");
1178 assert!(replayed.is_empty(), "buffer size 0 should retain no events");
1179 }
1180}