1use tokio::sync::broadcast;
8
9use ironflow_engine::notify::{Event, EventSubscriber, SubscriberFuture};
10
11const DEFAULT_CAPACITY: usize = 256;
13
14pub struct SseBroadcaster {
30 sender: broadcast::Sender<Event>,
31}
32
33impl SseBroadcaster {
34 pub fn new() -> Self {
44 Self::with_capacity(DEFAULT_CAPACITY)
45 }
46
47 pub fn with_capacity(capacity: usize) -> Self {
57 let (sender, _) = broadcast::channel(capacity);
58 Self { sender }
59 }
60
61 pub fn subscribe(&self) -> broadcast::Receiver<Event> {
65 self.sender.subscribe()
66 }
67
68 pub fn receiver_count(&self) -> usize {
70 self.sender.receiver_count()
71 }
72
73 pub fn sender(&self) -> broadcast::Sender<Event> {
78 self.sender.clone()
79 }
80}
81
82impl Default for SseBroadcaster {
83 fn default() -> Self {
84 Self::new()
85 }
86}
87
88impl EventSubscriber for SseBroadcaster {
89 fn name(&self) -> &str {
90 "sse"
91 }
92
93 fn handle<'a>(&'a self, event: &'a Event) -> SubscriberFuture<'a> {
94 let event = event.clone();
95 Box::pin(async move {
96 let _ = self.sender.send(event);
98 })
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use std::collections::HashMap;
105
106 use super::*;
107 use chrono::Utc;
108 use ironflow_engine::notify::RunStatusChangedEvent;
109 use ironflow_store::models::RunStatus;
110 use rust_decimal::Decimal;
111 use uuid::Uuid;
112
113 fn sample_event() -> Event {
114 Event::RunStatusChanged(RunStatusChangedEvent {
115 run_id: Uuid::now_v7(),
116 workflow_name: "deploy".to_string(),
117 from: RunStatus::Running,
118 to: RunStatus::Completed,
119 error: None,
120 cost_usd: Decimal::ZERO,
121 duration_ms: 1000,
122 labels: HashMap::new(),
123 at: Utc::now(),
124 })
125 }
126
127 #[test]
128 fn new_creates_broadcaster() {
129 let broadcaster = SseBroadcaster::new();
130 assert_eq!(broadcaster.receiver_count(), 0);
131 }
132
133 #[test]
134 fn default_creates_broadcaster() {
135 let broadcaster = SseBroadcaster::default();
136 assert_eq!(broadcaster.receiver_count(), 0);
137 }
138
139 #[test]
140 fn subscribe_creates_receiver() {
141 let broadcaster = SseBroadcaster::new();
142 let _rx = broadcaster.subscribe();
143 assert_eq!(broadcaster.receiver_count(), 1);
144 }
145
146 #[test]
147 fn receiver_count_tracks_active_receivers() {
148 let broadcaster = SseBroadcaster::new();
149 let _rx1 = broadcaster.subscribe();
150 let _rx2 = broadcaster.subscribe();
151 assert_eq!(broadcaster.receiver_count(), 2);
152 drop(_rx1);
153 assert_eq!(broadcaster.receiver_count(), 1);
154 }
155
156 #[tokio::test]
157 async fn handle_sends_event_to_receivers() {
158 let broadcaster = SseBroadcaster::new();
159 let mut rx = broadcaster.subscribe();
160
161 let event = sample_event();
162 broadcaster.handle(&event).await;
163
164 let received = rx.recv().await.expect("should receive event");
165 assert_eq!(received.event_type(), "run_status_changed");
166 }
167
168 #[tokio::test]
169 async fn handle_no_receivers_does_not_panic() {
170 let broadcaster = SseBroadcaster::new();
171 let event = sample_event();
172 broadcaster.handle(&event).await;
174 }
175
176 #[test]
177 fn sender_returns_clone() {
178 let broadcaster = SseBroadcaster::new();
179 let sender = broadcaster.sender();
180 let _rx = sender.subscribe();
181 assert_eq!(broadcaster.receiver_count(), 1);
183 }
184
185 #[test]
186 fn name_returns_sse() {
187 let broadcaster = SseBroadcaster::new();
188 assert_eq!(broadcaster.name(), "sse");
189 }
190}