1use async_trait::async_trait;
2use flate2::read::ZlibDecoder;
3use futures::{SinkExt, StreamExt, future::try_join_all, stream::FuturesUnordered};
4use http::header::USER_AGENT;
5use serde::de::DeserializeOwned;
6use serde_json::{Value, json};
7use std::{
8 collections::{BTreeMap, HashMap, VecDeque},
9 io::Read,
10 marker::PhantomData,
11 mem::take,
12 sync::{
13 Arc,
14 atomic::{AtomicBool, AtomicUsize, Ordering},
15 },
16 time::Duration,
17};
18use tokio::{
19 net::TcpStream,
20 select, spawn,
21 sync::{
22 Mutex, Notify,
23 mpsc::{Receiver, Sender, UnboundedSender, channel, unbounded_channel},
24 oneshot,
25 },
26 task::JoinHandle,
27 time::{sleep, timeout},
28};
29use tokio_tungstenite::{
30 MaybeTlsStream, WebSocketStream, connect_async_tls_with_config,
31 tungstenite::{
32 Message,
33 client::IntoClientRequest,
34 protocol::{CloseFrame, WebSocketConfig, frame::coding::CloseCode},
35 },
36};
37use tokio_util::time::DelayQueue;
38use tracing::{debug, error, info, warn};
39
40use super::{
41 config::{
42 AgentConnector, ConfigurationWebsocketApi, ConfigurationWebsocketStreams,
43 WebsocketHandshakeFn,
44 },
45 errors::{WebsocketConnectionFailureReason, WebsocketError},
46 models::{StreamId, WebsocketApiResponse, WebsocketEvent, WebsocketMode},
47 utils::{build_websocket_api_message, normalize_stream_id, random_string, validate_time_unit},
48};
49
50pub type WebSocketClient = WebSocketStream<MaybeTlsStream<TcpStream>>;
51
52const MAX_CONN_DURATION: Duration = Duration::from_secs(23 * 60 * 60);
53
54pub struct Subscription {
55 handle: JoinHandle<()>,
56}
57
58impl Subscription {
59 pub fn unsubscribe(self) {
74 self.handle.abort();
75 }
76}
77
78#[derive(Clone)]
79pub enum WebsocketBase {
80 WebsocketApi(Arc<WebsocketApi>),
81 WebsocketStreams(Arc<WebsocketStreams>),
82}
83
84pub struct WebsocketEventEmitter {
85 subscribers: Arc<std::sync::Mutex<Vec<UnboundedSender<WebsocketEvent>>>>,
86}
87
88impl Default for WebsocketEventEmitter {
89 fn default() -> Self {
90 Self::new()
91 }
92}
93
94impl WebsocketEventEmitter {
95 #[must_use]
96 pub fn new() -> Self {
97 Self {
98 subscribers: Arc::new(std::sync::Mutex::new(Vec::new())),
99 }
100 }
101
102 pub fn subscribe<F>(&self, mut callback: F) -> Subscription
129 where
130 F: FnMut(WebsocketEvent) + Send + 'static,
131 {
132 let (tx, mut rx) = unbounded_channel();
133 let mut guard = match self.subscribers.lock() {
134 Ok(guard) => guard,
135 Err(poisoned) => poisoned.into_inner(),
136 };
137 guard.push(tx);
138 drop(guard);
139
140 let handle = spawn(async move {
141 while let Some(event) = rx.recv().await {
142 callback(event);
143 }
144 });
145 Subscription { handle }
146 }
147
148 pub fn emit(&self, event: &WebsocketEvent) {
158 let mut guard = match self.subscribers.lock() {
159 Ok(guard) => guard,
160 Err(poisoned) => poisoned.into_inner(),
161 };
162
163 guard.retain(|tx| {
164 if tx.send(event.clone()).is_ok() {
165 true
166 } else {
167 warn!("subscriber dropped without unsubscribing");
168 false
169 }
170 });
171 }
172}
173
174#[async_trait]
189pub trait WebsocketHandler: Send + Sync + 'static {
190 async fn on_open(&self, url: String, connection: Arc<WebsocketConnection>);
191 async fn on_message(&self, data: String, connection: Arc<WebsocketConnection>);
192 async fn get_reconnect_url(
193 &self,
194 default_url: String,
195 connection: Arc<WebsocketConnection>,
196 ) -> String;
197}
198
199pub struct PendingRequest {
200 pub completion: oneshot::Sender<Result<Value, WebsocketError>>,
201}
202
203#[derive(Clone)]
204pub struct WebsocketSessionLogonReq {
205 pub method: String,
206 pub payload: BTreeMap<String, Value>,
207 pub options: WebsocketMessageSendOptions,
208}
209
210pub struct WebsocketConnectionState {
211 pub reconnection_pending: bool,
212 pub renewal_pending: bool,
213 pub close_initiated: bool,
214 pub pending_requests: HashMap<String, PendingRequest>,
215 pub pending_subscriptions: VecDeque<String>,
216 pub stream_callbacks: HashMap<String, Vec<Arc<dyn Fn(&Value) + Send + Sync + 'static>>>,
217 pub is_session_logged_on: bool,
218 pub session_logon_req: Option<WebsocketSessionLogonReq>,
219 pub url_path: Option<String>,
220 pub handler: Option<Arc<dyn WebsocketHandler>>,
221 pub ws_write_tx: Option<UnboundedSender<Message>>,
222}
223
224impl Default for WebsocketConnectionState {
225 fn default() -> Self {
226 Self::new()
227 }
228}
229
230impl WebsocketConnectionState {
231 #[must_use]
232 pub fn new() -> Self {
233 Self {
234 reconnection_pending: false,
235 renewal_pending: false,
236 close_initiated: false,
237 pending_requests: HashMap::new(),
238 pending_subscriptions: VecDeque::new(),
239 stream_callbacks: HashMap::new(),
240 is_session_logged_on: false,
241 session_logon_req: None,
242 url_path: None,
243 handler: None,
244 ws_write_tx: None,
245 }
246 }
247}
248
249pub struct WebsocketConnection {
250 pub id: String,
251 pub drain_notify: Notify,
252 pub state: Mutex<WebsocketConnectionState>,
253}
254
255impl WebsocketConnection {
256 pub fn new(id: impl Into<String>) -> Arc<Self> {
257 Arc::new(Self {
258 id: id.into(),
259 drain_notify: Notify::new(),
260 state: Mutex::new(WebsocketConnectionState::new()),
261 })
262 }
263
264 pub async fn set_handler(&self, handler: Arc<dyn WebsocketHandler>) {
265 let mut conn_state = self.state.lock().await;
266 conn_state.handler = Some(handler);
267 }
268}
269
270struct ReconnectEntry {
271 connection_id: String,
272 url: String,
273 is_renewal: bool,
274}
275
276pub struct WebsocketCommon {
277 pub events: WebsocketEventEmitter,
278 mode: WebsocketMode,
279 round_robin_index: AtomicUsize,
280 connection_pool: Vec<Arc<WebsocketConnection>>,
281 reconnect_tx: Sender<ReconnectEntry>,
282 renewal_tx: Sender<(String, String)>,
283 reconnect_delay: usize,
284 agent: Option<AgentConnector>,
285 handshake: Option<WebsocketHandshakeFn>,
286 user_agent: Option<String>,
287}
288
289impl WebsocketCommon {
290 #[must_use]
291 pub fn new(
292 initial_pool: Vec<Arc<WebsocketConnection>>,
293 mode: WebsocketMode,
294 reconnect_delay: usize,
295 agent: Option<AgentConnector>,
296 user_agent: Option<String>,
297 ) -> Arc<Self> {
298 Self::new_with_handshake(initial_pool, mode, reconnect_delay, agent, user_agent, None)
299 }
300
301 fn new_with_handshake(
307 mut initial_pool: Vec<Arc<WebsocketConnection>>,
308 mode: WebsocketMode,
309 reconnect_delay: usize,
310 agent: Option<AgentConnector>,
311 user_agent: Option<String>,
312 handshake: Option<WebsocketHandshakeFn>,
313 ) -> Arc<Self> {
314 if initial_pool.is_empty() {
315 for _ in 0..mode.pool_size() {
316 let id = random_string();
317 initial_pool.push(WebsocketConnection::new(id));
318 }
319 }
320
321 let (reconnect_tx, reconnect_rx) = channel::<ReconnectEntry>(mode.pool_size());
322 let (renewal_tx, renewal_rx) = channel::<(String, String)>(mode.pool_size());
323
324 let common = Arc::new(Self {
325 events: WebsocketEventEmitter::new(),
326 mode,
327 round_robin_index: AtomicUsize::new(0),
328 connection_pool: initial_pool,
329 reconnect_tx,
330 renewal_tx,
331 reconnect_delay,
332 agent,
333 handshake,
334 user_agent,
335 });
336
337 Self::spawn_reconnect_loop(Arc::clone(&common), reconnect_rx);
338 Self::spawn_renewal_loop(&Arc::clone(&common), renewal_rx);
339
340 common
341 }
342
343 fn spawn_reconnect_loop(common: Arc<Self>, mut reconnect_rx: Receiver<ReconnectEntry>) {
361 spawn(async move {
362 while let Some(entry) = reconnect_rx.recv().await {
363 info!("Scheduling reconnect for id {}", entry.connection_id);
364
365 if !entry.is_renewal {
366 sleep(Duration::from_millis(common.reconnect_delay as u64)).await;
367 }
368
369 if let Some(conn_arc) = common
370 .connection_pool
371 .iter()
372 .find(|c| c.id == entry.connection_id)
373 .cloned()
374 {
375 let common_clone = Arc::clone(&common);
376 if let Err(err) = common_clone
377 .init_connect(&entry.url, entry.is_renewal, Some(conn_arc.clone()))
378 .await
379 {
380 error!(
381 "Reconnect failed for {} → {}: {:?}",
382 entry.connection_id, entry.url, err
383 );
384 }
385
386 sleep(Duration::from_secs(1)).await;
387 } else {
388 warn!("No connection {} found for reconnect", entry.connection_id);
389 }
390 }
391 });
392 }
393
394 fn spawn_renewal_loop(common: &Arc<Self>, renewal_rx: Receiver<(String, String)>) {
408 let common = Arc::clone(common);
409 spawn(async move {
410 let mut dq = DelayQueue::new();
411 let mut renewal_rx = renewal_rx;
412
413 loop {
414 select! {
415 Some((conn_id, url)) = renewal_rx.recv() => {
416 debug!("Scheduling renewal for {}", conn_id);
417 dq.insert((conn_id, url), MAX_CONN_DURATION);
418 }
419
420 Some(expired) = dq.next() => {
421 let (conn_id, default_url) = expired.into_inner();
422
423 if let Some(conn_arc) = common
424 .connection_pool
425 .iter()
426 .find(|c| c.id == conn_id)
427 .cloned()
428 {
429 debug!("Renewing connection {}", conn_id);
430 let url = common
431 .get_reconnect_url(&default_url, Arc::clone(&conn_arc))
432 .await;
433 if let Err(e) = common.reconnect_tx.send(ReconnectEntry {
434 connection_id: conn_id.clone(),
435 url,
436 is_renewal: true,
437 }).await {
438 error!(
439 "Failed to enqueue renewal for {}: {:?}",
440 conn_id, e
441 );
442 }
443 } else {
444 warn!("No connection {} found for renewal", conn_id);
445 }
446 }
447 }
448 }
449 });
450 }
451
452 pub async fn is_connection_ready(
470 &self,
471 connection: &WebsocketConnection,
472 allow_non_established: bool,
473 ) -> bool {
474 let conn_state = connection.state.lock().await;
475 (allow_non_established || conn_state.ws_write_tx.is_some())
476 && !conn_state.reconnection_pending
477 && !conn_state.close_initiated
478 }
479
480 async fn is_connected(&self, connection: Option<&Arc<WebsocketConnection>>) -> bool {
496 if let Some(conn_arc) = connection {
497 return self.is_connection_ready(conn_arc, false).await;
498 }
499
500 for conn_arc in &self.connection_pool {
501 if self.is_connection_ready(conn_arc, false).await {
502 return true;
503 }
504 }
505
506 false
507 }
508
509 async fn get_available_connections(
526 &self,
527 allow_non_established: bool,
528 url_path: Option<&str>,
529 ) -> Vec<Arc<WebsocketConnection>> {
530 if matches!(self.mode, WebsocketMode::Single) && url_path.is_none() {
531 return vec![Arc::clone(&self.connection_pool[0])];
532 }
533
534 let mut ready = Vec::new();
535 for conn in &self.connection_pool {
536 if self.is_connection_ready(conn, allow_non_established).await {
537 ready.push(Arc::clone(conn));
538 }
539 }
540
541 ready
542 }
543
544 async fn get_connection(
565 &self,
566 allow_non_established: bool,
567 url_path: Option<&str>,
568 ) -> Result<Arc<WebsocketConnection>, WebsocketError> {
569 let candidates = self
570 .get_available_connections(allow_non_established, url_path)
571 .await;
572
573 let mut ready = Vec::new();
574 for conn in candidates {
575 if let Some(path) = url_path {
576 let st = conn.state.lock().await;
577 if st.url_path.as_deref() != Some(path) {
578 continue;
579 }
580 }
581 ready.push(conn);
582 }
583
584 if ready.is_empty() {
585 return Err(WebsocketError::NotConnected);
586 }
587
588 let idx = self.round_robin_index.fetch_add(1, Ordering::Relaxed) % ready.len();
589
590 Ok(Arc::clone(&ready[idx]))
591 }
592
593 async fn close_connection_gracefully(
610 &self,
611 ws_write_tx_to_close: UnboundedSender<Message>,
612 connection: Arc<WebsocketConnection>,
613 ) -> Result<(), WebsocketError> {
614 debug!("Waiting for pending requests to complete before disconnecting.");
615
616 let drain = async {
617 loop {
618 {
619 let conn_state = connection.state.lock().await;
620 if conn_state.pending_requests.is_empty() {
621 debug!("All pending requests completed, proceeding to close.");
622 break;
623 }
624 }
625 connection.drain_notify.notified().await;
626 }
627 };
628
629 if timeout(Duration::from_secs(30), drain).await.is_err() {
630 warn!("Timeout waiting for pending requests; forcing close.");
631 }
632
633 info!("Closing WebSocket connection for {}", connection.id);
634 let _ = ws_write_tx_to_close.send(Message::Close(Some(CloseFrame {
635 code: CloseCode::Normal,
636 reason: "".into(),
637 })));
638
639 Ok(())
640 }
641
642 async fn get_reconnect_url(
659 &self,
660 default_url: &str,
661 connection: Arc<WebsocketConnection>,
662 ) -> String {
663 if let Some(handler) = {
664 let conn_state = connection.state.lock().await;
665 conn_state.handler.clone()
666 } {
667 return handler
668 .get_reconnect_url(default_url.to_string(), Arc::clone(&connection))
669 .await;
670 }
671
672 default_url.to_string()
673 }
674
675 async fn on_open(
697 &self,
698 url: String,
699 connection: Arc<WebsocketConnection>,
700 old_ws_writer: Option<UnboundedSender<Message>>,
701 ) {
702 if let Some(handler) = {
703 let conn_state = connection.state.lock().await;
704 conn_state.handler.clone()
705 } {
706 handler.on_open(url.clone(), Arc::clone(&connection)).await;
707 }
708
709 let conn_id = &connection.id;
710 info!("Connected to WebSocket Server with id {}: {}", conn_id, url);
711
712 {
713 let mut conn_state = connection.state.lock().await;
714
715 if conn_state.renewal_pending {
716 conn_state.renewal_pending = false;
717 drop(conn_state);
718 if let Some(tx) = old_ws_writer {
719 info!("Connection renewal in progress; closing previous connection.");
720 let _ = self
721 .close_connection_gracefully(tx, Arc::clone(&connection))
722 .await;
723 }
724 return;
725 }
726
727 if conn_state.close_initiated {
728 drop(conn_state);
729 if let Some(tx) = connection.state.lock().await.ws_write_tx.clone() {
730 info!("Close initiated; closing connection.");
731 let _ = self
732 .close_connection_gracefully(tx, Arc::clone(&connection))
733 .await;
734 }
735 return;
736 }
737
738 self.events.emit(&WebsocketEvent::Open);
739 }
740 }
741
742 async fn on_message(&self, msg: String, connection: Arc<WebsocketConnection>) {
754 let handler = connection.state.lock().await.handler.clone();
755 if let Some(handler) = handler {
756 handler
757 .on_message(msg.clone(), Arc::clone(&connection))
758 .await;
759 }
760 self.events.emit(&WebsocketEvent::Message(msg));
761 }
762
763 async fn create_websocket(
786 url: &str,
787 agent: Option<AgentConnector>,
788 user_agent: Option<String>,
789 handshake: Option<WebsocketHandshakeFn>,
790 ) -> Result<WebSocketStream<MaybeTlsStream<tokio::net::TcpStream>>, WebsocketError> {
791 let mut req = url
792 .into_client_request()
793 .map_err(|e| WebsocketError::Handshake(e.to_string()))?;
794
795 if let Some(ua) = user_agent {
796 req.headers_mut().insert(USER_AGENT, ua.parse().unwrap());
797 }
798
799 let ws_config: Option<WebSocketConfig> = None;
800 let disable_nagle = false;
801
802 let timeout_duration = Duration::from_secs(10);
803 let connector = agent.map(|agent| agent.0);
804 let handshake = match handshake {
805 Some(handshake) => handshake(req, ws_config, disable_nagle, connector),
806 None => Box::pin(connect_async_tls_with_config(
807 req,
808 ws_config,
809 disable_nagle,
810 connector,
811 )),
812 };
813 match timeout(timeout_duration, handshake).await {
814 Ok(Ok((ws_stream, response))) => {
815 debug!("WebSocket connected: {:?}", response);
816 Ok(ws_stream)
817 }
818 Ok(Err(e)) => {
819 let msg = e.to_string();
820 error!("WebSocket handshake failed: {}", msg);
821 Err(WebsocketError::Handshake(msg))
822 }
823 Err(_) => {
824 error!(
825 "WebSocket connection timed out after {}s",
826 timeout_duration.as_secs()
827 );
828 Err(WebsocketError::Timeout)
829 }
830 }
831 }
832
833 async fn connect_pool(
853 self: Arc<Self>,
854 url: &str,
855 connections: Option<Vec<Arc<WebsocketConnection>>>,
856 ) -> Result<(), WebsocketError> {
857 let pool: Vec<Arc<WebsocketConnection>> = match connections {
858 Some(v) => v,
859 None => self.connection_pool.clone(),
860 };
861
862 let mut tasks = FuturesUnordered::new();
863
864 for conn in pool {
865 let common = Arc::clone(&self);
866 let url = url.to_owned();
867
868 tasks.push(async move {
869 match common.init_connect(&url, false, Some(conn)).await {
870 Ok(()) => {
871 info!("Successfully connected to {}", url);
872 Ok(())
873 }
874 Err(err) => {
875 error!("Failed to connect to {}: {:?}", url, err);
876 Err(err)
877 }
878 }
879 });
880 }
881
882 while let Some(result) = tasks.next().await {
883 result?;
884 }
885
886 Ok(())
887 }
888
889 async fn init_connect(
910 self: Arc<Self>,
911 url: &str,
912 is_renewal: bool,
913 connection: Option<Arc<WebsocketConnection>>,
914 ) -> Result<(), WebsocketError> {
915 let conn = connection.unwrap_or(self.get_connection(true, None).await?);
916
917 {
918 let mut conn_state = conn.state.lock().await;
919 if conn_state.renewal_pending && is_renewal {
920 info!("Renewal in progress {}→{}", conn.id, url);
921 return Ok(());
922 }
923 if conn_state.ws_write_tx.is_some() && !is_renewal && !conn_state.reconnection_pending {
924 info!("Exists {}; skipping {}", conn.id, url);
925 return Ok(());
926 }
927 if is_renewal {
928 conn_state.renewal_pending = true;
929 }
930
931 conn_state.is_session_logged_on = false;
932 }
933
934 let ws = Self::create_websocket(
935 url,
936 self.agent.clone(),
937 self.user_agent.clone(),
938 self.handshake.clone(),
939 )
940 .await
941 .map_err(|e| {
942 error!("Handshake failed {}: {:?}", url, e);
943 e
944 })?;
945
946 info!("Established {} → {}", conn.id, url);
947
948 if let Err(e) = self.renewal_tx.try_send((conn.id.clone(), url.to_string())) {
949 error!("Failed to schedule renewal for {}: {:?}", conn.id, e);
950 }
951
952 let (write_half, mut read_half) = ws.split();
953 let (tx, mut rx) = unbounded_channel::<Message>();
954
955 let old_writer = {
956 let mut conn_state = conn.state.lock().await;
957 conn_state.reconnection_pending = false;
958 conn_state.ws_write_tx.replace(tx.clone())
959 };
960
961 {
962 let wconn = conn.clone();
963 let common_clone = self.clone();
964 let writer_url = url.to_string();
965
966 spawn(async move {
967 let mut sink = write_half;
968 while let Some(msg) = rx.recv().await {
969 if let Err(e) = sink.send(msg).await {
970 let failure_reason =
971 WebsocketConnectionFailureReason::from_tungstenite_error(&e);
972
973 error!(
974 "Write error on {}: {:?}, classified as {:?}",
975 wconn.id, e, failure_reason
976 );
977
978 let mut conn_state = wconn.state.lock().await;
980 if !conn_state.close_initiated
981 && !is_renewal
982 && failure_reason.should_reconnect()
983 {
984 info!(
985 "Writer connection {} has recoverable error, attempting reconnection: {:?}",
986 wconn.id, failure_reason
987 );
988 conn_state.reconnection_pending = true;
989 conn_state.is_session_logged_on = false;
990 drop(conn_state);
991 let reconnect_url = common_clone
992 .get_reconnect_url(&writer_url, Arc::clone(&wconn))
993 .await;
994
995 let _ = common_clone
996 .reconnect_tx
997 .send(ReconnectEntry {
998 connection_id: wconn.id.clone(),
999 url: reconnect_url,
1000 is_renewal: false,
1001 })
1002 .await;
1003 } else {
1004 warn!(
1005 "Writer connection {} has permanent error, will not reconnect: {:?}",
1006 wconn.id, failure_reason
1007 );
1008 }
1009
1010 break;
1011 }
1012 }
1013 debug!("Writer {} exit", wconn.id);
1014 });
1015 }
1016
1017 {
1018 let common = self.clone();
1019 let conn = conn.clone();
1020 let url = url.to_string();
1021 spawn(async move {
1022 common.on_open(url, conn, old_writer).await;
1023 });
1024 }
1025
1026 {
1027 let common = self.clone();
1028 let reader_conn = conn.clone();
1029 let read_url = url.to_string();
1030
1031 spawn(async move {
1032 let mut stream_end_reason = None;
1033 while let Some(item) = read_half.next().await {
1034 match item {
1035 Ok(Message::Text(msg)) => {
1036 common
1037 .on_message(msg.to_string(), Arc::clone(&reader_conn))
1038 .await;
1039 }
1040 Ok(Message::Binary(bin)) => {
1041 let mut decoder = ZlibDecoder::new(&bin[..]);
1042 let mut decompressed = String::new();
1043 if let Err(err) = decoder.read_to_string(&mut decompressed) {
1044 error!("Binary message decompress failed: {:?}", err);
1045 continue;
1046 }
1047 common
1048 .on_message(decompressed, Arc::clone(&reader_conn))
1049 .await;
1050 }
1051 Ok(Message::Ping(payload)) => {
1052 info!("PING received from server on {}", reader_conn.id);
1053 common.events.emit(&WebsocketEvent::Ping);
1054 if let Some(tx) = reader_conn.state.lock().await.ws_write_tx.clone() {
1055 let _ = tx.send(Message::Pong(payload));
1056 info!(
1057 "Responded PONG to server's PING message on {}",
1058 reader_conn.id
1059 );
1060 }
1061 }
1062 Ok(Message::Pong(_)) => {
1063 info!("Received PONG from server on {}", reader_conn.id);
1064 common.events.emit(&WebsocketEvent::Pong);
1065 }
1066 Ok(Message::Close(frame)) => {
1067 let (code, reason) = frame
1068 .map_or((1000, String::new()), |CloseFrame { code, reason }| {
1069 (code.into(), reason.to_string())
1070 });
1071 common
1072 .events
1073 .emit(&WebsocketEvent::Close(code, reason.clone()));
1074
1075 let user_initiated = {
1077 let conn_state = reader_conn.state.lock().await;
1078 conn_state.close_initiated
1079 };
1080
1081 let failure_reason = WebsocketConnectionFailureReason::from_close_code(
1082 code,
1083 user_initiated,
1084 );
1085 stream_end_reason = Some(failure_reason);
1086
1087 info!(
1088 "Connection {} received close frame: code={}, reason='{}', classified as {:?}",
1089 reader_conn.id, code, reason, failure_reason
1090 );
1091
1092 let mut conn_state = reader_conn.state.lock().await;
1093 if !conn_state.close_initiated
1094 && !is_renewal
1095 && failure_reason.should_reconnect()
1096 {
1097 info!(
1098 "Connection {} received close frame with reconnectable failure: {:?}",
1099 reader_conn.id, failure_reason
1100 );
1101 conn_state.reconnection_pending = true;
1102 conn_state.is_session_logged_on = false;
1103 drop(conn_state);
1104 let reconnect_url = common
1105 .get_reconnect_url(&read_url, Arc::clone(&reader_conn))
1106 .await;
1107
1108 let _ = common
1109 .reconnect_tx
1110 .send(ReconnectEntry {
1111 connection_id: reader_conn.id.clone(),
1112 url: reconnect_url,
1113 is_renewal: false,
1114 })
1115 .await;
1116 } else {
1117 warn!(
1118 "Connection {} received close frame with non-reconnectable failure: {:?}",
1119 reader_conn.id, failure_reason
1120 );
1121
1122 common.events.emit(&WebsocketEvent::Error(format!(
1124 "[CRITICAL] Connection {} permanently failed: {:?}",
1125 reader_conn.id, failure_reason
1126 )));
1127 }
1128
1129 break;
1130 }
1131 Err(e) => {
1132 let failure_reason =
1134 WebsocketConnectionFailureReason::from_tungstenite_error(&e);
1135
1136 stream_end_reason = Some(failure_reason);
1137 error!(
1138 "WebSocket error on {}: {:?}, classified as {:?}",
1139 reader_conn.id, e, failure_reason
1140 );
1141
1142 common.events.emit(&WebsocketEvent::Error(e.to_string()));
1143
1144 let mut conn_state = reader_conn.state.lock().await;
1146 if !conn_state.close_initiated
1147 && !is_renewal
1148 && failure_reason.should_reconnect()
1149 {
1150 info!(
1151 "Connection {} has recoverable error, attempting reconnection: {:?}",
1152 reader_conn.id, failure_reason
1153 );
1154 conn_state.reconnection_pending = true;
1155 conn_state.is_session_logged_on = false;
1156 drop(conn_state);
1157 let reconnect_url = common
1158 .get_reconnect_url(&read_url, Arc::clone(&reader_conn))
1159 .await;
1160
1161 let _ = common
1162 .reconnect_tx
1163 .send(ReconnectEntry {
1164 connection_id: reader_conn.id.clone(),
1165 url: reconnect_url,
1166 is_renewal: false,
1167 })
1168 .await;
1169 } else {
1170 warn!(
1171 "Connection {} has permanent error, will not reconnect: {:?}",
1172 reader_conn.id, failure_reason
1173 );
1174
1175 common.events.emit(&WebsocketEvent::Error(format!(
1177 "[CRITICAL] Connection {} permanently failed: {:?}",
1178 reader_conn.id, failure_reason
1179 )));
1180 }
1181
1182 break;
1183 }
1184 _ => {}
1185 }
1186 }
1187
1188 info!("WebSocket stream ended for connection {}", reader_conn.id);
1190
1191 let failure_reason =
1193 stream_end_reason.unwrap_or(WebsocketConnectionFailureReason::StreamEnded);
1194
1195 info!(
1196 "WebSocket stream ended for connection {}, classified as {:?}",
1197 reader_conn.id, failure_reason
1198 );
1199
1200 let mut conn_state = reader_conn.state.lock().await;
1201 if !conn_state.close_initiated && !is_renewal && failure_reason.should_reconnect() {
1202 info!(
1203 "Connection {} stream ended unexpectedly, attempting reconnection",
1204 reader_conn.id
1205 );
1206 conn_state.reconnection_pending = true;
1207 conn_state.is_session_logged_on = false;
1208 conn_state.ws_write_tx = None;
1209 drop(conn_state);
1210 let reconnect_url = common
1211 .get_reconnect_url(&read_url, Arc::clone(&reader_conn))
1212 .await;
1213
1214 let _ = common
1215 .reconnect_tx
1216 .send(ReconnectEntry {
1217 connection_id: reader_conn.id.clone(),
1218 url: reconnect_url,
1219 is_renewal: false,
1220 })
1221 .await;
1222 } else {
1223 debug!(
1224 "Connection {} stream ended normally (close_initiated={}, is_renewal={})",
1225 reader_conn.id, conn_state.close_initiated, is_renewal
1226 );
1227 }
1228
1229 debug!("Reader actor for {} exiting", reader_conn.id);
1230 });
1231 }
1232
1233 Ok(())
1234 }
1235 async fn disconnect(&self) -> Result<(), WebsocketError> {
1250 if !self.is_connected(None).await {
1251 warn!("No active connection to close.");
1252 return Ok(());
1253 }
1254
1255 let mut shutdowns = FuturesUnordered::new();
1256 for conn in &self.connection_pool {
1257 {
1258 let mut conn_state = conn.state.lock().await;
1259 conn_state.close_initiated = true;
1260 if let Some(tx) = &conn_state.ws_write_tx {
1261 shutdowns.push(self.close_connection_gracefully(tx.clone(), Arc::clone(conn)));
1262 }
1263 }
1264 }
1265
1266 let close_all = async {
1267 while let Some(result) = shutdowns.next().await {
1268 result?;
1269 }
1270 Ok::<(), WebsocketError>(())
1271 };
1272
1273 match timeout(Duration::from_secs(30), close_all).await {
1274 Ok(Ok(())) => {
1275 info!("Disconnected all WebSocket connections successfully.");
1276 for conn in &self.connection_pool {
1277 let mut st = conn.state.lock().await;
1278 st.is_session_logged_on = false;
1279 st.session_logon_req = None;
1280 }
1281 Ok(())
1282 }
1283 Ok(Err(err)) => {
1284 error!("Error while disconnecting: {:?}", err);
1285 Err(err)
1286 }
1287 Err(_) => {
1288 error!("Timed out while disconnecting WebSocket connections.");
1289 Err(WebsocketError::Timeout)
1290 }
1291 }
1292 }
1293
1294 async fn ping_server(&self) {
1307 let mut ready = Vec::new();
1308 for conn in &self.connection_pool {
1309 if self.is_connection_ready(conn, false).await {
1310 let id = conn.id.clone();
1311 let ws_write_tx = {
1312 let conn_state = conn.state.lock().await;
1313 conn_state.ws_write_tx.clone()
1314 };
1315 ready.push((id, ws_write_tx));
1316 }
1317 }
1318
1319 if ready.is_empty() {
1320 warn!("No ready connections for PING.");
1321 return;
1322 }
1323 info!("Sending PING to {} WebSocket connections.", ready.len());
1324
1325 let mut tasks = FuturesUnordered::new();
1326 for (id, ws_write_tx_opt) in ready {
1327 if let Some(tx) = ws_write_tx_opt {
1328 tasks.push(async move {
1329 if let Err(e) = tx.send(Message::Ping(Vec::new().into())) {
1330 error!("Failed to send PING to {}: {:?}", id, e);
1331 } else {
1332 debug!("Sent PING to connection {}", id);
1333 }
1334 });
1335 } else {
1336 error!("Connection {} was ready but has no write channel", id);
1337 }
1338 }
1339
1340 while tasks.next().await.is_some() {}
1341 }
1342
1343 async fn send(
1361 &self,
1362 payload: String,
1363 id: Option<String>,
1364 wait_for_reply: bool,
1365 timeout: Duration,
1366 connection: Option<Arc<WebsocketConnection>>,
1367 ) -> Result<Option<oneshot::Receiver<Result<Value, WebsocketError>>>, WebsocketError> {
1368 let conn = if let Some(c) = connection {
1369 c
1370 } else {
1371 self.get_connection(false, None).await?
1372 };
1373
1374 if !self.is_connected(Some(&conn)).await {
1375 warn!("Send attempted on a non-connected socket");
1376 return Err(WebsocketError::NotConnected);
1377 }
1378
1379 let ws_write_tx = {
1380 let conn_state = conn.state.lock().await;
1381 conn_state
1382 .ws_write_tx
1383 .clone()
1384 .ok_or(WebsocketError::NotConnected)?
1385 };
1386
1387 let pending_setup = if wait_for_reply {
1388 let request_id = id.ok_or_else(|| {
1389 error!("id is required when waiting for a reply");
1390 WebsocketError::NotConnected
1391 })?;
1392
1393 let (tx, rx) = oneshot::channel();
1394 {
1395 let mut conn_state = conn.state.lock().await;
1396 conn_state
1397 .pending_requests
1398 .insert(request_id.clone(), PendingRequest { completion: tx });
1399 }
1400
1401 Some((request_id, rx))
1402 } else {
1403 None
1404 };
1405
1406 debug!("Sending message to WebSocket on connection {}", conn.id);
1407
1408 if ws_write_tx
1409 .send(Message::Text(payload.clone().into()))
1410 .is_err()
1411 {
1412 if let Some((request_id, _)) = &pending_setup {
1413 let mut conn_state = conn.state.lock().await;
1414 conn_state.pending_requests.remove(request_id);
1415 }
1416 return Err(WebsocketError::NotConnected);
1417 }
1418
1419 let rx = if let Some((request_id, rx)) = pending_setup {
1420 let conn_clone = Arc::clone(&conn);
1421 let timeout_id = request_id.clone();
1422 spawn(async move {
1423 sleep(timeout).await;
1424 let mut conn_state = conn_clone.state.lock().await;
1425 if let Some(pending_req) = conn_state.pending_requests.remove(&timeout_id) {
1426 let _ = pending_req.completion.send(Err(WebsocketError::Timeout));
1427 }
1428 });
1429 Some(rx)
1430 } else {
1431 None
1432 };
1433
1434 Ok(rx)
1435 }
1436}
1437
1438#[derive(Debug, Default, Clone)]
1439pub struct WebsocketMessageSendOptions {
1440 pub with_api_key: bool,
1441 pub is_signed: bool,
1442 pub is_session_logon: Option<bool>,
1443 pub is_session_logout: Option<bool>,
1444}
1445
1446impl WebsocketMessageSendOptions {
1447 #[must_use]
1448 pub fn new() -> Self {
1449 Self::default()
1450 }
1451
1452 #[must_use]
1453 pub fn with_api_key(mut self) -> Self {
1454 self.with_api_key = true;
1455 self
1456 }
1457
1458 #[must_use]
1459 pub fn signed(mut self) -> Self {
1460 self.is_signed = true;
1461 self
1462 }
1463
1464 #[must_use]
1465 pub fn session_logon(mut self) -> Self {
1466 self.is_session_logon = Some(true);
1467 self
1468 }
1469
1470 #[must_use]
1471 pub fn session_logout(mut self) -> Self {
1472 self.is_session_logout = Some(true);
1473 self
1474 }
1475}
1476
1477#[derive(Debug)]
1478pub enum SendWebsocketMessageResult<R> {
1479 Single(WebsocketApiResponse<R>),
1480 Multiple(Vec<WebsocketApiResponse<R>>),
1481}
1482
1483impl<R> IntoIterator for SendWebsocketMessageResult<R> {
1484 type Item = WebsocketApiResponse<R>;
1485 type IntoIter = std::vec::IntoIter<Self::Item>;
1486
1487 fn into_iter(self) -> Self::IntoIter {
1488 match self {
1489 SendWebsocketMessageResult::Single(resp) => vec![resp].into_iter(),
1490 SendWebsocketMessageResult::Multiple(v) => v.into_iter(),
1491 }
1492 }
1493}
1494
1495pub struct WebsocketApi {
1496 pub common: Arc<WebsocketCommon>,
1497 configuration: ConfigurationWebsocketApi,
1498 is_connecting: Arc<Mutex<bool>>,
1499 stream_callbacks: Mutex<HashMap<String, Vec<Arc<dyn Fn(&Value) + Send + Sync + 'static>>>>,
1500}
1501
1502impl WebsocketApi {
1503 #[must_use]
1504 pub fn new(
1525 configuration: ConfigurationWebsocketApi,
1526 connection_pool: Vec<Arc<WebsocketConnection>>,
1527 ) -> Arc<Self> {
1528 let agent_clone = configuration.agent.clone();
1529 let handshake_clone = configuration.handshake.clone();
1530 let user_agent_clone = configuration.user_agent.clone();
1531 let common = WebsocketCommon::new_with_handshake(
1532 connection_pool,
1533 configuration.mode.clone(),
1534 usize::try_from(configuration.reconnect_delay)
1535 .expect("reconnect_delay should fit in usize"),
1536 agent_clone,
1537 Some(user_agent_clone),
1538 handshake_clone,
1539 );
1540
1541 Arc::new(Self {
1542 common: Arc::clone(&common),
1543 configuration,
1544 is_connecting: Arc::new(Mutex::new(false)),
1545 stream_callbacks: Mutex::new(HashMap::new()),
1546 })
1547 }
1548
1549 pub async fn connect(self: Arc<Self>) -> Result<(), WebsocketError> {
1571 if self.common.is_connected(None).await {
1572 info!("WebSocket connection already established");
1573 return Ok(());
1574 }
1575
1576 {
1577 let mut flag = self.is_connecting.lock().await;
1578 if *flag {
1579 info!("Already connecting...");
1580 return Ok(());
1581 }
1582 *flag = true;
1583 }
1584
1585 let url = self.prepare_url(self.configuration.ws_url.as_deref().unwrap_or_default());
1586
1587 let handler: Arc<dyn WebsocketHandler> = self.clone();
1588 for slot in &self.common.connection_pool {
1589 slot.set_handler(handler.clone()).await;
1590 }
1591
1592 let result = select! {
1593 () = sleep(Duration::from_secs(10)) => Err(WebsocketError::Timeout),
1594 r = self.common.clone().connect_pool(&url, None) => r,
1595 };
1596
1597 {
1598 let mut flag = self.is_connecting.lock().await;
1599 *flag = false;
1600 }
1601
1602 result
1603 }
1604
1605 pub async fn disconnect(&self) -> Result<(), WebsocketError> {
1618 self.common.disconnect().await
1619 }
1620
1621 pub async fn is_connected(&self) -> bool {
1627 self.common.is_connected(None).await
1628 }
1629
1630 pub async fn ping_server(&self) {
1635 self.common.ping_server().await;
1636 }
1637
1638 pub async fn send_message<R>(
1668 &self,
1669 method: &str,
1670 payload: BTreeMap<String, Value>,
1671 options: WebsocketMessageSendOptions,
1672 ) -> Result<SendWebsocketMessageResult<R>, WebsocketError>
1673 where
1674 R: DeserializeOwned + Send + Sync + 'static,
1675 {
1676 if !self.common.is_connected(None).await {
1677 return Err(WebsocketError::NotConnected);
1678 }
1679
1680 let do_multi =
1681 options.is_session_logon.unwrap_or(false) || options.is_session_logout.unwrap_or(false);
1682
1683 let connections = if do_multi {
1684 self.common.get_available_connections(false, None).await
1685 } else {
1686 vec![self.common.get_connection(false, None).await?]
1687 };
1688
1689 let skip_auth = if do_multi {
1690 false
1691 } else {
1692 let connection = &connections[0];
1693 let conn_state = connection.state.lock().await;
1694 self.configuration.auto_session_relogon && conn_state.is_session_logged_on
1695 };
1696
1697 let payload_clone = payload.clone();
1698
1699 let (id, request) =
1700 build_websocket_api_message(&self.configuration, method, payload, &options, skip_auth);
1701 let raw_payload = serde_json::to_string(&request).unwrap();
1702 debug!("Sending message to WebSocket API: {:?}", request);
1703
1704 let timeout = Duration::from_millis(self.configuration.timeout);
1705
1706 let mut receivers = Vec::with_capacity(connections.len());
1707 for connection in &connections {
1708 let opt_rx = self
1709 .common
1710 .send(
1711 raw_payload.clone(),
1712 Some(id.clone()),
1713 true,
1714 timeout,
1715 Some(connection.clone()),
1716 )
1717 .await?;
1718 receivers.push((connection.clone(), opt_rx));
1719 }
1720
1721 let mut raw_msgs = Vec::with_capacity(receivers.len());
1722 for (_conn, opt_rx) in receivers {
1723 let rx = opt_rx.ok_or(WebsocketError::NoResponse)?;
1724 let msg = rx.await.unwrap_or(Err(WebsocketError::Timeout))?;
1725 raw_msgs.push(msg);
1726 }
1727
1728 let mut responses = Vec::with_capacity(raw_msgs.len());
1729 for msg in raw_msgs {
1730 let raw = msg
1731 .get("result")
1732 .or_else(|| msg.get("response"))
1733 .cloned()
1734 .unwrap_or(Value::Null);
1735
1736 let rate_limits = msg
1737 .get("rateLimits")
1738 .and_then(Value::as_array)
1739 .map(|arr| {
1740 arr.iter()
1741 .filter_map(|v| serde_json::from_value(v.clone()).ok())
1742 .collect()
1743 })
1744 .unwrap_or_default();
1745
1746 responses.push(WebsocketApiResponse {
1747 raw,
1748 rate_limits,
1749 _marker: PhantomData,
1750 });
1751 }
1752
1753 if do_multi && self.configuration.auto_session_relogon {
1754 for connection in &connections {
1755 let mut state = connection.state.lock().await;
1756 if options.is_session_logon.unwrap_or(false) {
1757 state.is_session_logged_on = true;
1758 state.session_logon_req = Some(WebsocketSessionLogonReq {
1759 method: method.to_string(),
1760 payload: payload_clone.clone(),
1761 options: options.clone(),
1762 });
1763 } else {
1764 state.is_session_logged_on = false;
1765 state.session_logon_req = None;
1766 }
1767 }
1768 }
1769
1770 Ok(if responses.len() == 1 && !do_multi {
1771 SendWebsocketMessageResult::Single(responses.into_iter().next().unwrap())
1772 } else {
1773 SendWebsocketMessageResult::Multiple(responses)
1774 })
1775 }
1776
1777 fn prepare_url(&self, ws_url: &str) -> String {
1792 let mut url = ws_url.to_string();
1793
1794 let time_unit = match &self.configuration.time_unit {
1795 Some(u) => u.to_string(),
1796 None => return url,
1797 };
1798
1799 match validate_time_unit(&time_unit) {
1800 Ok(Some(validated)) => {
1801 let sep = if url.contains('?') { '&' } else { '?' };
1802 url.push(sep);
1803 url.push_str("timeUnit=");
1804 url.push_str(validated);
1805 }
1806 Ok(None) => {}
1807 Err(e) => {
1808 error!("Invalid time unit provided: {:?}", e);
1809 }
1810 }
1811
1812 url
1813 }
1814}
1815
1816#[async_trait]
1817impl WebsocketHandler for WebsocketApi {
1818 async fn on_open(&self, _url: String, connection: Arc<WebsocketConnection>) {
1836 let session_req = {
1837 let conn_state = connection.state.lock().await;
1838 conn_state.session_logon_req.clone()
1839 };
1840
1841 let Some(req) = session_req else {
1842 return;
1843 };
1844
1845 let already_logged_on = {
1846 let conn_state = connection.state.lock().await;
1847 conn_state.is_session_logged_on
1848 };
1849
1850 if already_logged_on {
1851 debug!(
1852 "Connection {} already logged on, skipping re-logon",
1853 connection.id
1854 );
1855 return;
1856 }
1857
1858 let conn = connection.clone();
1859 let common = Arc::clone(&self.common);
1860 let configuration = self.configuration.clone();
1861 let method = req.method.clone();
1862 let payload = req.payload.clone();
1863 let options = req.options.clone();
1864
1865 spawn(async move {
1866 let (id, json_msg) =
1867 build_websocket_api_message(&configuration, &method, payload, &options, false);
1868
1869 let raw_message = match serde_json::to_string(&json_msg) {
1870 Ok(msg) => msg,
1871 Err(e) => {
1872 warn!(
1873 "Failed to serialize session logon message for connection {}: {}",
1874 conn.id, e
1875 );
1876 return;
1877 }
1878 };
1879
1880 debug!(
1881 "Session re-logon on connection {}: {}",
1882 conn.id, raw_message
1883 );
1884
1885 let rx = match common
1886 .send(
1887 raw_message,
1888 Some(id.clone()),
1889 true,
1890 Duration::from_millis(configuration.timeout),
1891 Some(conn.clone()),
1892 )
1893 .await
1894 {
1895 Ok(Some(rx)) => rx,
1896 Ok(None) => {
1897 warn!(
1898 "Session re-logon dispatch returned None for connection {}",
1899 conn.id
1900 );
1901 return;
1902 }
1903 Err(e) => {
1904 warn!(
1905 "Session re-logon dispatch failed on connection {}: {}",
1906 conn.id, e
1907 );
1908 return;
1909 }
1910 };
1911
1912 let Ok(result) = timeout(Duration::from_millis(configuration.timeout), rx).await else {
1913 warn!("Session re-logon timed out on connection {}", conn.id);
1914 return;
1915 };
1916
1917 let final_result = match result {
1918 Ok(final_result) => final_result,
1919 Err(e) => {
1920 warn!(
1921 "Session re-logon receiver error on connection {}: {}",
1922 conn.id, e
1923 );
1924 return;
1925 }
1926 };
1927
1928 let payload = match final_result {
1929 Ok(payload) => payload,
1930 Err(e) => {
1931 warn!(
1932 "Session re-logon payload error on connection {}: {}",
1933 conn.id, e
1934 );
1935 return;
1936 }
1937 };
1938
1939 debug!(
1940 "Session re-logon succeeded on connection {}: {}",
1941 conn.id, payload
1942 );
1943 let mut conn_state = conn.state.lock().await;
1944 conn_state.is_session_logged_on = true;
1945 });
1946 }
1947
1948 async fn on_message(&self, data: String, connection: Arc<WebsocketConnection>) {
1968 let msg: Value = match serde_json::from_str(&data) {
1969 Ok(v) => v,
1970 Err(err) => {
1971 error!("Failed to parse WebSocket message {} – {}", data, err);
1972 return;
1973 }
1974 };
1975
1976 if let Some(id) = msg.get("id").and_then(Value::as_str) {
1977 let maybe_sender = {
1978 let mut conn_state = connection.state.lock().await;
1979 conn_state.pending_requests.remove(id)
1980 };
1981
1982 if let Some(PendingRequest { completion }) = maybe_sender {
1983 connection.drain_notify.notify_one();
1984 let status = msg.get("status").and_then(Value::as_u64).unwrap_or(200);
1985 if status >= 400 {
1986 let error_map = msg
1987 .get("error")
1988 .and_then(Value::as_object)
1989 .unwrap_or(&serde_json::Map::new())
1990 .clone();
1991
1992 let code = error_map
1993 .get("code")
1994 .and_then(Value::as_i64)
1995 .unwrap_or(status.try_into().unwrap());
1996
1997 let message = error_map
1998 .get("msg")
1999 .and_then(Value::as_str)
2000 .unwrap_or("Unknown error")
2001 .to_string();
2002
2003 let _ = completion.send(Err(WebsocketError::ResponseError { code, message }));
2004 } else {
2005 let _ = completion.send(Ok(msg.clone()));
2006 }
2007 }
2008
2009 return;
2010 }
2011
2012 if let Some(event) = msg.get("event") {
2013 if let Some(event_type) = event.get("e").and_then(Value::as_str) {
2014 if event_type == "serverShutdown" {
2015 warn!(
2016 "Received serverShutdown event on connection {}",
2017 connection.id
2018 );
2019
2020 let mut conn_state = connection.state.lock().await;
2021
2022 if !conn_state.renewal_pending && !conn_state.close_initiated {
2023 conn_state.renewal_pending = true;
2024
2025 let url = conn_state.url_path.clone().unwrap_or_default();
2026
2027 drop(conn_state);
2028
2029 if let Err(e) = self
2030 .common
2031 .reconnect_tx
2032 .send(ReconnectEntry {
2033 connection_id: connection.id.clone(),
2034 url,
2035 is_renewal: true,
2036 })
2037 .await
2038 {
2039 error!("Failed to enqueue serverShutdown renewal: {:?}", e);
2040 }
2041 }
2042
2043 return;
2044 }
2045 }
2046 }
2047
2048 if let Some(event) = msg.get("event") {
2049 if event.get("e").is_some() {
2050 for callbacks in self.stream_callbacks.lock().await.values() {
2051 for callback in callbacks {
2052 callback(event);
2053 }
2054 }
2055
2056 return;
2057 }
2058 }
2059
2060 warn!(
2061 "Received response for unknown or timed-out request: {}",
2062 data
2063 );
2064 }
2065
2066 async fn get_reconnect_url(
2077 &self,
2078 default_url: String,
2079 _connection: Arc<WebsocketConnection>,
2080 ) -> String {
2081 default_url
2082 }
2083}
2084
2085pub struct WebsocketStreams {
2086 pub common: Arc<WebsocketCommon>,
2087 pub stream_id_is_strictly_number: AtomicBool,
2088 url_paths: Vec<String>,
2089 is_connecting: Mutex<bool>,
2090 connection_streams: Mutex<HashMap<String, Arc<WebsocketConnection>>>,
2091 configuration: ConfigurationWebsocketStreams,
2092}
2093
2094impl WebsocketStreams {
2095 #[must_use]
2111 pub fn new(
2112 configuration: ConfigurationWebsocketStreams,
2113 mut connection_pool: Vec<Arc<WebsocketConnection>>,
2114 url_paths: Vec<String>,
2115 ) -> Arc<Self> {
2116 if !url_paths.is_empty() {
2117 let base_pool_size = configuration.mode.pool_size();
2118 let expected = base_pool_size * url_paths.len();
2119
2120 while connection_pool.len() < expected {
2121 connection_pool.push(WebsocketConnection::new(random_string()));
2122 }
2123 }
2124
2125 let agent_clone = configuration.agent.clone();
2126 let handshake_clone = configuration.handshake.clone();
2127 let user_agent_clone = configuration.user_agent.clone();
2128 let common = WebsocketCommon::new_with_handshake(
2129 connection_pool,
2130 configuration.mode.clone(),
2131 usize::try_from(configuration.reconnect_delay)
2132 .expect("reconnect_delay should fit in usize"),
2133 agent_clone,
2134 Some(user_agent_clone),
2135 handshake_clone,
2136 );
2137 Arc::new(Self {
2138 common,
2139 is_connecting: Mutex::new(false),
2140 connection_streams: Mutex::new(HashMap::new()),
2141 configuration,
2142 stream_id_is_strictly_number: AtomicBool::new(false),
2143 url_paths,
2144 })
2145 }
2146
2147 pub async fn connect(self: Arc<Self>, streams: Vec<String>) -> Result<(), WebsocketError> {
2164 if self.common.is_connected(None).await {
2165 info!("WebSocket connection already established");
2166 return Ok(());
2167 }
2168
2169 {
2170 let mut flag = self.is_connecting.lock().await;
2171 if *flag {
2172 info!("Already connecting...");
2173 return Ok(());
2174 }
2175 *flag = true;
2176 }
2177
2178 let handler: Arc<dyn WebsocketHandler> = self.clone();
2179 for conn in &self.common.connection_pool {
2180 conn.set_handler(handler.clone()).await;
2181 }
2182
2183 let base_pool_size = self.configuration.mode.pool_size();
2184
2185 let connect_fut = async {
2186 if self.url_paths.is_empty() {
2187 let url = self.prepare_url(&streams, None);
2188 self.common.clone().connect_pool(&url, None).await
2189 } else {
2190 let mut futures = Vec::with_capacity(self.url_paths.len());
2191
2192 for (i, path) in self.url_paths.iter().enumerate() {
2193 let start = i * base_pool_size;
2194
2195 let subset: Vec<Arc<WebsocketConnection>> = self
2196 .common
2197 .connection_pool
2198 .iter()
2199 .skip(start)
2200 .take(base_pool_size)
2201 .cloned()
2202 .collect();
2203
2204 if subset.len() != base_pool_size {
2205 return Err(WebsocketError::ServerError(format!(
2206 "connection_pool too small for url_paths: need {} per path, got {} for path index {}",
2207 base_pool_size,
2208 subset.len(),
2209 i
2210 )));
2211 }
2212
2213 for c in &subset {
2214 let mut st = c.state.lock().await;
2215 st.url_path = Some(path.clone());
2216 }
2217
2218 let url = self.prepare_url(&streams, Some(path.as_str()));
2219 let common = self.common.clone();
2220
2221 futures.push(async move { common.connect_pool(&url, Some(subset)).await });
2222 }
2223
2224 try_join_all(futures).await?;
2225 Ok(())
2226 }
2227 };
2228
2229 let connect_res = select! {
2230 () = sleep(Duration::from_secs(10)) => Err(WebsocketError::Timeout),
2231 r = connect_fut => r,
2232 };
2233
2234 {
2235 let mut flag = self.is_connecting.lock().await;
2236 *flag = false;
2237 }
2238
2239 connect_res
2240 }
2241
2242 pub async fn disconnect(&self) -> Result<(), WebsocketError> {
2258 for connection in &self.common.connection_pool {
2259 let mut conn_state = connection.state.lock().await;
2260 conn_state.stream_callbacks.clear();
2261 conn_state.pending_subscriptions.clear();
2262 }
2263 self.connection_streams.lock().await.clear();
2264 self.common.disconnect().await
2265 }
2266
2267 pub async fn is_connected(&self) -> bool {
2273 self.common.is_connected(None).await
2274 }
2275
2276 pub async fn ping_server(&self) {
2285 self.common.ping_server().await;
2286 }
2287
2288 pub async fn subscribe(
2308 self: Arc<Self>,
2309 streams: Vec<String>,
2310 id: Option<StreamId>,
2311 url_path: Option<&str>,
2312 ) {
2313 let streams: Vec<String> = {
2314 let map = self.connection_streams.lock().await;
2315 streams
2316 .into_iter()
2317 .filter(|s| {
2318 let key = self.stream_key(s, url_path);
2319 !map.contains_key(&key)
2320 })
2321 .collect()
2322 };
2323
2324 if streams.is_empty() {
2325 return;
2326 }
2327
2328 let connection_streams = self.handle_stream_assignment(streams, url_path).await;
2329
2330 for (conn, assigned_streams) in connection_streams {
2331 if !self.common.is_connected(Some(&conn)).await {
2332 info!(
2333 "Connection {} is not ready. Queuing subscription for streams: {:?}",
2334 conn.id, assigned_streams
2335 );
2336
2337 let mut conn_state = conn.state.lock().await;
2338 conn_state
2339 .pending_subscriptions
2340 .extend(assigned_streams.iter().cloned());
2341
2342 continue;
2343 }
2344
2345 self.send_subscription_payload(&conn, &assigned_streams, id.clone());
2346 }
2347 }
2348
2349 pub async fn unsubscribe(
2378 &self,
2379 streams: Vec<String>,
2380 id: Option<StreamId>,
2381 url_path: Option<&str>,
2382 ) {
2383 let request_id = normalize_stream_id(
2384 id.clone(),
2385 self.stream_id_is_strictly_number.load(Ordering::Relaxed),
2386 );
2387
2388 for stream in streams {
2389 let key = self.stream_key(&stream, url_path);
2390 let maybe_conn = { self.connection_streams.lock().await.get(&key).cloned() };
2391
2392 let conn = match maybe_conn {
2393 Some(c) => {
2394 if !self.common.is_connected(Some(&c)).await {
2395 warn!(
2396 "Stream {} not associated with an active connection.",
2397 stream
2398 );
2399 continue;
2400 }
2401 c
2402 }
2403 None => {
2404 warn!("Stream {} was not subscribed.", stream);
2405 continue;
2406 }
2407 };
2408
2409 let has_callbacks = {
2410 let conn_state = conn.state.lock().await;
2411 conn_state
2412 .stream_callbacks
2413 .get(&key)
2414 .is_some_and(|v| !v.is_empty())
2415 };
2416
2417 if has_callbacks {
2418 continue;
2419 }
2420
2421 let payload = json!({
2422 "method": "UNSUBSCRIBE",
2423 "params": [stream.clone()],
2424 "id": request_id,
2425 });
2426
2427 info!("UNSUBSCRIBE → {:?}", payload);
2428
2429 let common = Arc::clone(&self.common);
2430 let conn_clone = Arc::clone(&conn);
2431 let msg = serde_json::to_string(&payload).unwrap();
2432 spawn(async move {
2433 let _ = common
2434 .send(msg, None, false, Duration::ZERO, Some(conn_clone))
2435 .await;
2436 });
2437
2438 {
2439 let mut connection_streams = self.connection_streams.lock().await;
2440 connection_streams.remove(&key);
2441 }
2442 {
2443 let mut conn_state = conn.state.lock().await;
2444 conn_state.stream_callbacks.remove(&key);
2445 }
2446 }
2447 }
2448
2449 pub async fn is_subscribed(&self, stream: &str) -> bool {
2463 let map = self.connection_streams.lock().await;
2464
2465 if map.contains_key(stream) {
2466 return true;
2467 }
2468
2469 let suffix = format!("::{}", stream);
2470 map.keys().any(|k| k.ends_with(&suffix))
2471 }
2472
2473 fn stream_key(&self, stream: &str, url_path: Option<&str>) -> String {
2485 match url_path {
2486 Some(p) if !p.is_empty() => format!("{p}::{stream}"),
2487 _ => stream.to_string(),
2488 }
2489 }
2490
2491 fn prepare_url(&self, streams: &[String], url_path: Option<&str>) -> String {
2508 let mut url = format!(
2509 "{}/stream?streams={}",
2510 match url_path {
2511 Some(path) => format!(
2512 "{}/{}",
2513 self.configuration.ws_url.as_deref().unwrap_or(""),
2514 path
2515 ),
2516 None => self
2517 .configuration
2518 .ws_url
2519 .as_deref()
2520 .unwrap_or("")
2521 .to_string(),
2522 },
2523 streams.join("/")
2524 );
2525
2526 let time_unit = match &self.configuration.time_unit {
2527 Some(u) => u.to_string(),
2528 None => return url,
2529 };
2530
2531 match validate_time_unit(&time_unit) {
2532 Ok(Some(validated)) => {
2533 let sep = if url.contains('?') { '&' } else { '?' };
2534 url.push(sep);
2535 url.push_str("timeUnit=");
2536 url.push_str(validated);
2537 }
2538 Ok(None) => {}
2539 Err(e) => {
2540 error!("Invalid time unit provided: {:?}", e);
2541 }
2542 }
2543
2544 url
2545 }
2546
2547 async fn handle_stream_assignment(
2566 &self,
2567 streams: Vec<String>,
2568 url_path: Option<&str>,
2569 ) -> Vec<(Arc<WebsocketConnection>, Vec<String>)> {
2570 let mut connection_streams: Vec<(String, Arc<WebsocketConnection>)> = Vec::new();
2571
2572 for stream in streams {
2573 let key = self.stream_key(&stream, url_path);
2574
2575 let mut conn_opt = {
2576 let map = self.connection_streams.lock().await;
2577 map.get(&key).cloned()
2578 };
2579
2580 let need_new = if let Some(conn) = &conn_opt {
2581 let state = conn.state.lock().await;
2582 state.close_initiated || state.reconnection_pending
2583 } else {
2584 true
2585 };
2586
2587 if need_new {
2588 match self.common.get_connection(true, url_path).await {
2589 Ok(new_conn) => {
2590 let mut map = self.connection_streams.lock().await;
2591 map.insert(key.clone(), new_conn.clone());
2592 conn_opt = Some(new_conn);
2593 }
2594 Err(err) => {
2595 warn!(
2596 "No available WebSocket connection to subscribe stream `{}` (key `{}`): {:?}",
2597 stream, key, err
2598 );
2599 continue;
2600 }
2601 }
2602 }
2603
2604 if let Some(conn) = conn_opt {
2605 {
2606 let mut conn_state = conn.state.lock().await;
2607 conn_state.stream_callbacks.entry(key.clone()).or_default();
2608 }
2609 connection_streams.push((stream, conn));
2610 }
2611 }
2612
2613 let mut groups: Vec<(Arc<WebsocketConnection>, Vec<String>)> = Vec::new();
2614 for (stream, conn) in connection_streams {
2615 if let Some((_, vec)) = groups.iter_mut().find(|(c, _)| Arc::ptr_eq(c, &conn)) {
2616 vec.push(stream);
2617 } else {
2618 groups.push((conn, vec![stream]));
2619 }
2620 }
2621
2622 groups
2623 }
2624
2625 fn send_subscription_payload(
2638 &self,
2639 connection: &Arc<WebsocketConnection>,
2640 streams: &Vec<String>,
2641 id: Option<StreamId>,
2642 ) {
2643 let request_id = normalize_stream_id(
2644 id.clone(),
2645 self.stream_id_is_strictly_number.load(Ordering::Relaxed),
2646 );
2647
2648 let payload = json!({
2649 "method": "SUBSCRIBE",
2650 "params": streams,
2651 "id": request_id,
2652 });
2653
2654 info!("SUBSCRIBE → {:?}", payload);
2655
2656 let common = Arc::clone(&self.common);
2657 let msg = match serde_json::to_string(&payload) {
2658 Ok(s) => s,
2659 Err(e) => {
2660 error!("Failed to serialize SUBSCRIBE payload: {}", e);
2661 return;
2662 }
2663 };
2664 let conn_clone = Arc::clone(connection);
2665
2666 spawn(async move {
2667 let _ = common
2668 .send(msg, None, false, Duration::ZERO, Some(conn_clone))
2669 .await;
2670 });
2671 }
2672}
2673
2674#[async_trait]
2675impl WebsocketHandler for WebsocketStreams {
2676 async fn on_open(&self, _url: String, connection: Arc<WebsocketConnection>) {
2693 let pending_subs: Vec<String> = {
2694 let mut conn_state = connection.state.lock().await;
2695 take(&mut conn_state.pending_subscriptions)
2696 .into_iter()
2697 .collect()
2698 };
2699
2700 if !pending_subs.is_empty() {
2701 info!("Processing queued subscriptions for connection");
2702 self.send_subscription_payload(&connection, &pending_subs, None);
2703 }
2704 }
2705
2706 async fn on_message(&self, data: String, connection: Arc<WebsocketConnection>) {
2723 let msg: Value = match serde_json::from_str(&data) {
2724 Ok(v) => v,
2725 Err(err) => {
2726 error!(
2727 "Failed to parse WebSocket stream message {} – {}",
2728 data, err
2729 );
2730 return;
2731 }
2732 };
2733
2734 let (stream_name, payload) = match (
2735 msg.get("stream").and_then(Value::as_str),
2736 msg.get("data").cloned(),
2737 ) {
2738 (Some(name), Some(data)) => (name.to_string(), data),
2739 _ => return,
2740 };
2741
2742 let callbacks = {
2743 let conn_state = connection.state.lock().await;
2744 let key = self.stream_key(&stream_name, conn_state.url_path.as_deref());
2745 conn_state
2746 .stream_callbacks
2747 .get(&key)
2748 .cloned()
2749 .unwrap_or_else(Vec::new)
2750 };
2751
2752 for callback in callbacks {
2753 callback(&payload);
2754 }
2755 }
2756
2757 async fn get_reconnect_url(
2768 &self,
2769 _default_url: String,
2770 connection: Arc<WebsocketConnection>,
2771 ) -> String {
2772 let connection_streams = self.connection_streams.lock().await;
2773 let reconnect_streams = connection_streams
2774 .iter()
2775 .filter_map(|(key, conn_arc)| {
2776 if Arc::ptr_eq(conn_arc, &connection) {
2777 let stream = match key.split_once("::") {
2778 Some((_prefix, rest)) => rest.to_string(),
2779 None => key.clone(),
2780 };
2781 Some(stream)
2782 } else {
2783 None
2784 }
2785 })
2786 .collect::<Vec<_>>();
2787
2788 let url_path = {
2789 let st = connection.state.lock().await;
2790 st.url_path.as_deref().map(std::string::ToString::to_string)
2791 };
2792
2793 self.prepare_url(&reconnect_streams, url_path.as_deref())
2794 }
2795}
2796
2797pub struct WebsocketStream<T> {
2798 websocket_base: WebsocketBase,
2799 stream_or_id: String,
2800 url_path: Option<String>,
2801 callback: Mutex<Option<Arc<dyn Fn(&Value) + Send + Sync>>>,
2802 pub id: Option<StreamId>,
2803 _phantom: PhantomData<T>,
2804}
2805
2806impl<T> WebsocketStream<T>
2807where
2808 T: DeserializeOwned + Send + 'static,
2809{
2810 async fn on<F>(&self, event: &str, callback_fn: F)
2831 where
2832 F: Fn(T) + Send + Sync + 'static,
2833 {
2834 if event != "message" {
2835 return;
2836 }
2837
2838 let cb_wrapper: Arc<dyn Fn(&Value) + Send + Sync> =
2839 Arc::new(
2840 move |v: &Value| match serde_json::from_value::<T>(v.clone()) {
2841 Ok(data) => callback_fn(data),
2842 Err(e) => error!("Failed to deserialize stream payload: {:?}", e),
2843 },
2844 );
2845
2846 {
2847 let mut guard = self.callback.lock().await;
2848 *guard = Some(cb_wrapper.clone());
2849 }
2850
2851 match &self.websocket_base {
2852 WebsocketBase::WebsocketStreams(ws_streams) => {
2853 let key = ws_streams.stream_key(&self.stream_or_id, self.url_path.as_deref());
2854 let conn = {
2855 let map = ws_streams.connection_streams.lock().await;
2856 map.get(&key).cloned().expect("stream must be subscribed")
2857 };
2858
2859 {
2860 let mut conn_state = conn.state.lock().await;
2861 let entry = conn_state.stream_callbacks.entry(key).or_default();
2862
2863 if !entry
2864 .iter()
2865 .any(|existing| Arc::ptr_eq(existing, &cb_wrapper))
2866 {
2867 entry.push(cb_wrapper);
2868 }
2869 }
2870 }
2871 WebsocketBase::WebsocketApi(ws_api) => {
2872 let mut stream_callbacks = ws_api.stream_callbacks.lock().await;
2873 let entry = stream_callbacks
2874 .entry(self.stream_or_id.clone())
2875 .or_default();
2876
2877 if !entry
2878 .iter()
2879 .any(|existing| Arc::ptr_eq(existing, &cb_wrapper))
2880 {
2881 entry.push(cb_wrapper);
2882 }
2883 }
2884 }
2885 }
2886
2887 pub fn on_message<F>(self: &Arc<Self>, callback_fn: F)
2906 where
2907 T: Send + Sync,
2908 F: Fn(T) + Send + Sync + 'static,
2909 {
2910 let handler: Arc<Self> = Arc::clone(self);
2911
2912 std::thread::spawn(move || {
2913 let rt = tokio::runtime::Builder::new_current_thread()
2914 .enable_all()
2915 .build()
2916 .expect("failed to build Tokio runtime");
2917
2918 rt.block_on(handler.on("message", callback_fn));
2919 })
2920 .join()
2921 .expect("on_message thread panicked");
2922 }
2923
2924 pub async fn unsubscribe(&self) {
2939 let maybe_cb = {
2940 let mut guard = self.callback.lock().await;
2941 guard.take()
2942 };
2943
2944 if let Some(cb) = maybe_cb {
2945 match &self.websocket_base {
2946 WebsocketBase::WebsocketStreams(ws_streams) => {
2947 let key = ws_streams.stream_key(&self.stream_or_id, self.url_path.as_deref());
2948 let conn = {
2949 let map = ws_streams.connection_streams.lock().await;
2950 map.get(&key)
2951 .cloned()
2952 .expect("stream must have been subscribed")
2953 };
2954
2955 {
2956 let mut conn_state = conn.state.lock().await;
2957 if let Some(list) = conn_state.stream_callbacks.get_mut(&key) {
2958 list.retain(|existing| !Arc::ptr_eq(existing, &cb));
2959 }
2960 }
2961
2962 let stream = self.stream_or_id.clone();
2963 let id = self.id.clone();
2964 let url_path = self.url_path.clone();
2965 let websocket_streams_base = Arc::clone(ws_streams);
2966 spawn(async move {
2967 websocket_streams_base
2968 .unsubscribe(vec![stream], id, url_path.as_deref())
2969 .await;
2970 });
2971 }
2972 WebsocketBase::WebsocketApi(ws_api) => {
2973 let mut stream_callbacks = ws_api.stream_callbacks.lock().await;
2974 if let Some(list) = stream_callbacks.get_mut(&self.stream_or_id) {
2975 list.retain(|existing| !Arc::ptr_eq(existing, &cb));
2976 }
2977 }
2978 }
2979 }
2980 }
2981}
2982
2983pub async fn create_stream_handler<T>(
2998 websocket_base: WebsocketBase,
2999 stream_or_id: String,
3000 id: Option<StreamId>,
3001 url_path: Option<String>,
3002) -> Arc<WebsocketStream<T>>
3003where
3004 T: DeserializeOwned + Send + 'static,
3005{
3006 match &websocket_base {
3007 WebsocketBase::WebsocketStreams(ws_streams) => {
3008 ws_streams
3009 .clone()
3010 .subscribe(vec![stream_or_id.clone()], id.clone(), url_path.as_deref())
3011 .await;
3012 }
3013 WebsocketBase::WebsocketApi(_) => {}
3014 }
3015
3016 Arc::new(WebsocketStream {
3017 websocket_base,
3018 stream_or_id,
3019 url_path,
3020 id,
3021 callback: Mutex::new(None),
3022 _phantom: PhantomData,
3023 })
3024}
3025
3026#[cfg(test)]
3027mod tests {
3028 use crate::TOKIO_SHARED_RT;
3029 use crate::common::utils::{SignatureGenerator, build_user_agent};
3030 use crate::common::websocket::{
3031 PendingRequest, ReconnectEntry, SendWebsocketMessageResult, WebsocketApi, WebsocketBase,
3032 WebsocketCommon, WebsocketConnection, WebsocketEvent, WebsocketEventEmitter,
3033 WebsocketHandler, WebsocketMessageSendOptions, WebsocketMode, WebsocketSessionLogonReq,
3034 WebsocketStream, WebsocketStreams, create_stream_handler,
3035 };
3036 use crate::config::{
3037 ConfigurationWebsocketApi, ConfigurationWebsocketStreams, PrivateKey, WebsocketHandshakeFn,
3038 };
3039 use crate::errors::{WebsocketConnectionFailureReason, WebsocketError};
3040 use crate::models::{StreamId, TimeUnit};
3041 use async_trait::async_trait;
3042 use futures::{SinkExt, StreamExt};
3043 use http::header::USER_AGENT;
3044 use regex::Regex;
3045 use serde_json::{Value, json};
3046 use std::collections::{BTreeMap, HashSet};
3047 use std::marker::PhantomData;
3048 use std::net::SocketAddr;
3049 use std::sync::{
3050 Arc,
3051 atomic::{AtomicBool, AtomicUsize, Ordering},
3052 };
3053 use tokio::net::TcpListener;
3054 use tokio::sync::{
3055 Mutex,
3056 mpsc::{Receiver, unbounded_channel},
3057 oneshot,
3058 };
3059 use tokio::time::{Duration, advance, pause, resume, sleep, timeout};
3060 use tokio_tungstenite::{accept_async, accept_hdr_async, tungstenite, tungstenite::Message};
3061 use tungstenite::handshake::server::Request;
3062
3063 struct AbortOnDrop(tokio::task::JoinHandle<()>);
3069
3070 impl Drop for AbortOnDrop {
3071 fn drop(&mut self) {
3072 self.0.abort();
3073 }
3074 }
3075
3076 fn spawn_mock_ws_listener(listener: TcpListener) -> AbortOnDrop {
3080 AbortOnDrop(tokio::spawn(async move {
3081 if let Ok((stream, _)) = listener.accept().await {
3082 let Ok(mut ws) = accept_async(stream).await else {
3083 return;
3084 };
3085 while ws.next().await.is_some() {}
3086 }
3087 }))
3088 }
3089
3090 fn subscribe_events(common: &WebsocketCommon) -> Arc<Mutex<Vec<WebsocketEvent>>> {
3091 let events = Arc::new(Mutex::new(Vec::new()));
3092 let events_clone = events.clone();
3093 common.events.subscribe(move |event| {
3094 let events_clone = events_clone.clone();
3095 tokio::spawn(async move {
3096 events_clone.lock().await.push(event);
3097 });
3098 });
3099 events
3100 }
3101
3102 async fn create_connection(
3103 id: &str,
3104 has_writer: bool,
3105 reconnection_pending: bool,
3106 renewal_pending: bool,
3107 close_initiated: bool,
3108 ) -> Arc<WebsocketConnection> {
3109 let conn = WebsocketConnection::new(id);
3110 let mut st = conn.state.lock().await;
3111 st.reconnection_pending = reconnection_pending;
3112 st.renewal_pending = renewal_pending;
3113 st.close_initiated = close_initiated;
3114 if has_writer {
3115 let (tx, _) = unbounded_channel::<Message>();
3116 st.ws_write_tx = Some(tx);
3117 } else {
3118 st.ws_write_tx = None;
3119 }
3120 drop(st);
3121 conn
3122 }
3123
3124 fn create_websocket_api(
3125 time_unit: Option<TimeUnit>,
3126 mode: Option<WebsocketMode>,
3127 auto_session_relogon: Option<bool>,
3128 ) -> Arc<WebsocketApi> {
3129 let mode = mode.unwrap_or(WebsocketMode::Single);
3130 let auto_session_relogon = auto_session_relogon.unwrap_or(true);
3131 let sig_gen = SignatureGenerator::new(
3132 Some("api_secret".into()),
3133 None::<PrivateKey>,
3134 None::<String>,
3135 );
3136 let config = ConfigurationWebsocketApi {
3137 api_key: Some("api_key".into()),
3138 api_secret: Some("api_secret".into()),
3139 private_key: None,
3140 private_key_passphrase: None,
3141 ws_url: Some("wss://example.com".into()),
3142 mode,
3143 reconnect_delay: 1000,
3144 signature_gen: sig_gen,
3145 timeout: 500,
3146 time_unit,
3147 auto_session_relogon,
3148 agent: None,
3149 handshake: None,
3150 user_agent: build_user_agent("product"),
3151 };
3152 let conn1 = WebsocketConnection::new("c1");
3153 let conn2 = WebsocketConnection::new("c2");
3154 WebsocketApi::new(config, vec![conn1, conn2])
3155 }
3156
3157 fn create_websocket_streams(
3158 ws_url: Option<&str>,
3159 conns: Option<Vec<Arc<WebsocketConnection>>>,
3160 url_paths: Option<Vec<String>>,
3161 ) -> Arc<WebsocketStreams> {
3162 let mut connections: Vec<Arc<WebsocketConnection>> = vec![];
3163 let url_paths = url_paths.unwrap_or_default();
3164 if conns.is_none() {
3165 connections.push(WebsocketConnection::new("c1"));
3166 connections.push(WebsocketConnection::new("c2"));
3167 } else {
3168 connections = conns.expect("Expected connections to be set");
3169 }
3170 let config = ConfigurationWebsocketStreams {
3171 ws_url: Some(ws_url.unwrap_or("example.com").to_string()),
3172 mode: WebsocketMode::Single,
3173 reconnect_delay: 500,
3174 time_unit: None,
3175 agent: None,
3176 handshake: None,
3177 user_agent: build_user_agent("product"),
3178 };
3179 WebsocketStreams::new(config, connections, url_paths)
3180 }
3181
3182 fn subscribe_to_emitter(emitter: &WebsocketEventEmitter) -> Receiver<WebsocketEvent> {
3183 let (test_tx, test_rx) = tokio::sync::mpsc::channel(16);
3184 let _sub = emitter.subscribe(move |evt| {
3185 let _ = test_tx.try_send(evt);
3186 });
3187 test_rx
3188 }
3189
3190 async fn expect_websocket_event(rx: &mut Receiver<WebsocketEvent>) -> WebsocketEvent {
3191 timeout(Duration::from_millis(200), rx.recv())
3192 .await
3193 .expect("timed out waiting for event")
3194 .expect("subscriber channel closed")
3195 }
3196
3197 async fn eventually_async<F, Fut>(max_wait: Duration, mut f: F) -> bool
3198 where
3199 F: FnMut() -> Fut,
3200 Fut: std::future::Future<Output = bool>,
3201 {
3202 let start = tokio::time::Instant::now();
3203 while start.elapsed() < max_wait {
3204 if f().await {
3205 return true;
3206 }
3207 sleep(Duration::from_millis(20)).await;
3208 }
3209 false
3210 }
3211
3212 mod event_emitter {
3213 use super::*;
3214
3215 #[test]
3216 fn event_emitter_subscribe_and_emit() {
3217 TOKIO_SHARED_RT.block_on(async {
3218 let emitter = WebsocketEventEmitter::new();
3219 let (tx, rx) = oneshot::channel();
3220 let tx = Arc::new(std::sync::Mutex::new(Some(tx)));
3221 let tx_clone = tx.clone();
3222 let _sub = emitter.subscribe(move |event| {
3223 if let Some(sender) = tx_clone.lock().unwrap().take() {
3224 let _ = sender.send(event);
3225 }
3226 });
3227 emitter.emit(&WebsocketEvent::Open);
3228 let received = timeout(Duration::from_millis(100), rx)
3229 .await
3230 .expect("timed out");
3231 assert_eq!(received, Ok(WebsocketEvent::Open));
3232 });
3233 }
3234
3235 #[test]
3236 fn single_subscriber_gets_event() {
3237 TOKIO_SHARED_RT.block_on(async {
3238 let emitter = WebsocketEventEmitter::new();
3239 let mut rx = subscribe_to_emitter(&emitter);
3240
3241 let e1 = WebsocketEvent::Open;
3242 emitter.emit(&e1);
3243
3244 let got = expect_websocket_event(&mut rx).await;
3245 assert_eq!(got, e1);
3246 });
3247 }
3248
3249 #[test]
3250 fn multiple_subscribers_get_event() {
3251 TOKIO_SHARED_RT.block_on(async {
3252 let emitter = WebsocketEventEmitter::new();
3253 let mut rx1 = subscribe_to_emitter(&emitter);
3254 let mut rx2 = subscribe_to_emitter(&emitter);
3255
3256 let e = WebsocketEvent::Message("hello".into());
3257 emitter.emit(&e);
3258
3259 assert_eq!(expect_websocket_event(&mut rx1).await, e.clone());
3260 assert_eq!(expect_websocket_event(&mut rx2).await, e);
3261 });
3262 }
3263
3264 #[test]
3265 fn closed_subscribers_are_pruned() {
3266 TOKIO_SHARED_RT.block_on(async {
3267 let emitter = WebsocketEventEmitter::new();
3268 let rx1 = subscribe_to_emitter(&emitter);
3269 let mut rx2 = subscribe_to_emitter(&emitter);
3270 drop(rx1);
3271
3272 let e = WebsocketEvent::Pong;
3273 emitter.emit(&e);
3274
3275 assert_eq!(expect_websocket_event(&mut rx2).await, e);
3276 });
3277 }
3278
3279 #[test]
3280 fn prune_on_error_does_not_hang() {
3281 TOKIO_SHARED_RT.block_on(async {
3282 let emitter = WebsocketEventEmitter::new();
3283 let rx = subscribe_to_emitter(&emitter);
3284 drop(rx);
3285
3286 let e = WebsocketEvent::Close(1000, "bye".into());
3287 emitter.emit(&e);
3288 });
3289 }
3290 }
3291
3292 mod websocket_common {
3293 use super::*;
3294
3295 mod initialisation {
3296 use super::*;
3297
3298 #[test]
3299 fn single_mode() {
3300 TOKIO_SHARED_RT.block_on(async {
3301 let common = WebsocketCommon::new(vec![], WebsocketMode::Single, 0, None, None);
3302 assert_eq!(common.connection_pool.len(), 1);
3303 });
3304 }
3305
3306 #[test]
3307 fn pool_mode() {
3308 TOKIO_SHARED_RT.block_on(async {
3309 let common =
3310 WebsocketCommon::new(vec![], WebsocketMode::Pool(3), 0, None, None);
3311 assert_eq!(common.connection_pool.len(), 3);
3312 });
3313 }
3314 }
3315
3316 mod spawn_reconnect_loop {
3317 use super::*;
3318
3319 #[test]
3320 fn successful_reconnect_entry_triggers_init_connect() {
3321 TOKIO_SHARED_RT.block_on(async {
3322 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
3323 let addr = listener.local_addr().unwrap();
3324 tokio::spawn(async move {
3325 if let Ok((stream, _)) = listener.accept().await {
3326 let mut ws = accept_async(stream).await.unwrap();
3327 sleep(Duration::from_secs(5)).await;
3328 let _ = ws.close(None).await;
3329 }
3330 });
3331
3332 let conn = WebsocketConnection::new("c1");
3333 let common = WebsocketCommon::new(
3334 vec![conn.clone()],
3335 WebsocketMode::Single,
3336 10,
3337 None,
3338 None,
3339 );
3340 let url = format!("ws://{addr}");
3341 common
3342 .reconnect_tx
3343 .send(ReconnectEntry {
3344 connection_id: "c1".into(),
3345 url: url.clone(),
3346 is_renewal: false,
3347 })
3348 .await
3349 .unwrap();
3350
3351 let mut ok = false;
3352 for _ in 0..100 {
3353 if conn.state.lock().await.ws_write_tx.is_some() {
3354 ok = true;
3355 break;
3356 }
3357 sleep(Duration::from_millis(50)).await;
3358 }
3359 assert!(ok, "expected ws_write_tx to be Some after reconnect");
3360 });
3361 }
3362
3363 #[test]
3364 fn reconnect_entry_with_unknown_id_is_ignored() {
3365 TOKIO_SHARED_RT.block_on(async {
3366 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
3367 let addr = listener.local_addr().unwrap();
3368 tokio::spawn(async move {
3369 if let Ok((stream, _)) = listener.accept().await {
3370 let mut ws = accept_async(stream).await.unwrap();
3371 let _ = ws.close(None).await;
3372 }
3373 });
3374
3375 let conn = WebsocketConnection::new("c1");
3376 let common = WebsocketCommon::new(
3377 vec![conn.clone()],
3378 WebsocketMode::Single,
3379 5,
3380 None,
3381 None,
3382 );
3383 let url = format!("ws://{addr}");
3384 common
3385 .reconnect_tx
3386 .send(ReconnectEntry {
3387 connection_id: "other".into(),
3388 url,
3389 is_renewal: false,
3390 })
3391 .await
3392 .unwrap();
3393
3394 sleep(Duration::from_secs(1)).await;
3395
3396 let st = conn.state.lock().await;
3397 assert!(st.ws_write_tx.is_none());
3398 });
3399 }
3400
3401 #[test]
3402 fn renewal_entries_bypass_initial_delay() {
3403 TOKIO_SHARED_RT.block_on(async {
3404 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
3405 let addr = listener.local_addr().unwrap();
3406 tokio::spawn(async move {
3407 if let Ok((stream, _)) = listener.accept().await {
3408 let mut ws = accept_async(stream).await.unwrap();
3409 let _ = ws.close(None).await;
3410 }
3411 });
3412
3413 let conn = WebsocketConnection::new("renew");
3414 let common = WebsocketCommon::new(
3415 vec![conn.clone()],
3416 WebsocketMode::Single,
3417 200,
3418 None,
3419 None,
3420 );
3421 let url = format!("ws://{addr}");
3422 common
3423 .reconnect_tx
3424 .send(ReconnectEntry {
3425 connection_id: "renew".into(),
3426 url: url.clone(),
3427 is_renewal: true,
3428 })
3429 .await
3430 .unwrap();
3431
3432 sleep(Duration::from_secs(2)).await;
3433
3434 let st = conn.state.lock().await;
3435
3436 assert!(st.ws_write_tx.is_some());
3437 });
3438 }
3439
3440 #[test]
3441 fn non_renewal_entries_respect_initial_delay() {
3442 TOKIO_SHARED_RT.block_on(async {
3443 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
3444 let addr = listener.local_addr().unwrap();
3445 let _listener_guard = spawn_mock_ws_listener(listener);
3446
3447 let reconnect_delay_ms = 1500;
3448 let conn = WebsocketConnection::new("nonrenew");
3449 let common = WebsocketCommon::new(
3450 vec![conn.clone()],
3451 WebsocketMode::Single,
3452 reconnect_delay_ms,
3453 None,
3454 None,
3455 );
3456 let url = format!("ws://{addr}");
3457 let queued_at = tokio::time::Instant::now();
3458 common
3459 .reconnect_tx
3460 .send(ReconnectEntry {
3461 connection_id: "nonrenew".into(),
3462 url: url.clone(),
3463 is_renewal: false,
3464 })
3465 .await
3466 .unwrap();
3467
3468 sleep(Duration::from_millis(100)).await;
3469 if queued_at.elapsed()
3470 < Duration::from_millis(reconnect_delay_ms as u64)
3471 .saturating_sub(Duration::from_millis(200))
3472 {
3473 assert!(conn.state.lock().await.ws_write_tx.is_none());
3474 }
3475
3476 let mut ok = false;
3477 for _ in 0..200 {
3478 if conn.state.lock().await.ws_write_tx.is_some() {
3479 ok = true;
3480 break;
3481 }
3482 sleep(Duration::from_millis(50)).await;
3483 }
3484 assert!(
3485 ok,
3486 "expected ws_write_tx to be Some after reconnect delay elapsed"
3487 );
3488 });
3489 }
3490 }
3491
3492 mod spawn_renewal_loop {
3493 use super::*;
3494
3495 #[tokio::test]
3496 async fn scheduling_renewal_does_not_panic_for_known_connection() {
3497 pause();
3498
3499 let conn = WebsocketConnection::new("known");
3500 let common =
3501 WebsocketCommon::new(vec![conn.clone()], WebsocketMode::Single, 0, None, None);
3502 let url = "wss://example".to_string();
3503 common
3504 .renewal_tx
3505 .send((conn.id.clone(), url))
3506 .await
3507 .unwrap();
3508 advance(Duration::from_secs(23 * 60 * 60 + 1)).await;
3509 resume();
3510 }
3511
3512 #[tokio::test]
3513 async fn scheduling_renewal_ignored_for_unknown_connection() {
3514 pause();
3515
3516 let conn = WebsocketConnection::new("c1");
3517 let common =
3518 WebsocketCommon::new(vec![conn.clone()], WebsocketMode::Single, 0, None, None);
3519 common
3520 .renewal_tx
3521 .send(("other".into(), "u".into()))
3522 .await
3523 .unwrap();
3524 advance(Duration::from_secs(23 * 60 * 60 + 1)).await;
3525
3526 resume();
3527 }
3528 }
3529
3530 mod reconnect_regressions {
3531 use super::*;
3532
3533 #[test]
3534 fn init_connect_is_not_skipped_when_reconnection_pending() {
3535 TOKIO_SHARED_RT.block_on(async {
3536 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
3537 let addr = listener.local_addr().unwrap();
3538
3539 tokio::spawn(async move {
3540 if let Ok((stream, _)) = listener.accept().await {
3541 tokio::spawn(async move {
3542 let mut ws = accept_async(stream).await.unwrap();
3543 sleep(Duration::from_millis(500)).await;
3544 let _ = ws.close(None).await;
3545 });
3546 }
3547 });
3548
3549 let conn = WebsocketConnection::new("c-reconnect");
3550 {
3551 let mut st = conn.state.lock().await;
3552 let (tx, _) = unbounded_channel::<Message>();
3553 st.ws_write_tx = Some(tx);
3554 st.reconnection_pending = true;
3555 }
3556
3557 let common = WebsocketCommon::new(
3558 vec![conn.clone()],
3559 WebsocketMode::Single,
3560 0,
3561 None,
3562 None,
3563 );
3564
3565 let url = format!("ws://{addr}");
3566 common
3567 .clone()
3568 .init_connect(&url, false, Some(conn.clone()))
3569 .await
3570 .unwrap();
3571
3572 let ok = eventually_async(Duration::from_secs(2), || {
3573 let conn = conn.clone();
3574 async move {
3575 let st = conn.state.lock().await;
3576 st.ws_write_tx.is_some() && !st.reconnection_pending
3577 }
3578 })
3579 .await;
3580
3581 assert!(
3582 ok,
3583 "expected writer installed and reconnection_pending cleared"
3584 );
3585 });
3586 }
3587
3588 #[test]
3589 fn pending_request_is_resolved_on_socket_drop() {
3590 TOKIO_SHARED_RT.block_on(async {
3591 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
3592 let addr = listener.local_addr().unwrap();
3593
3594 tokio::spawn(async move {
3595 if let Ok((stream, _)) = listener.accept().await {
3596 let mut ws = accept_async(stream).await.unwrap();
3597 let _ = ws.next().await;
3598 let _ = ws.close(None).await;
3599 }
3600 });
3601
3602 let conn = WebsocketConnection::new("c1");
3603 let common = WebsocketCommon::new(
3604 vec![conn.clone()],
3605 WebsocketMode::Single,
3606 0,
3607 None,
3608 None,
3609 );
3610
3611 let url = format!("ws://{addr}");
3612 common
3613 .clone()
3614 .init_connect(&url, false, Some(conn.clone()))
3615 .await
3616 .unwrap();
3617
3618 let rx = common
3619 .send(
3620 "{\"id\":\"req-1\",\"method\":\"PING\"}".to_string(),
3621 Some("req-1".to_string()),
3622 true,
3623 Duration::from_millis(150),
3624 Some(conn.clone()),
3625 )
3626 .await
3627 .unwrap()
3628 .expect("expected oneshot receiver");
3629
3630 let res = timeout(Duration::from_secs(2), rx)
3631 .await
3632 .expect("did not resolve pending request")
3633 .expect("oneshot cancelled");
3634
3635 assert!(matches!(res, Err(WebsocketError::Timeout)));
3636
3637 let ok = eventually_async(Duration::from_secs(1), || {
3638 let conn = conn.clone();
3639 async move { conn.state.lock().await.pending_requests.is_empty() }
3640 })
3641 .await;
3642
3643 assert!(ok, "pending_requests should be drained");
3644 });
3645 }
3646 }
3647
3648 mod is_connection_ready {
3649 use super::*;
3650
3651 #[test]
3652 fn is_connection_ready() {
3653 TOKIO_SHARED_RT.block_on(async {
3654 let conn = WebsocketConnection::new("c1");
3655 let common = WebsocketCommon::new(
3656 vec![conn.clone()],
3657 WebsocketMode::Single,
3658 0,
3659 None,
3660 None,
3661 );
3662 assert!(!common.is_connection_ready(&conn, false).await);
3663 assert!(common.is_connection_ready(&conn, true).await);
3664 });
3665 }
3666
3667 #[test]
3668 fn connection_ready_basic() {
3669 TOKIO_SHARED_RT.block_on(async {
3670 let conn = create_connection("c1", true, false, false, false).await;
3671 let common = WebsocketCommon::new(
3672 vec![conn.clone()],
3673 WebsocketMode::Single,
3674 0,
3675 None,
3676 None,
3677 );
3678 assert!(common.is_connection_ready(&conn, false).await);
3679 });
3680 }
3681
3682 #[test]
3683 fn connection_not_ready_without_writer() {
3684 TOKIO_SHARED_RT.block_on(async {
3685 let conn = create_connection("c1", false, false, false, false).await;
3686 let common = WebsocketCommon::new(
3687 vec![conn.clone()],
3688 WebsocketMode::Single,
3689 0,
3690 None,
3691 None,
3692 );
3693 assert!(!common.is_connection_ready(&conn, false).await);
3694 assert!(common.is_connection_ready(&conn, true).await);
3695 });
3696 }
3697
3698 #[test]
3699 fn connection_not_ready_when_flagged() {
3700 TOKIO_SHARED_RT.block_on(async {
3701 let conn1 = create_connection("c1", true, true, false, false).await;
3702 let conn2 = create_connection("c2", true, false, true, false).await;
3703 let conn3 = create_connection("c3", true, false, false, true).await;
3704
3705 let common = WebsocketCommon::new(
3706 vec![conn1.clone(), conn2.clone(), conn3.clone()],
3707 WebsocketMode::Pool(3),
3708 0,
3709 None,
3710 None,
3711 );
3712
3713 assert!(!common.is_connection_ready(&conn1, false).await);
3714 assert!(common.is_connection_ready(&conn2, false).await);
3715 assert!(!common.is_connection_ready(&conn3, false).await);
3716 });
3717 }
3718 }
3719
3720 mod is_connected {
3721 use super::*;
3722
3723 #[test]
3724 fn with_pool_various_connections() {
3725 TOKIO_SHARED_RT.block_on(async {
3726 let conn_a = create_connection("a", true, false, false, false).await;
3727 let conn_b = create_connection("b", false, false, false, false).await;
3728 let conn_c = create_connection("c", true, true, false, false).await;
3729 let pool = vec![conn_a.clone(), conn_b.clone(), conn_c.clone()];
3730 let common = WebsocketCommon::new(pool, WebsocketMode::Pool(3), 0, None, None);
3731
3732 assert!(common.is_connected(None).await);
3733 assert!(common.is_connected(Some(&conn_a)).await);
3734 assert!(!common.is_connected(Some(&conn_b)).await);
3735 assert!(!common.is_connected(Some(&conn_c)).await);
3736 });
3737 }
3738
3739 #[test]
3740 fn with_pool_all_bad_connections() {
3741 TOKIO_SHARED_RT.block_on(async {
3742 let bad1 = create_connection("c1", false, false, false, false).await;
3743 let bad2 = create_connection("c2", true, true, false, false).await;
3744 let bad3 = create_connection("c3", true, false, false, true).await;
3745 let common = WebsocketCommon::new(
3746 vec![bad1, bad2, bad3],
3747 WebsocketMode::Pool(3),
3748 0,
3749 None,
3750 None,
3751 );
3752
3753 assert!(!common.is_connected(None).await);
3754 });
3755 }
3756
3757 #[test]
3758 fn with_pool_ignore_close_initiated() {
3759 TOKIO_SHARED_RT.block_on(async {
3760 let good = create_connection("c1", true, false, false, false).await;
3761 let closed = create_connection("c2", true, false, false, true).await;
3762 let bad = create_connection("c3", false, false, false, false).await;
3763 let common = WebsocketCommon::new(
3764 vec![closed.clone(), good.clone(), bad.clone()],
3765 WebsocketMode::Pool(3),
3766 0,
3767 None,
3768 None,
3769 );
3770
3771 assert!(common.is_connected(None).await);
3772 assert!(!common.is_connected(Some(&closed)).await);
3773 });
3774 }
3775 }
3776
3777 mod get_available_connections {
3778 use super::*;
3779
3780 #[test]
3781 fn single_mode() {
3782 TOKIO_SHARED_RT.block_on(async {
3783 let common = WebsocketCommon::new(vec![], WebsocketMode::Single, 0, None, None);
3784 let connections = common.get_available_connections(false, None).await;
3785 assert_eq!(connections[0].id, common.connection_pool[0].id);
3786 });
3787 }
3788
3789 #[test]
3790 fn single_mode_with_url_path_does_not_force_first_connection() {
3791 TOKIO_SHARED_RT.block_on(async {
3792 let conn1 = WebsocketConnection::new("c1");
3793 let conn2 = WebsocketConnection::new("c2");
3794
3795 let (tx2, _rx2) = unbounded_channel();
3796 {
3797 let mut s2 = conn2.state.lock().await;
3798 s2.ws_write_tx = Some(tx2);
3799 s2.url_path = Some("path1".to_string());
3800 }
3801
3802 {
3803 let mut s1 = conn1.state.lock().await;
3804 s1.url_path = Some("path1".to_string());
3805 }
3806
3807 let pool = vec![conn1.clone(), conn2.clone()];
3808 let common = WebsocketCommon::new(pool, WebsocketMode::Single, 0, None, None);
3809
3810 let connections = common.get_available_connections(false, Some("path1")).await;
3811
3812 assert_eq!(connections.len(), 1);
3813 assert_eq!(connections[0].id, "c2");
3814 });
3815 }
3816
3817 #[test]
3818 fn pool_mode_not_ready() {
3819 TOKIO_SHARED_RT.block_on(async {
3820 let common =
3821 WebsocketCommon::new(vec![], WebsocketMode::Pool(2), 0, None, None);
3822 let connections = common.get_available_connections(false, None).await;
3823 assert!(connections.is_empty());
3824 });
3825 }
3826
3827 #[test]
3828 fn pool_mode_with_ready() {
3829 TOKIO_SHARED_RT.block_on(async {
3830 let conn1 = WebsocketConnection::new("c1");
3831 let conn2 = WebsocketConnection::new("c2");
3832 let (tx1, _rx1) = unbounded_channel();
3833 {
3834 let mut s1 = conn1.state.lock().await;
3835 s1.ws_write_tx = Some(tx1);
3836 }
3837 let pool = vec![conn1.clone(), conn2.clone()];
3838 let common = WebsocketCommon::new(pool, WebsocketMode::Pool(2), 0, None, None);
3839 let connections = common.get_available_connections(false, None).await;
3840 assert!(connections.len() == 1);
3841 });
3842 }
3843 }
3844
3845 mod get_connection {
3846 use super::*;
3847
3848 #[test]
3849 fn single_mode() {
3850 TOKIO_SHARED_RT.block_on(async {
3851 let common = WebsocketCommon::new(vec![], WebsocketMode::Single, 0, None, None);
3852 let conn = common
3853 .get_connection(false, None)
3854 .await
3855 .expect("should get connection");
3856 assert_eq!(conn.id, common.connection_pool[0].id);
3857 });
3858 }
3859
3860 #[test]
3861 fn single_mode_with_url_path_selects_matching_ready_connection() {
3862 TOKIO_SHARED_RT.block_on(async {
3863 let conn1 = WebsocketConnection::new("c1");
3864 let conn2 = WebsocketConnection::new("c2");
3865
3866 let (tx1, _rx1) = unbounded_channel();
3867 {
3868 let mut s1 = conn1.state.lock().await;
3869 s1.ws_write_tx = Some(tx1);
3870 s1.url_path = Some("path2".to_string());
3871 }
3872
3873 let (tx2, _rx2) = unbounded_channel();
3874 {
3875 let mut s2 = conn2.state.lock().await;
3876 s2.ws_write_tx = Some(tx2);
3877 s2.url_path = Some("path1".to_string());
3878 }
3879
3880 let pool = vec![conn1.clone(), conn2.clone()];
3881 let common = WebsocketCommon::new(pool, WebsocketMode::Single, 0, None, None);
3882
3883 let chosen = common
3884 .get_connection(false, Some("path1"))
3885 .await
3886 .expect("should get connection");
3887
3888 assert_eq!(chosen.id, "c2");
3889 });
3890 }
3891
3892 #[test]
3893 fn pool_mode_not_ready() {
3894 TOKIO_SHARED_RT.block_on(async {
3895 let common =
3896 WebsocketCommon::new(vec![], WebsocketMode::Pool(2), 0, None, None);
3897 let result = common.get_connection(false, None).await;
3898 assert!(matches!(
3899 result,
3900 Err(crate::errors::WebsocketError::NotConnected)
3901 ));
3902 });
3903 }
3904
3905 #[test]
3906 fn pool_mode_with_ready() {
3907 TOKIO_SHARED_RT.block_on(async {
3908 let conn1 = WebsocketConnection::new("c1");
3909 let conn2 = WebsocketConnection::new("c2");
3910 let (tx1, _rx1) = unbounded_channel();
3911 {
3912 let mut s1 = conn1.state.lock().await;
3913 s1.ws_write_tx = Some(tx1);
3914 }
3915 let pool = vec![conn1.clone(), conn2.clone()];
3916 let common = WebsocketCommon::new(pool, WebsocketMode::Pool(2), 0, None, None);
3917 let result = common.get_connection(false, None).await;
3918 assert!(result.is_ok());
3919 let chosen = result.unwrap();
3920 assert_eq!(chosen.id, conn1.id);
3921 });
3922 }
3923
3924 #[test]
3925 fn pool_mode_with_url_path_filters_connections() {
3926 TOKIO_SHARED_RT.block_on(async {
3927 let conn1 = WebsocketConnection::new("c1");
3928 let conn2 = WebsocketConnection::new("c2");
3929
3930 let (tx1, _rx1) = unbounded_channel();
3931 {
3932 let mut s1 = conn1.state.lock().await;
3933 s1.ws_write_tx = Some(tx1);
3934 s1.url_path = Some("path1".to_string());
3935 }
3936
3937 let (tx2, _rx2) = unbounded_channel();
3938 {
3939 let mut s2 = conn2.state.lock().await;
3940 s2.ws_write_tx = Some(tx2);
3941 s2.url_path = Some("path2".to_string());
3942 }
3943
3944 let pool = vec![conn1.clone(), conn2.clone()];
3945 let common = WebsocketCommon::new(pool, WebsocketMode::Pool(2), 0, None, None);
3946
3947 let chosen = common
3948 .get_connection(false, Some("path2"))
3949 .await
3950 .expect("should pick ready connection for path2");
3951
3952 assert_eq!(chosen.id, "c2");
3953 });
3954 }
3955
3956 #[test]
3957 fn url_path_no_match_returns_not_connected() {
3958 TOKIO_SHARED_RT.block_on(async {
3959 let conn1 = WebsocketConnection::new("c1");
3960 let (tx1, _rx1) = unbounded_channel();
3961 {
3962 let mut s1 = conn1.state.lock().await;
3963 s1.ws_write_tx = Some(tx1);
3964 s1.url_path = Some("path1".to_string());
3965 }
3966
3967 let pool = vec![conn1.clone()];
3968 let common = WebsocketCommon::new(pool, WebsocketMode::Pool(1), 0, None, None);
3969
3970 let result = common.get_connection(false, Some("path2")).await;
3971 assert!(matches!(
3972 result,
3973 Err(crate::errors::WebsocketError::NotConnected)
3974 ));
3975 });
3976 }
3977 }
3978
3979 mod close_connection_gracefully {
3980 use super::*;
3981
3982 #[tokio::test]
3983 async fn waits_for_pending_requests_then_closes() {
3984 pause();
3985
3986 let conn = WebsocketConnection::new("c1");
3987 let (tx, mut rx) = unbounded_channel::<Message>();
3988 let (req_tx, _req_rx) = oneshot::channel();
3989 {
3990 let mut st = conn.state.lock().await;
3991 st.pending_requests
3992 .insert("r".to_string(), PendingRequest { completion: req_tx });
3993 }
3994 let common =
3995 WebsocketCommon::new(vec![conn.clone()], WebsocketMode::Single, 0, None, None);
3996 let close_fut = common.close_connection_gracefully(tx.clone(), conn.clone());
3997 advance(Duration::from_secs(1)).await;
3998 {
3999 let mut st = conn.state.lock().await;
4000 st.pending_requests.clear();
4001 }
4002 conn.drain_notify.notify_waiters();
4003 advance(Duration::from_secs(1)).await;
4004 close_fut.await.unwrap();
4005 match rx.try_recv() {
4006 Ok(Message::Close(_)) => {}
4007 other => panic!("expected Close, got {other:?}"),
4008 }
4009
4010 resume();
4011 }
4012
4013 #[tokio::test]
4014 async fn force_closes_after_timeout() {
4015 pause();
4016
4017 let conn = WebsocketConnection::new("c2");
4018 let (tx, mut rx) = unbounded_channel::<Message>();
4019 let (req_tx, _req_rx) = oneshot::channel();
4020 {
4021 let mut st = conn.state.lock().await;
4022 st.pending_requests.insert(
4023 "request_id".to_string(),
4024 PendingRequest { completion: req_tx },
4025 );
4026 }
4027 let common =
4028 WebsocketCommon::new(vec![conn.clone()], WebsocketMode::Single, 0, None, None);
4029 let close_fut = common.close_connection_gracefully(tx.clone(), conn.clone());
4030 advance(Duration::from_secs(30)).await;
4031 close_fut.await.unwrap();
4032 match rx.try_recv() {
4033 Ok(Message::Close(_)) => {}
4034 other => panic!("expected Close on timeout, got {other:?}"),
4035 }
4036
4037 resume();
4038 }
4039 }
4040
4041 mod get_reconnect_url {
4042 use super::*;
4043
4044 struct DummyHandler {
4045 url: String,
4046 }
4047
4048 #[async_trait::async_trait]
4049 impl WebsocketHandler for DummyHandler {
4050 async fn on_open(&self, _url: String, _connection: Arc<WebsocketConnection>) {}
4051 async fn on_message(&self, _data: String, _connection: Arc<WebsocketConnection>) {}
4052 async fn get_reconnect_url(
4053 &self,
4054 _default_url: String,
4055 _connection: Arc<WebsocketConnection>,
4056 ) -> String {
4057 self.url.clone()
4058 }
4059 }
4060
4061 #[test]
4062 fn returns_default_when_no_handler() {
4063 TOKIO_SHARED_RT.block_on(async {
4064 let conn = WebsocketConnection::new("c1");
4065 let common = WebsocketCommon::new(
4066 vec![conn.clone()],
4067 WebsocketMode::Single,
4068 0,
4069 None,
4070 None,
4071 );
4072 let default = "wss://default".to_string();
4073 let result = common.get_reconnect_url(&default, conn.clone()).await;
4074 assert_eq!(result, default);
4075 });
4076 }
4077
4078 #[test]
4079 fn returns_handler_url_when_set() {
4080 TOKIO_SHARED_RT.block_on(async {
4081 let conn = WebsocketConnection::new("c2");
4082 let handler = Arc::new(DummyHandler {
4083 url: "wss://custom".into(),
4084 });
4085 conn.set_handler(handler).await;
4086 let common = WebsocketCommon::new(
4087 vec![conn.clone()],
4088 WebsocketMode::Single,
4089 0,
4090 None,
4091 None,
4092 );
4093 let default = "wss://default".to_string();
4094 let result = common.get_reconnect_url(&default, conn.clone()).await;
4095 assert_eq!(result, "wss://custom");
4096 });
4097 }
4098 }
4099
4100 mod on_open {
4101 use super::*;
4102
4103 struct DummyHandler {
4104 called: Arc<Mutex<bool>>,
4105 opened_url: Arc<Mutex<Option<String>>>,
4106 }
4107
4108 #[async_trait]
4109 impl WebsocketHandler for DummyHandler {
4110 async fn on_open(&self, url: String, _connection: Arc<WebsocketConnection>) {
4111 let mut flag = self.called.lock().await;
4112 *flag = true;
4113 let mut store = self.opened_url.lock().await;
4114 *store = Some(url);
4115 }
4116 async fn on_message(&self, _data: String, _connection: Arc<WebsocketConnection>) {}
4117 async fn get_reconnect_url(
4118 &self,
4119 default_url: String,
4120 _connection: Arc<WebsocketConnection>,
4121 ) -> String {
4122 default_url
4123 }
4124 }
4125
4126 #[test]
4127 fn emits_open_and_calls_handler() {
4128 TOKIO_SHARED_RT.block_on(async {
4129 let conn = WebsocketConnection::new("c1");
4130 let called = Arc::new(Mutex::new(false));
4131 let opened_url = Arc::new(Mutex::new(None));
4132 let handler = Arc::new(DummyHandler {
4133 called: called.clone(),
4134 opened_url: opened_url.clone(),
4135 });
4136
4137 conn.set_handler(handler.clone()).await;
4138 let common = WebsocketCommon::new(
4139 vec![conn.clone()],
4140 WebsocketMode::Single,
4141 0,
4142 None,
4143 None,
4144 );
4145 let events = subscribe_events(&common);
4146 common
4147 .on_open("wss://example.com".into(), conn.clone(), None)
4148 .await;
4149
4150 sleep(std::time::Duration::from_millis(10)).await;
4151
4152 let evs = events.lock().await;
4153 assert!(evs.iter().any(|e| matches!(e, WebsocketEvent::Open)));
4154 assert!(*called.lock().await);
4155 assert_eq!(
4156 opened_url.lock().await.as_deref(),
4157 Some("wss://example.com")
4158 );
4159 });
4160 }
4161
4162 #[test]
4163 fn handles_renewal_pending_and_closes_old_writer() {
4164 TOKIO_SHARED_RT.block_on(async {
4165 let conn = WebsocketConnection::new("c2");
4166 let (old_tx, mut old_rx) = unbounded_channel::<Message>();
4167 {
4168 let mut st = conn.state.lock().await;
4169 st.renewal_pending = true;
4170 }
4171 let common = WebsocketCommon::new(
4172 vec![conn.clone()],
4173 WebsocketMode::Single,
4174 0,
4175 None,
4176 None,
4177 );
4178 common
4179 .on_open("url".into(), conn.clone(), Some(old_tx.clone()))
4180 .await;
4181 assert!(!conn.state.lock().await.renewal_pending);
4182 match old_rx.try_recv() {
4183 Ok(Message::Close(_)) => {}
4184 other => panic!("expected Close, got {other:?}"),
4185 }
4186 });
4187 }
4188 }
4189
4190 mod on_message {
4191 use super::*;
4192
4193 struct DummyHandler {
4194 called_with: Arc<Mutex<Vec<String>>>,
4195 }
4196
4197 #[async_trait]
4198 impl WebsocketHandler for DummyHandler {
4199 async fn on_open(&self, _url: String, _connection: Arc<WebsocketConnection>) {}
4200 async fn on_message(&self, data: String, _connection: Arc<WebsocketConnection>) {
4201 self.called_with.lock().await.push(data);
4202 }
4203 async fn get_reconnect_url(
4204 &self,
4205 default_url: String,
4206 _connection: Arc<WebsocketConnection>,
4207 ) -> String {
4208 default_url
4209 }
4210 }
4211
4212 #[test]
4213 fn emits_message_event_without_handler() {
4214 TOKIO_SHARED_RT.block_on(async {
4215 let conn = WebsocketConnection::new("c1");
4216 let common = WebsocketCommon::new(
4217 vec![conn.clone()],
4218 WebsocketMode::Single,
4219 0,
4220 None,
4221 None,
4222 );
4223 let events = subscribe_events(&common);
4224 common.on_message("msg".into(), conn.clone()).await;
4225
4226 sleep(Duration::from_millis(10)).await;
4227
4228 let locked = events.lock().await;
4229 assert!(
4230 locked
4231 .iter()
4232 .any(|e| matches!(e, WebsocketEvent::Message(m) if m == "msg"))
4233 );
4234 });
4235 }
4236
4237 #[test]
4238 fn calls_handler_and_emits_message() {
4239 TOKIO_SHARED_RT.block_on(async {
4240 let conn = WebsocketConnection::new("c2");
4241 let called = Arc::new(Mutex::new(Vec::new()));
4242 let handler = Arc::new(DummyHandler {
4243 called_with: called.clone(),
4244 });
4245 conn.set_handler(handler.clone()).await;
4246
4247 let common = WebsocketCommon::new(
4248 vec![conn.clone()],
4249 WebsocketMode::Single,
4250 0,
4251 None,
4252 None,
4253 );
4254 let events = subscribe_events(&common);
4255 common.on_message("msg".into(), conn.clone()).await;
4256
4257 sleep(Duration::from_millis(10)).await;
4258
4259 let evs = events.lock().await;
4260 assert!(
4261 evs.iter()
4262 .any(|e| matches!(e, WebsocketEvent::Message(m) if m == "msg"))
4263 );
4264 let msgs = called.lock().await;
4265 assert_eq!(msgs.as_slice(), &["msg".to_string()]);
4266 });
4267 }
4268
4269 #[test]
4270 fn preserves_message_order() {
4271 TOKIO_SHARED_RT.block_on(async {
4272 let conn = WebsocketConnection::new("c3");
4273 let called = Arc::new(Mutex::new(Vec::new()));
4274 let handler = Arc::new(DummyHandler {
4275 called_with: called.clone(),
4276 });
4277 conn.set_handler(handler.clone()).await;
4278
4279 let common = WebsocketCommon::new(
4280 vec![conn.clone()],
4281 WebsocketMode::Single,
4282 0,
4283 None,
4284 None,
4285 );
4286
4287 for i in 0..20 {
4288 common.on_message(format!("msg_{i}"), conn.clone()).await;
4289 }
4290
4291 let msgs = called.lock().await;
4292 let expected: Vec<String> = (0..20).map(|i| format!("msg_{i}")).collect();
4293 assert_eq!(msgs.as_slice(), expected.as_slice());
4294 });
4295 }
4296
4297 #[test]
4298 fn preserves_order_with_slow_handler() {
4299 use std::sync::atomic::{AtomicU32, Ordering};
4300
4301 struct SlowHandler {
4302 received: Arc<Mutex<Vec<String>>>,
4303 concurrent_count: Arc<AtomicU32>,
4304 max_concurrent: Arc<AtomicU32>,
4305 }
4306
4307 #[async_trait]
4308 impl WebsocketHandler for SlowHandler {
4309 async fn on_open(&self, _url: String, _connection: Arc<WebsocketConnection>) {}
4310 async fn on_message(
4311 &self,
4312 data: String,
4313 _connection: Arc<WebsocketConnection>,
4314 ) {
4315 let prev = self.concurrent_count.fetch_add(1, Ordering::SeqCst);
4316 self.max_concurrent.fetch_max(prev + 1, Ordering::SeqCst);
4317 sleep(Duration::from_millis(1)).await;
4318 self.received.lock().await.push(data);
4319 self.concurrent_count.fetch_sub(1, Ordering::SeqCst);
4320 }
4321 async fn get_reconnect_url(
4322 &self,
4323 default_url: String,
4324 _connection: Arc<WebsocketConnection>,
4325 ) -> String {
4326 default_url
4327 }
4328 }
4329
4330 TOKIO_SHARED_RT.block_on(async {
4331 let conn = WebsocketConnection::new("c4");
4332 let received = Arc::new(Mutex::new(Vec::new()));
4333 let concurrent_count = Arc::new(AtomicU32::new(0));
4334 let max_concurrent = Arc::new(AtomicU32::new(0));
4335 let handler = Arc::new(SlowHandler {
4336 received: received.clone(),
4337 concurrent_count: concurrent_count.clone(),
4338 max_concurrent: max_concurrent.clone(),
4339 });
4340 conn.set_handler(handler.clone()).await;
4341
4342 let common = WebsocketCommon::new(
4343 vec![conn.clone()],
4344 WebsocketMode::Single,
4345 0,
4346 None,
4347 None,
4348 );
4349
4350 for i in 0..10 {
4351 common.on_message(format!("msg_{i}"), conn.clone()).await;
4352 }
4353
4354 let msgs = received.lock().await;
4355 let expected: Vec<String> = (0..10).map(|i| format!("msg_{i}")).collect();
4356 assert_eq!(msgs.as_slice(), expected.as_slice());
4357 assert_eq!(
4358 max_concurrent.load(Ordering::SeqCst),
4359 1,
4360 "messages must be processed sequentially, not concurrently"
4361 );
4362 });
4363 }
4364 }
4365
4366 mod create_websocket {
4367 use super::*;
4368
4369 #[test]
4370 fn configuration_builders_accept_custom_handshakes() {
4371 let handshake: WebsocketHandshakeFn = Arc::new(|_, _, _, _| {
4372 Box::pin(async { Err(tungstenite::Error::ConnectionClosed) })
4373 });
4374
4375 let api = ConfigurationWebsocketApi::builder()
4376 .handshake(handshake.clone())
4377 .build()
4378 .unwrap();
4379 let streams = ConfigurationWebsocketStreams::builder()
4380 .handshake(handshake)
4381 .build()
4382 .unwrap();
4383
4384 assert!(api.handshake.is_some());
4385 assert!(streams.handshake.is_some());
4386 assert!(format!("{streams:?}").contains("<custom handshake fn>"));
4387 }
4388
4389 #[test]
4390 fn custom_handshake_routes_connection_through_custom_transport() {
4391 use tokio::io::{AsyncReadExt, AsyncWriteExt};
4392
4393 TOKIO_SHARED_RT.block_on(async {
4394 let origin_listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
4396 let origin_addr = origin_listener.local_addr().unwrap();
4397 let _origin_guard = AbortOnDrop(tokio::spawn(async move {
4398 if let Ok((stream, _)) = origin_listener.accept().await {
4399 let Ok(mut ws) = accept_async(stream).await else {
4400 return;
4401 };
4402 while let Some(Ok(msg)) = ws.next().await {
4405 if let Message::Text(text) = msg {
4406 let _ =
4407 ws.send(Message::Text(format!("echo:{text}").into())).await;
4408 }
4409 }
4410 }
4411 }));
4412
4413 let relay_listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
4419 let relay_addr = relay_listener.local_addr().unwrap();
4420 let relay_bytes = Arc::new(AtomicUsize::new(0));
4421 let relay_bytes_srv = Arc::clone(&relay_bytes);
4422 let _relay_guard = AbortOnDrop(tokio::spawn(async move {
4423 if let Ok((mut inbound, _)) = relay_listener.accept().await {
4424 let Ok(mut outbound) =
4425 tokio::net::TcpStream::connect(origin_addr).await
4426 else {
4427 return;
4428 };
4429 let (mut ri, mut wi) = inbound.split();
4430 let (mut ro, mut wo) = outbound.split();
4431 let up = async {
4434 let mut buf = [0u8; 4096];
4435 loop {
4436 match ri.read(&mut buf).await {
4437 Ok(0) | Err(_) => break,
4438 Ok(n) => {
4439 relay_bytes_srv.fetch_add(n, Ordering::SeqCst);
4440 if wo.write_all(&buf[..n]).await.is_err() {
4441 break;
4442 }
4443 }
4444 }
4445 }
4446 };
4447 let down = tokio::io::copy(&mut ro, &mut wi);
4448 let _ = tokio::join!(up, down);
4449 }
4450 }));
4451
4452 let handshake_calls = Arc::new(AtomicUsize::new(0));
4457 let handshake_calls_clone = Arc::clone(&handshake_calls);
4458 let handshake: WebsocketHandshakeFn =
4459 Arc::new(move |mut request, ws_config, disable_nagle, connector| {
4460 let handshake_calls = Arc::clone(&handshake_calls_clone);
4461 Box::pin(async move {
4462 handshake_calls.fetch_add(1, Ordering::SeqCst);
4463 *request.uri_mut() = format!("ws://{relay_addr}/").parse().unwrap();
4465 tokio_tungstenite::connect_async_tls_with_config(
4466 request,
4467 ws_config,
4468 disable_nagle,
4469 connector,
4470 )
4471 .await
4472 })
4473 });
4474
4475 let ws = WebsocketCommon::create_websocket(
4479 &format!("ws://{origin_addr}"),
4480 None,
4481 None,
4482 Some(handshake),
4483 )
4484 .await
4485 .expect("custom-handshake connection should succeed");
4486
4487 let (mut write, mut read) = ws.split();
4490 write
4491 .send(Message::Text("through-tunnel".into()))
4492 .await
4493 .unwrap();
4494 let reply = timeout(Duration::from_secs(5), read.next())
4495 .await
4496 .expect("timed out awaiting echo")
4497 .expect("stream ended")
4498 .expect("read error");
4499
4500 assert_eq!(
4501 reply,
4502 Message::Text("echo:through-tunnel".into()),
4503 "message did not round-trip through the origin"
4504 );
4505 assert_eq!(
4506 handshake_calls.load(Ordering::SeqCst),
4507 1,
4508 "custom handshake should have been invoked exactly once"
4509 );
4510 assert!(
4511 relay_bytes.load(Ordering::SeqCst) > 0,
4512 "no bytes flowed through the custom transport relay"
4513 );
4514 });
4515 }
4516
4517 #[test]
4518 fn custom_handshake_is_used_for_initial_connection() {
4519 TOKIO_SHARED_RT.block_on(async {
4520 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
4521 let addr = listener.local_addr().unwrap();
4522 let _listener_guard = spawn_mock_ws_listener(listener);
4523 let handshake_calls = Arc::new(AtomicUsize::new(0));
4524 let handshake_calls_clone = Arc::clone(&handshake_calls);
4525 let handshake: WebsocketHandshakeFn =
4526 Arc::new(move |request, config, disable_nagle, connector| {
4527 let handshake_calls = Arc::clone(&handshake_calls_clone);
4528 Box::pin(async move {
4529 handshake_calls.fetch_add(1, Ordering::SeqCst);
4530 assert!(config.is_none());
4531 assert!(!disable_nagle);
4532 assert!(connector.is_none());
4533 tokio_tungstenite::connect_async_tls_with_config(
4534 request,
4535 config,
4536 disable_nagle,
4537 connector,
4538 )
4539 .await
4540 })
4541 });
4542
4543 let result = WebsocketCommon::create_websocket(
4544 &format!("ws://{addr}"),
4545 None,
4546 None,
4547 Some(handshake),
4548 )
4549 .await;
4550
4551 assert!(result.is_ok(), "custom handshake failed: {result:?}");
4552 assert_eq!(handshake_calls.load(Ordering::SeqCst), 1);
4553 });
4554 }
4555
4556 #[test]
4557 fn custom_handshake_error_maps_to_handshake_error() {
4558 TOKIO_SHARED_RT.block_on(async {
4559 let handshake_calls = Arc::new(AtomicUsize::new(0));
4560 let handshake_calls_clone = Arc::clone(&handshake_calls);
4561 let handshake: WebsocketHandshakeFn = Arc::new(move |_, _, _, _| {
4562 let handshake_calls = Arc::clone(&handshake_calls_clone);
4563 Box::pin(async move {
4564 handshake_calls.fetch_add(1, Ordering::SeqCst);
4565 Err(tungstenite::Error::ConnectionClosed)
4566 })
4567 });
4568
4569 let result = WebsocketCommon::create_websocket(
4570 "ws://127.0.0.1:1",
4571 None,
4572 None,
4573 Some(handshake),
4574 )
4575 .await;
4576
4577 assert!(matches!(result, Err(WebsocketError::Handshake(_))));
4578 assert_eq!(handshake_calls.load(Ordering::SeqCst), 1);
4579 });
4580 }
4581
4582 #[test]
4583 fn successful_connection() {
4584 TOKIO_SHARED_RT.block_on(async {
4585 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
4586 let addr: SocketAddr = listener.local_addr().unwrap();
4587
4588 let expected_ua = build_user_agent("product");
4589 let expected_ua_clone = expected_ua.clone();
4590
4591 tokio::spawn(async move {
4592 if let Ok((stream, _)) = listener.accept().await {
4593 let callback = |req: &Request, resp| {
4594 let got = req
4595 .headers()
4596 .get(USER_AGENT)
4597 .expect("no USER_AGENT header in WS handshake")
4598 .to_str()
4599 .expect("invalid USER_AGENT header");
4600 assert_eq!(got, expected_ua_clone, "User-Agent mismatch");
4601 Ok(resp)
4602 };
4603 let _ = accept_hdr_async(stream, callback).await.unwrap();
4604 }
4605 });
4606
4607 let url = format!("ws://{addr}");
4608 let res =
4609 WebsocketCommon::create_websocket(&url, None, Some(expected_ua), None)
4610 .await;
4611 assert!(res.is_ok(), "handshake failed: {res:?}");
4612 });
4613 }
4614
4615 #[test]
4616 fn invalid_url_returns_handshake_error() {
4617 TOKIO_SHARED_RT.block_on(async {
4618 let res =
4619 WebsocketCommon::create_websocket("not-a-valid-url", None, None, None)
4620 .await;
4621 assert!(matches!(res, Err(WebsocketError::Handshake(_))));
4622 });
4623 }
4624
4625 #[test]
4626 fn unreachable_host_returns_handshake_error() {
4627 TOKIO_SHARED_RT.block_on(async {
4628 let res =
4629 WebsocketCommon::create_websocket("ws://127.0.0.1:1", None, None, None)
4630 .await;
4631 assert!(matches!(res, Err(WebsocketError::Handshake(_))));
4632 });
4633 }
4634 }
4635
4636 mod connect_pool {
4637 use super::*;
4638
4639 #[test]
4640 fn connects_all_in_pool() {
4641 TOKIO_SHARED_RT.block_on(async {
4642 let pool_size = 3;
4643 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
4644 let addr = listener.local_addr().unwrap();
4645 tokio::spawn(async move {
4646 for _ in 0..pool_size {
4647 if let Ok((stream, _)) = listener.accept().await {
4648 tokio::spawn(async move {
4649 let mut ws = accept_async(stream).await.unwrap();
4650 sleep(Duration::from_millis(500)).await;
4651 let _ = ws.close(None).await;
4652 });
4653 }
4654 }
4655 });
4656 let conns: Vec<Arc<WebsocketConnection>> = (0..pool_size)
4657 .map(|i| WebsocketConnection::new(format!("c{i}")))
4658 .collect();
4659 let common = WebsocketCommon::new(
4660 conns.clone(),
4661 WebsocketMode::Pool(pool_size),
4662 0,
4663 None,
4664 None,
4665 );
4666 let url = format!("ws://{addr}");
4667 common.clone().connect_pool(&url, None).await.unwrap();
4668 for conn in conns {
4669 let mut ok = false;
4670 for _ in 0..100 {
4671 if conn.state.lock().await.ws_write_tx.is_some() {
4672 ok = true;
4673 break;
4674 }
4675 sleep(Duration::from_millis(50)).await;
4676 }
4677 assert!(ok, "expected ws_write_tx Some after connect");
4678 }
4679 });
4680 }
4681
4682 #[test]
4683 fn fails_if_any_refused() {
4684 TOKIO_SHARED_RT.block_on(async {
4685 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
4686 let addr = listener.local_addr().unwrap();
4687 let pool_size = 3;
4688 tokio::spawn(async move {
4689 for _ in 0..2 {
4690 if let Ok((stream, _)) = listener.accept().await {
4691 let mut ws = accept_async(stream).await.unwrap();
4692 let _ = ws.close(None).await;
4693 }
4694 }
4695 });
4696 let mut conns = Vec::new();
4697 let valid_url = format!("ws://{addr}");
4698 for i in 0..2 {
4699 conns.push(WebsocketConnection::new(format!("c{i}")));
4700 }
4701 conns.push(WebsocketConnection::new("bad"));
4702 let common = WebsocketCommon::new(
4703 conns.clone(),
4704 WebsocketMode::Pool(pool_size),
4705 0,
4706 None,
4707 None,
4708 );
4709 let res = common.clone().connect_pool(&valid_url, None).await;
4710 assert!(matches!(res, Err(WebsocketError::Handshake(_))));
4711 });
4712 }
4713
4714 #[test]
4715 fn fails_on_invalid_url() {
4716 TOKIO_SHARED_RT.block_on(async {
4717 let conns = vec![WebsocketConnection::new("c1")];
4718 let common = WebsocketCommon::new(conns, WebsocketMode::Pool(1), 0, None, None);
4719 let res = common.connect_pool("not-a-url", None).await;
4720 assert!(matches!(res, Err(WebsocketError::Handshake(_))));
4721 });
4722 }
4723
4724 #[test]
4725 fn fails_if_mixed_success_and_invalid_url() {
4726 TOKIO_SHARED_RT.block_on(async {
4727 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
4728 let addr = listener.local_addr().unwrap();
4729 tokio::spawn(async move {
4730 if let Ok((stream, _)) = listener.accept().await {
4731 let mut ws = accept_async(stream).await.unwrap();
4732 let _ = ws.close(None).await;
4733 }
4734 });
4735 let good = WebsocketConnection::new("good");
4736 let bad = WebsocketConnection::new("bad");
4737 let common = WebsocketCommon::new(
4738 vec![good, bad],
4739 WebsocketMode::Pool(2),
4740 0,
4741 None,
4742 None,
4743 );
4744 let url = format!("ws://{addr}");
4745 let res = common.connect_pool(&url, None).await;
4746 assert!(matches!(res, Err(WebsocketError::Handshake(_))));
4747 });
4748 }
4749
4750 #[test]
4751 fn init_connect_invoked_for_each() {
4752 TOKIO_SHARED_RT.block_on(async {
4753 let pool_size = 2;
4754 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
4755 let addr = listener.local_addr().unwrap();
4756 tokio::spawn(async move {
4757 for _ in 0..pool_size {
4758 if let Ok((stream, _)) = listener.accept().await {
4759 tokio::spawn(async move {
4760 let mut ws = accept_async(stream).await.unwrap();
4761 sleep(Duration::from_millis(500)).await;
4762 let _ = ws.close(None).await;
4763 });
4764 }
4765 }
4766 });
4767 let conns: Vec<Arc<WebsocketConnection>> = (0..pool_size)
4768 .map(|i| WebsocketConnection::new(format!("c{i}")))
4769 .collect();
4770 let common = WebsocketCommon::new(
4771 conns.clone(),
4772 WebsocketMode::Pool(pool_size),
4773 0,
4774 None,
4775 None,
4776 );
4777 let url = format!("ws://{addr}");
4778 common.clone().connect_pool(&url, None).await.unwrap();
4779 for conn in conns {
4780 let mut ok = false;
4781 for _ in 0..100 {
4782 if conn.state.lock().await.ws_write_tx.is_some() {
4783 ok = true;
4784 break;
4785 }
4786 sleep(Duration::from_millis(25)).await;
4787 }
4788 assert!(ok, "expected ws_write_tx Some for {}", conn.id);
4789 }
4790 });
4791 }
4792
4793 #[test]
4794 fn single_mode_uses_first_connection() {
4795 TOKIO_SHARED_RT.block_on(async {
4796 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
4797 let addr = listener.local_addr().unwrap();
4798 let _listener_guard = spawn_mock_ws_listener(listener);
4799 let conn = WebsocketConnection::new("c1");
4800 let common = WebsocketCommon::new(
4801 vec![conn.clone()],
4802 WebsocketMode::Single,
4803 0,
4804 None,
4805 None,
4806 );
4807 let url = format!("ws://{addr}");
4808 common.connect_pool(&url, None).await.unwrap();
4809 let ok = eventually_async(Duration::from_secs(5), || {
4810 let conn = conn.clone();
4811 async move { conn.state.lock().await.ws_write_tx.is_some() }
4812 })
4813 .await;
4814
4815 assert!(ok, "single mode did not select first connection");
4816 });
4817 }
4818
4819 #[test]
4820 fn empty_subset_is_ok_and_connects_none() {
4821 TOKIO_SHARED_RT.block_on(async {
4822 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
4823 let addr = listener.local_addr().unwrap();
4824
4825 tokio::spawn(async move {
4826 let _ = addr;
4827 });
4828
4829 let c1 = WebsocketConnection::new("c1");
4830 let c2 = WebsocketConnection::new("c2");
4831
4832 let common = WebsocketCommon::new(
4833 vec![c1.clone(), c2.clone()],
4834 WebsocketMode::Pool(2),
4835 0,
4836 None,
4837 None,
4838 );
4839
4840 let url = format!("ws://{addr}");
4841 common
4842 .clone()
4843 .connect_pool(&url, Some(vec![]))
4844 .await
4845 .unwrap();
4846
4847 assert!(c1.state.lock().await.ws_write_tx.is_none());
4848 assert!(c2.state.lock().await.ws_write_tx.is_none());
4849 });
4850 }
4851 }
4852
4853 mod init_connect {
4854 use super::*;
4855 use std::panic;
4856 use tokio::sync::mpsc::{channel, error::TryRecvError};
4857
4858 #[test]
4859 fn pool_mode_none_connection_uses_first() {
4860 TOKIO_SHARED_RT.block_on(async {
4861 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
4862 let addr = listener.local_addr().unwrap();
4863 tokio::spawn(async move {
4864 for _ in 0..2 {
4865 if let Ok((stream, _)) = listener.accept().await {
4866 let mut ws = accept_async(stream).await.unwrap();
4867 ws.close(None).await.ok();
4868 }
4869 }
4870 });
4871
4872 let c1 = WebsocketConnection::new("c1");
4873 let c2 = WebsocketConnection::new("c2");
4874 let common = WebsocketCommon::new(
4875 vec![c1.clone(), c2.clone()],
4876 WebsocketMode::Pool(2),
4877 0,
4878 None,
4879 None,
4880 );
4881 let url = format!("ws://{addr}");
4882
4883 common
4884 .clone()
4885 .init_connect(&url, false, None)
4886 .await
4887 .unwrap();
4888
4889 let ok = eventually_async(Duration::from_secs(5), || {
4890 let conn1 = c1.clone();
4891 async move { conn1.state.lock().await.ws_write_tx.is_some() }
4892 })
4893 .await;
4894
4895 assert!(ok, "first connection was never selected");
4896 assert!(c2.state.lock().await.ws_write_tx.is_none());
4897 });
4898 }
4899
4900 #[test]
4901 fn writer_channel_can_send_text() {
4902 TOKIO_SHARED_RT.block_on(async {
4903 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
4904 let addr = listener.local_addr().unwrap();
4905 let received = Arc::new(Mutex::new(None::<String>));
4906 let received_clone = received.clone();
4907
4908 tokio::spawn(async move {
4909 if let Ok((stream, _)) = listener.accept().await {
4910 let mut ws = accept_async(stream).await.unwrap();
4911 if let Some(Ok(Message::Text(txt))) = ws.next().await {
4912 *received_clone.lock().await = Some(txt.to_string());
4913 }
4914 ws.close(None).await.ok();
4915 }
4916 });
4917
4918 let conn = WebsocketConnection::new("cw");
4919 let common = WebsocketCommon::new(
4920 vec![conn.clone()],
4921 WebsocketMode::Single,
4922 0,
4923 None,
4924 None,
4925 );
4926 let url = format!("ws://{addr}");
4927 common
4928 .clone()
4929 .init_connect(&url, false, Some(conn.clone()))
4930 .await
4931 .unwrap();
4932
4933 let tx = conn.state.lock().await.ws_write_tx.clone().unwrap();
4934 tx.send(Message::Text("ping".into())).unwrap();
4935
4936 sleep(Duration::from_millis(50)).await;
4937
4938 let lock = received.lock().await;
4939 assert_eq!(lock.as_deref(), Some("ping"));
4940 });
4941 }
4942
4943 #[test]
4944 fn does_not_skip_when_reconnection_pending_even_if_writer_exists() {
4945 TOKIO_SHARED_RT.block_on(async {
4946 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
4947 let addr = listener.local_addr().unwrap();
4948 let _listener_guard = spawn_mock_ws_listener(listener);
4949
4950 let conn = WebsocketConnection::new("c-reconnect");
4951 {
4952 let mut st = conn.state.lock().await;
4953 let (tx, _) = unbounded_channel::<Message>();
4954 st.ws_write_tx = Some(tx);
4955 st.reconnection_pending = true;
4956 }
4957
4958 let common = WebsocketCommon::new(
4959 vec![conn.clone()],
4960 WebsocketMode::Single,
4961 0,
4962 None,
4963 None,
4964 );
4965
4966 let url = format!("ws://{addr}");
4967 common
4968 .clone()
4969 .init_connect(&url, false, Some(conn.clone()))
4970 .await
4971 .unwrap();
4972
4973 let st = conn.state.lock().await;
4974 assert!(
4975 st.ws_write_tx.is_some(),
4976 "writer should be set after connect"
4977 );
4978 assert!(
4979 !st.reconnection_pending,
4980 "reconnection_pending should be cleared after successful connect"
4981 );
4982 });
4983 }
4984
4985 #[test]
4986 fn responds_to_ping_with_pong() {
4987 TOKIO_SHARED_RT.block_on(async {
4988 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
4989 let addr = listener.local_addr().unwrap();
4990
4991 let saw_pong = Arc::new(Mutex::new(false));
4992 let saw_pong2 = saw_pong.clone();
4993
4994 tokio::spawn(async move {
4995 if let Ok((stream, _)) = listener.accept().await {
4996 let mut ws = accept_async(stream).await.unwrap();
4997 ws.send(Message::Ping(vec![1, 2, 3].into())).await.unwrap();
4998 if let Some(Ok(Message::Pong(payload))) = ws.next().await {
4999 if payload[..] == [1, 2, 3] {
5000 *saw_pong2.lock().await = true;
5001 }
5002 }
5003 let _ = ws.close(None).await;
5004 }
5005 });
5006
5007 let conn = WebsocketConnection::new("c-ping");
5008 let common = WebsocketCommon::new(
5009 vec![conn.clone()],
5010 WebsocketMode::Single,
5011 0,
5012 None,
5013 None,
5014 );
5015 let url = format!("ws://{addr}");
5016 common
5017 .clone()
5018 .init_connect(&url, false, Some(conn))
5019 .await
5020 .unwrap();
5021
5022 sleep(Duration::from_millis(50)).await;
5023
5024 assert!(*saw_pong.lock().await, "server should have seen a Pong");
5025 });
5026 }
5027
5028 #[test]
5029 fn handshake_error_on_invalid_url() {
5030 TOKIO_SHARED_RT.block_on(async {
5031 let conn = WebsocketConnection::new("c-invalid");
5032 let common = WebsocketCommon::new(
5033 vec![conn.clone()],
5034 WebsocketMode::Single,
5035 0,
5036 None,
5037 None,
5038 );
5039 let res = common
5040 .clone()
5041 .init_connect("not-a-url", false, Some(conn.clone()))
5042 .await;
5043 assert!(matches!(res, Err(WebsocketError::Handshake(_))));
5044 });
5045 }
5046
5047 #[test]
5048 fn skip_if_writer_exists_and_not_renewal() {
5049 TOKIO_SHARED_RT.block_on(async {
5050 let conn = WebsocketConnection::new("c-writer");
5051 let (tx, mut rx) = unbounded_channel::<Message>();
5052 {
5053 let mut st = conn.state.lock().await;
5054 st.ws_write_tx = Some(tx.clone());
5055 }
5056 let common = WebsocketCommon::new(
5057 vec![conn.clone()],
5058 WebsocketMode::Single,
5059 0,
5060 None,
5061 None,
5062 );
5063 let res = common
5064 .clone()
5065 .init_connect("ws://127.0.0.1:1", false, Some(conn.clone()))
5066 .await;
5067
5068 assert!(res.is_ok());
5069 assert!(rx.try_recv().is_err());
5070 });
5071 }
5072
5073 #[test]
5074 fn short_circuit_on_already_renewing() {
5075 TOKIO_SHARED_RT.block_on(async {
5076 let conn = WebsocketConnection::new("c-renew");
5077 {
5078 let mut st = conn.state.lock().await;
5079 st.renewal_pending = true;
5080 }
5081 let common = WebsocketCommon::new(
5082 vec![conn.clone()],
5083 WebsocketMode::Single,
5084 0,
5085 None,
5086 None,
5087 );
5088 let res = common
5089 .clone()
5090 .init_connect("ws://127.0.0.1:1", true, Some(conn.clone()))
5091 .await;
5092
5093 assert!(res.is_ok());
5094 assert!(conn.state.lock().await.ws_write_tx.is_none());
5095 });
5096 }
5097
5098 #[test]
5099 fn is_renewal_true_sets_and_clears_flag() {
5100 struct GatedHandler {
5101 gate: Mutex<Option<oneshot::Receiver<()>>>,
5102 }
5103 #[async_trait]
5104 impl WebsocketHandler for GatedHandler {
5105 async fn on_open(&self, _url: String, _connection: Arc<WebsocketConnection>) {
5106 if let Some(rx) = self.gate.lock().await.take() {
5107 let _ = rx.await;
5108 }
5109 }
5110 async fn on_message(
5111 &self,
5112 _data: String,
5113 _connection: Arc<WebsocketConnection>,
5114 ) {
5115 }
5116 async fn get_reconnect_url(
5117 &self,
5118 url: String,
5119 _connection: Arc<WebsocketConnection>,
5120 ) -> String {
5121 url
5122 }
5123 }
5124
5125 TOKIO_SHARED_RT.block_on(async {
5126 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
5127 let addr = listener.local_addr().unwrap();
5128 let _listener_guard = spawn_mock_ws_listener(listener);
5129
5130 let conn = WebsocketConnection::new("c-new-renew");
5131 let (gate_tx, gate_rx) = oneshot::channel();
5132 conn.set_handler(Arc::new(GatedHandler {
5133 gate: Mutex::new(Some(gate_rx)),
5134 }))
5135 .await;
5136
5137 let common = WebsocketCommon::new(
5138 vec![conn.clone()],
5139 WebsocketMode::Single,
5140 0,
5141 None,
5142 None,
5143 );
5144 let url = format!("ws://{addr}");
5145 let res = common
5146 .clone()
5147 .init_connect(&url, true, Some(conn.clone()))
5148 .await;
5149
5150 assert!(res.is_ok());
5151
5152 {
5153 let st = conn.state.lock().await;
5154 assert!(st.ws_write_tx.is_some(), "writer should be set");
5155 assert!(
5156 st.renewal_pending,
5157 "renewal_pending must be true until on_open"
5158 );
5159 }
5160
5161 let _ = gate_tx.send(());
5162
5163 let ok = eventually_async(Duration::from_secs(2), || {
5164 let conn = conn.clone();
5165 async move { !conn.state.lock().await.renewal_pending }
5166 })
5167 .await;
5168 assert!(ok, "renewal_pending should be cleared in on_open");
5169 let st = conn.state.lock().await;
5170 assert!(
5171 !st.renewal_pending,
5172 "renewal_pending should be cleared in on_open"
5173 );
5174 });
5175 }
5176
5177 #[test]
5178 fn does_not_schedule_reconnect_on_renewal() {
5179 TOKIO_SHARED_RT.block_on(async {
5180 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
5181 let addr = listener.local_addr().unwrap();
5182 let server_handle = tokio::spawn(async move {
5183 if let Ok((stream, _)) = listener.accept().await {
5184 let _ws = accept_async(stream).await.unwrap();
5185 if let Ok((stream, _)) = listener.accept().await {
5186 let _ws = accept_async(stream).await.unwrap();
5187 }
5188 sleep(Duration::from_secs(10)).await;
5189 }
5190 });
5191
5192 let conn = WebsocketConnection::new("c-needs-renew");
5193 let (reconnect_tx, mut reconnect_rx) = channel::<ReconnectEntry>(1);
5194 let (renewal_tx, _renewal_rx) = channel::<(String, String)>(1);
5195 let common = Arc::new(WebsocketCommon {
5196 events: WebsocketEventEmitter::new(),
5197 mode: WebsocketMode::Single,
5198 round_robin_index: AtomicUsize::new(0),
5199 connection_pool: vec![conn.clone()],
5200 reconnect_tx,
5201 renewal_tx,
5202 reconnect_delay: 0,
5203 agent: None,
5204 handshake: None,
5205 user_agent: None,
5206 });
5207 let url = format!("ws://{addr}");
5208 let res = common
5209 .clone()
5210 .init_connect(&url, false, Some(conn.clone()))
5211 .await;
5212
5213 assert!(res.is_ok());
5214
5215 {
5216 let st = conn.state.lock().await;
5217 assert!(st.ws_write_tx.is_some(), "writer should be set");
5218 }
5219
5220 common
5221 .clone()
5222 .init_connect(&url, true, Some(conn.clone()))
5223 .await
5224 .expect("Renewal init_connect should succeed");
5225
5226 sleep(Duration::from_millis(1000)).await;
5227
5228 match reconnect_rx.try_recv() {
5229 Err(TryRecvError::Empty) => {}
5230 Ok(_) => panic!("Received reconnection request on renewal"),
5231 Err(TryRecvError::Disconnected) => {
5232 panic!("Sender for reconnection_rx disconnected")
5233 }
5234 }
5235 server_handle.abort();
5236 });
5237 }
5238
5239 #[test]
5240 fn default_connection_selected_when_none_passed() {
5241 TOKIO_SHARED_RT.block_on(async {
5242 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
5243 let addr = listener.local_addr().unwrap();
5244 let _listener_guard = spawn_mock_ws_listener(listener);
5245 let conn = WebsocketConnection::new("c-default");
5246 let common = WebsocketCommon::new(
5247 vec![conn.clone()],
5248 WebsocketMode::Single,
5249 0,
5250 None,
5251 None,
5252 );
5253 let url = format!("ws://{addr}");
5254 let res = common.clone().init_connect(&url, false, None).await;
5255
5256 assert!(res.is_ok());
5257 let ok = eventually_async(Duration::from_secs(5), || {
5258 let conn = conn.clone();
5259 async move { conn.state.lock().await.ws_write_tx.is_some() }
5260 })
5261 .await;
5262
5263 assert!(ok, "default connection was never selected");
5264 });
5265 }
5266
5267 #[test]
5268 fn schedules_reconnect_on_abnormal_close() {
5269 TOKIO_SHARED_RT.block_on(async {
5270 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
5271 let addr = listener.local_addr().unwrap();
5272 tokio::spawn(async move {
5273 if let Ok((stream, _)) = listener.accept().await {
5274 let ws = accept_async(stream).await.unwrap();
5275
5276 drop(ws);
5282 }
5283 });
5284 let conn = WebsocketConnection::new("c-close");
5285 let common = WebsocketCommon::new(
5286 vec![conn.clone()],
5287 WebsocketMode::Single,
5288 5_000,
5289 None,
5290 None,
5291 );
5292 let url = format!("ws://{addr}");
5293 common
5294 .clone()
5295 .init_connect(&url, false, Some(conn.clone()))
5296 .await
5297 .unwrap();
5298
5299 sleep(Duration::from_millis(50)).await;
5300
5301 let st = conn.state.lock().await;
5302 assert!(
5303 st.reconnection_pending,
5304 "expected reconnection_pending to be true after abnormal close"
5305 );
5306 assert!(
5307 st.ws_write_tx.is_none(),
5308 "ws_write_tx should be cleared when scheduling a reconnect"
5309 );
5310 });
5311 }
5312 }
5313
5314 mod disconnect {
5315 use super::*;
5316
5317 #[test]
5318 fn returns_ok_when_no_connections_are_ready() {
5319 TOKIO_SHARED_RT.block_on(async {
5320 let conn = WebsocketConnection::new("c1");
5321 let common = WebsocketCommon::new(
5322 vec![conn.clone()],
5323 WebsocketMode::Single,
5324 0,
5325 None,
5326 None,
5327 );
5328 let res = common.disconnect().await;
5329
5330 assert!(res.is_ok());
5331 assert!(!conn.state.lock().await.close_initiated);
5332 });
5333 }
5334
5335 #[test]
5336 fn closes_all_ready_connections() {
5337 TOKIO_SHARED_RT.block_on(async {
5338 let conn1 = WebsocketConnection::new("c1");
5339 let conn2 = WebsocketConnection::new("c2");
5340 let (tx1, mut rx1) = unbounded_channel::<Message>();
5341 let (tx2, mut rx2) = unbounded_channel::<Message>();
5342 {
5343 let mut s1 = conn1.state.lock().await;
5344 s1.ws_write_tx = Some(tx1);
5345 }
5346 {
5347 let mut s2 = conn2.state.lock().await;
5348 s2.ws_write_tx = Some(tx2);
5349 }
5350 let common = WebsocketCommon::new(
5351 vec![conn1.clone(), conn2.clone()],
5352 WebsocketMode::Pool(2),
5353 0,
5354 None,
5355 None,
5356 );
5357 let fut = common.disconnect();
5358
5359 sleep(Duration::from_millis(50)).await;
5360
5361 fut.await.unwrap();
5362
5363 assert!(conn1.state.lock().await.close_initiated);
5364 assert!(conn2.state.lock().await.close_initiated);
5365
5366 {
5367 let st = conn1.state.lock().await;
5368 assert!(!st.is_session_logged_on, "conn1 should be logged out");
5369 assert!(st.session_logon_req.is_none(), "conn1 req cleared");
5370 }
5371 {
5372 let st = conn2.state.lock().await;
5373 assert!(!st.is_session_logged_on, "conn2 should be logged out");
5374 assert!(st.session_logon_req.is_none(), "conn2 req cleared");
5375 }
5376
5377 match (rx1.try_recv(), rx2.try_recv()) {
5378 (Ok(Message::Close(_)), Ok(Message::Close(_))) => {}
5379 other => panic!("expected two Close frames, got {other:?}"),
5380 }
5381 });
5382 }
5383
5384 #[test]
5385 fn does_not_mark_close_initiated_if_no_writer() {
5386 TOKIO_SHARED_RT.block_on(async {
5387 let conn = WebsocketConnection::new("c-new");
5388 let common = WebsocketCommon::new(
5389 vec![conn.clone()],
5390 WebsocketMode::Single,
5391 0,
5392 None,
5393 None,
5394 );
5395 common.disconnect().await.unwrap();
5396
5397 assert!(!conn.state.lock().await.close_initiated);
5398 });
5399 }
5400
5401 #[test]
5402 fn mixed_pool_marks_all_and_closes_only_writers() {
5403 TOKIO_SHARED_RT.block_on(async {
5404 let conn_w = WebsocketConnection::new("with");
5405 let conn_wo = WebsocketConnection::new("without");
5406 let (tx, mut rx) = unbounded_channel::<Message>();
5407 {
5408 let mut st = conn_w.state.lock().await;
5409 st.ws_write_tx = Some(tx);
5410 }
5411 let common = WebsocketCommon::new(
5412 vec![conn_w.clone(), conn_wo.clone()],
5413 WebsocketMode::Pool(2),
5414 0,
5415 None,
5416 None,
5417 );
5418 let fut = common.disconnect();
5419
5420 sleep(Duration::from_millis(50)).await;
5421
5422 fut.await.unwrap();
5423
5424 assert!(conn_w.state.lock().await.close_initiated);
5425 assert!(conn_wo.state.lock().await.close_initiated);
5426 assert!(matches!(rx.try_recv(), Ok(Message::Close(_))));
5427 });
5428 }
5429
5430 #[test]
5431 fn after_disconnect_not_connected() {
5432 TOKIO_SHARED_RT.block_on(async {
5433 let conn = WebsocketConnection::new("c1");
5434 let (tx, mut _rx) = unbounded_channel::<Message>();
5435 {
5436 let mut st = conn.state.lock().await;
5437 st.ws_write_tx = Some(tx);
5438 }
5439 let common = WebsocketCommon::new(
5440 vec![conn.clone()],
5441 WebsocketMode::Single,
5442 0,
5443 None,
5444 None,
5445 );
5446 common.disconnect().await.unwrap();
5447 assert!(!common.is_connected(Some(&conn)).await);
5448 });
5449 }
5450 }
5451
5452 mod ping_server {
5453 use super::*;
5454
5455 #[test]
5456 fn sends_ping_to_all_ready_connections() {
5457 TOKIO_SHARED_RT.block_on(async {
5458 let mut conns = Vec::new();
5459 for i in 0..3 {
5460 let conn = WebsocketConnection::new(format!("c{i}"));
5461 let (tx, rx) = unbounded_channel::<Message>();
5462 {
5463 let mut st = conn.state.lock().await;
5464 st.ws_write_tx = Some(tx);
5465 }
5466 conns.push((conn, rx));
5467 }
5468 let common = WebsocketCommon::new(
5469 conns.iter().map(|(c, _)| c.clone()).collect(),
5470 WebsocketMode::Pool(3),
5471 0,
5472 None,
5473 None,
5474 );
5475 common.ping_server().await;
5476 for (_, mut rx) in conns {
5477 match rx.try_recv() {
5478 Ok(Message::Ping(payload)) if payload.is_empty() => {}
5479 other => panic!("expected empty-payload Ping, got {other:?}"),
5480 }
5481 }
5482 });
5483 }
5484
5485 #[test]
5486 fn skips_not_ready_and_partial() {
5487 TOKIO_SHARED_RT.block_on(async {
5488 let ready = WebsocketConnection::new("ready");
5489 let not_ready = WebsocketConnection::new("not-ready");
5490 let (tx_r, mut rx_r) = unbounded_channel::<Message>();
5491 {
5492 let mut st = ready.state.lock().await;
5493 st.ws_write_tx = Some(tx_r);
5494 }
5495 {
5496 let mut st = not_ready.state.lock().await;
5497 st.ws_write_tx = None;
5498 }
5499 let common = WebsocketCommon::new(
5500 vec![ready.clone(), not_ready.clone()],
5501 WebsocketMode::Pool(2),
5502 0,
5503 None,
5504 None,
5505 );
5506 common.ping_server().await;
5507 match rx_r.try_recv() {
5508 Ok(Message::Ping(payload)) if payload.is_empty() => {}
5509 other => panic!("expected Ping on ready, got {other:?}"),
5510 }
5511 });
5512 }
5513
5514 #[test]
5515 fn no_ping_when_flags_block() {
5516 TOKIO_SHARED_RT.block_on(async {
5517 let conn = WebsocketConnection::new("c1");
5518 let (tx, mut rx) = unbounded_channel::<Message>();
5519 {
5520 let mut st = conn.state.lock().await;
5521 st.ws_write_tx = Some(tx);
5522 st.reconnection_pending = true;
5523 }
5524 let common = WebsocketCommon::new(
5525 vec![conn.clone()],
5526 WebsocketMode::Single,
5527 0,
5528 None,
5529 None,
5530 );
5531 common.ping_server().await;
5532 assert!(rx.try_recv().is_err());
5533 });
5534 }
5535 }
5536
5537 mod send {
5538 use super::*;
5539
5540 #[test]
5541 fn round_robin_send_without_specific() {
5542 TOKIO_SHARED_RT.block_on(async {
5543 let conn1 = WebsocketConnection::new("c1");
5544 let conn2 = WebsocketConnection::new("c2");
5545 let (tx1, mut rx1) = unbounded_channel::<Message>();
5546 let (tx2, mut rx2) = unbounded_channel::<Message>();
5547 {
5548 let mut s1 = conn1.state.lock().await;
5549 s1.ws_write_tx = Some(tx1);
5550 }
5551 {
5552 let mut s2 = conn2.state.lock().await;
5553 s2.ws_write_tx = Some(tx2);
5554 }
5555 let common = WebsocketCommon::new(
5556 vec![conn1.clone(), conn2.clone()],
5557 WebsocketMode::Pool(2),
5558 0,
5559 None,
5560 None,
5561 );
5562
5563 let res1 = common
5564 .send("a".into(), None, false, Duration::from_secs(1), None)
5565 .await
5566 .unwrap();
5567 assert!(res1.is_none());
5568
5569 let res2 = common
5570 .send("b".into(), None, false, Duration::from_secs(1), None)
5571 .await
5572 .unwrap();
5573 assert!(res2.is_none());
5574
5575 assert_eq!(
5576 if let Message::Text(t) = rx1.try_recv().unwrap() {
5577 t
5578 } else {
5579 panic!()
5580 },
5581 "a"
5582 );
5583 assert_eq!(
5584 if let Message::Text(t) = rx2.try_recv().unwrap() {
5585 t
5586 } else {
5587 panic!()
5588 },
5589 "b"
5590 );
5591 });
5592 }
5593
5594 #[test]
5595 fn round_robin_skips_not_ready() {
5596 TOKIO_SHARED_RT.block_on(async {
5597 let conn1 = WebsocketConnection::new("c1");
5598 let conn2 = WebsocketConnection::new("c2");
5599 let (tx2, mut rx2) = unbounded_channel::<Message>();
5600 {
5601 let mut s1 = conn1.state.lock().await;
5602 s1.ws_write_tx = None;
5603 }
5604 {
5605 let mut s2 = conn2.state.lock().await;
5606 s2.ws_write_tx = Some(tx2);
5607 }
5608 let common = WebsocketCommon::new(
5609 vec![conn1.clone(), conn2.clone()],
5610 WebsocketMode::Pool(2),
5611 0,
5612 None,
5613 None,
5614 );
5615 let res = common
5616 .send("bar".into(), None, false, Duration::from_secs(1), None)
5617 .await
5618 .unwrap();
5619 assert!(res.is_none());
5620 match rx2.try_recv().unwrap() {
5621 Message::Text(t) => assert_eq!(t, "bar"),
5622 other => panic!("unexpected {other:?}"),
5623 }
5624 });
5625 }
5626
5627 #[test]
5628 fn sync_send_on_specific_connection() {
5629 TOKIO_SHARED_RT.block_on(async {
5630 let conn1 = WebsocketConnection::new("c1");
5631 let conn2 = WebsocketConnection::new("c2");
5632 let (tx2, mut rx2) = unbounded_channel::<Message>();
5633 {
5634 let mut st = conn2.state.lock().await;
5635 st.ws_write_tx = Some(tx2);
5636 }
5637 let common = WebsocketCommon::new(
5638 vec![conn1.clone(), conn2.clone()],
5639 WebsocketMode::Pool(2),
5640 0,
5641 None,
5642 None,
5643 );
5644 let res = common
5645 .send(
5646 "payload".into(),
5647 Some("id".into()),
5648 false,
5649 Duration::from_secs(1),
5650 Some(conn2.clone()),
5651 )
5652 .await
5653 .unwrap();
5654 assert!(res.is_none());
5655 match rx2.try_recv() {
5656 Ok(Message::Text(t)) => assert_eq!(t, "payload"),
5657 other => panic!("expected Text, got {other:?}"),
5658 }
5659 });
5660 }
5661
5662 #[test]
5663 fn sync_send_with_id_does_not_insert_pending() {
5664 TOKIO_SHARED_RT.block_on(async {
5665 let conn = WebsocketConnection::new("c1");
5666 let (tx, mut rx) = unbounded_channel::<Message>();
5667 {
5668 let mut st = conn.state.lock().await;
5669 st.ws_write_tx = Some(tx);
5670 }
5671 let common = WebsocketCommon::new(
5672 vec![conn.clone()],
5673 WebsocketMode::Single,
5674 0,
5675 None,
5676 None,
5677 );
5678 let res = common
5679 .send(
5680 "msg".into(),
5681 Some("id".into()),
5682 false,
5683 Duration::from_secs(1),
5684 Some(conn.clone()),
5685 )
5686 .await
5687 .unwrap();
5688 assert!(res.is_none());
5689 assert!(conn.state.lock().await.pending_requests.is_empty());
5690 match rx.try_recv().unwrap() {
5691 Message::Text(t) => assert_eq!(t, "msg"),
5692 other => panic!("unexpected {other:?}"),
5693 }
5694 });
5695 }
5696
5697 #[test]
5698 fn sync_send_error_if_not_ready() {
5699 TOKIO_SHARED_RT.block_on(async {
5700 let conn = WebsocketConnection::new("c1");
5701 let common = WebsocketCommon::new(
5702 vec![conn.clone()],
5703 WebsocketMode::Single,
5704 0,
5705 None,
5706 None,
5707 );
5708 let err = common
5709 .send(
5710 "msg".into(),
5711 Some("id".into()),
5712 false,
5713 Duration::from_secs(1),
5714 Some(conn.clone()),
5715 )
5716 .await
5717 .unwrap_err();
5718 assert!(matches!(err, WebsocketError::NotConnected));
5719 });
5720 }
5721
5722 #[test]
5723 fn sync_send_error_when_no_ready() {
5724 TOKIO_SHARED_RT.block_on(async {
5725 let conn = WebsocketConnection::new("c1");
5726 let common = WebsocketCommon::new(
5727 vec![conn.clone()],
5728 WebsocketMode::Single,
5729 0,
5730 None,
5731 None,
5732 );
5733 let err = common
5734 .send("msg".into(), None, false, Duration::from_secs(1), None)
5735 .await
5736 .unwrap_err();
5737 assert!(matches!(err, WebsocketError::NotConnected));
5738 });
5739 }
5740
5741 #[test]
5742 fn async_send_and_receive() {
5743 TOKIO_SHARED_RT.block_on(async {
5744 let conn = WebsocketConnection::new("c1");
5745 let (tx, mut rx) = unbounded_channel::<Message>();
5746 {
5747 let mut st = conn.state.lock().await;
5748 st.ws_write_tx = Some(tx);
5749 }
5750 let common = WebsocketCommon::new(
5751 vec![conn.clone()],
5752 WebsocketMode::Single,
5753 0,
5754 None,
5755 None,
5756 );
5757 let fut = common
5758 .send(
5759 "hello".into(),
5760 Some("id".into()),
5761 true,
5762 Duration::from_secs(5),
5763 Some(conn.clone()),
5764 )
5765 .await
5766 .unwrap()
5767 .unwrap();
5768 match rx.try_recv() {
5769 Ok(Message::Text(t)) => assert_eq!(t, "hello"),
5770 other => panic!("expected Text, got {other:?}"),
5771 }
5772 {
5773 let mut st = conn.state.lock().await;
5774 let pr = st.pending_requests.remove("id").unwrap();
5775 pr.completion.send(Ok(serde_json::json!("ok"))).unwrap();
5776 }
5777 let resp = fut.await.unwrap().unwrap();
5778 assert_eq!(resp, serde_json::json!("ok"));
5779 });
5780 }
5781
5782 #[test]
5783 fn async_send_default_connection() {
5784 TOKIO_SHARED_RT.block_on(async {
5785 let conn = WebsocketConnection::new("c1");
5786 let (tx, mut rx) = unbounded_channel::<Message>();
5787 {
5788 let mut st = conn.state.lock().await;
5789 st.ws_write_tx = Some(tx);
5790 }
5791 let common = WebsocketCommon::new(
5792 vec![conn.clone()],
5793 WebsocketMode::Single,
5794 0,
5795 None,
5796 None,
5797 );
5798 let fut = common
5799 .send(
5800 "msg".into(),
5801 Some("id".into()),
5802 true,
5803 Duration::from_secs(5),
5804 None,
5805 )
5806 .await
5807 .unwrap()
5808 .unwrap();
5809 match rx.try_recv() {
5810 Ok(Message::Text(t)) => assert_eq!(t, "msg"),
5811 _ => panic!("no text"),
5812 }
5813 {
5814 let mut st = conn.state.lock().await;
5815 let pr = st.pending_requests.remove("id").unwrap();
5816 pr.completion.send(Ok(serde_json::json!(123))).unwrap();
5817 }
5818 let resp = fut.await.unwrap().unwrap();
5819 assert_eq!(resp, serde_json::json!(123));
5820 });
5821 }
5822
5823 #[test]
5824 fn async_send_failure_removes_pending_entry() {
5825 TOKIO_SHARED_RT.block_on(async {
5833 let conn = WebsocketConnection::new("c1");
5834 let (tx, rx) = unbounded_channel::<Message>();
5835 drop(rx);
5836 {
5837 let mut st = conn.state.lock().await;
5838 st.ws_write_tx = Some(tx);
5839 }
5840 let common = WebsocketCommon::new(
5841 vec![conn.clone()],
5842 WebsocketMode::Single,
5843 0,
5844 None,
5845 None,
5846 );
5847 let err = common
5848 .send(
5849 "msg".into(),
5850 Some("id".into()),
5851 true,
5852 Duration::from_secs(1),
5853 Some(conn.clone()),
5854 )
5855 .await
5856 .unwrap_err();
5857 assert!(matches!(err, WebsocketError::NotConnected));
5858 assert!(
5859 conn.state.lock().await.pending_requests.is_empty(),
5860 "pending_requests must be empty after a failed send"
5861 );
5862 });
5863 }
5864
5865 #[test]
5866 fn async_send_error_if_no_id() {
5867 TOKIO_SHARED_RT.block_on(async {
5868 let conn = WebsocketConnection::new("c§");
5869 let (tx, _rx) = unbounded_channel::<Message>();
5870 {
5871 let mut st = conn.state.lock().await;
5872 st.ws_write_tx = Some(tx);
5873 }
5874 let common = WebsocketCommon::new(
5875 vec![conn.clone()],
5876 WebsocketMode::Single,
5877 0,
5878 None,
5879 None,
5880 );
5881 let err = common
5882 .send(
5883 "msg".into(),
5884 None,
5885 true,
5886 Duration::from_secs(1),
5887 Some(conn.clone()),
5888 )
5889 .await
5890 .unwrap_err();
5891 assert!(matches!(err, WebsocketError::NotConnected));
5892 });
5893 }
5894
5895 #[test]
5896 fn timeout_rejects_async() {
5897 TOKIO_SHARED_RT.block_on(async {
5898 pause();
5899 let conn = WebsocketConnection::new("c1");
5900 let (tx, _rx) = unbounded_channel::<Message>();
5901 {
5902 let mut st = conn.state.lock().await;
5903 st.ws_write_tx = Some(tx);
5904 }
5905 let common = WebsocketCommon::new(
5906 vec![conn.clone()],
5907 WebsocketMode::Single,
5908 0,
5909 None,
5910 None,
5911 );
5912 let fut = common
5913 .send(
5914 "msg".into(),
5915 Some("id".into()),
5916 true,
5917 Duration::from_secs(1),
5918 Some(conn.clone()),
5919 )
5920 .await
5921 .unwrap()
5922 .unwrap();
5923 advance(Duration::from_secs(1)).await;
5924 let res = fut.await.unwrap();
5925 assert!(res.is_err(), "expected timeout error");
5926 assert!(!conn.state.lock().await.pending_requests.contains_key("id"));
5927 });
5928 }
5929
5930 #[test]
5931 fn async_send_errors_if_no_connection_ready() {
5932 TOKIO_SHARED_RT.block_on(async {
5933 let conn = WebsocketConnection::new("c1");
5934 let common = WebsocketCommon::new(
5935 vec![conn.clone()],
5936 WebsocketMode::Single,
5937 0,
5938 None,
5939 None,
5940 );
5941 let err = common
5942 .send(
5943 "msg".into(),
5944 Some("id".into()),
5945 true,
5946 Duration::from_secs(1),
5947 None,
5948 )
5949 .await
5950 .unwrap_err();
5951 assert!(matches!(err, WebsocketError::NotConnected));
5952 });
5953 }
5954 }
5955 }
5956
5957 mod websocket_api {
5958 use super::*;
5959
5960 mod initialisation {
5961 use super::*;
5962
5963 #[test]
5964 fn new_initializes_common() {
5965 TOKIO_SHARED_RT.block_on(async {
5966 let conn = WebsocketConnection::new("id");
5967 let pool = vec![conn.clone()];
5968
5969 let sig_gen = SignatureGenerator::new(
5970 Some("api_secret".to_string()),
5971 None::<PrivateKey>,
5972 None::<String>,
5973 );
5974
5975 let config = ConfigurationWebsocketApi {
5976 api_key: Some("api_key".to_string()),
5977 api_secret: Some("api_secret".to_string()),
5978 private_key: None,
5979 private_key_passphrase: None,
5980 ws_url: Some("wss://example".to_string()),
5981 mode: WebsocketMode::Single,
5982 reconnect_delay: 1000,
5983 signature_gen: sig_gen,
5984 timeout: 500,
5985 time_unit: None,
5986 auto_session_relogon: false,
5987 agent: None,
5988 handshake: None,
5989 user_agent: build_user_agent("product"),
5990 };
5991
5992 let api = WebsocketApi::new(config, pool.clone());
5993
5994 assert_eq!(api.common.connection_pool.len(), 1);
5995 assert_eq!(api.common.mode, WebsocketMode::Single);
5996
5997 let flag = *api.is_connecting.lock().await;
5998 assert!(!flag);
5999 });
6000 }
6001 }
6002
6003 mod connect {
6004 use super::*;
6005
6006 #[test]
6007 fn connect_when_not_connected_establishes() {
6008 TOKIO_SHARED_RT.block_on(async {
6009 let conn = WebsocketConnection::new("id");
6010 {
6011 let mut st = conn.state.lock().await;
6012 st.ws_write_tx = None;
6013 }
6014 let sig = SignatureGenerator::new(
6015 Some("api_secret".into()),
6016 None::<PrivateKey>,
6017 None::<String>,
6018 );
6019 let cfg = ConfigurationWebsocketApi {
6020 api_key: Some("api_key".into()),
6021 api_secret: Some("api_secret".to_string()),
6022 private_key: None,
6023 private_key_passphrase: None,
6024 ws_url: Some("ws://doesnotexist:1".to_string()),
6025 mode: WebsocketMode::Single,
6026 reconnect_delay: 0,
6027 signature_gen: sig,
6028 timeout: 10,
6029 time_unit: None,
6030 auto_session_relogon: false,
6031 agent: None,
6032 handshake: None,
6033 user_agent: build_user_agent("product"),
6034 };
6035 let api = WebsocketApi::new(cfg, vec![conn.clone()]);
6036 let res = api.clone().connect().await;
6037 assert!(!matches!(res, Err(WebsocketError::Timeout)));
6038 });
6039 }
6040
6041 #[test]
6042 fn already_connected_returns_ok() {
6043 TOKIO_SHARED_RT.block_on(async {
6044 let conn = WebsocketConnection::new("id2");
6045 let (tx, _) = unbounded_channel();
6046 {
6047 let mut st = conn.state.lock().await;
6048 st.ws_write_tx = Some(tx);
6049 }
6050 let sig = SignatureGenerator::new(
6051 Some("api_secret".to_string()),
6052 None::<PrivateKey>,
6053 None::<String>,
6054 );
6055 let cfg = ConfigurationWebsocketApi {
6056 api_key: Some("api_key".to_string()),
6057 api_secret: Some("api_secret".to_string()),
6058 private_key: None,
6059 private_key_passphrase: None,
6060 ws_url: Some("ws://example.com".to_string()),
6061 mode: WebsocketMode::Single,
6062 reconnect_delay: 0,
6063 signature_gen: sig,
6064 timeout: 10,
6065 time_unit: None,
6066 auto_session_relogon: false,
6067 agent: None,
6068 handshake: None,
6069 user_agent: build_user_agent("product"),
6070 };
6071 let api = WebsocketApi::new(cfg, vec![conn.clone()]);
6072 let res = api.connect().await;
6073 assert!(res.is_ok());
6074 });
6075 }
6076
6077 #[test]
6078 fn not_connected_returns_error() {
6079 TOKIO_SHARED_RT.block_on(async {
6080 let conn = WebsocketConnection::new("id1");
6081 let sig = SignatureGenerator::new(
6082 Some("api_secret".to_string()),
6083 None::<PrivateKey>,
6084 None::<String>,
6085 );
6086 let cfg = ConfigurationWebsocketApi {
6087 api_key: Some("api_key".to_string()),
6088 api_secret: Some("api_secret".to_string()),
6089 private_key: None,
6090 private_key_passphrase: None,
6091 ws_url: Some("ws://127.0.0.1:9".to_string()),
6092 mode: WebsocketMode::Single,
6093 reconnect_delay: 0,
6094 signature_gen: sig,
6095 timeout: 10,
6096 time_unit: None,
6097 auto_session_relogon: false,
6098 agent: None,
6099 handshake: None,
6100 user_agent: build_user_agent("product"),
6101 };
6102 let api = WebsocketApi::new(cfg, vec![conn.clone()]);
6103 let res = api.connect().await;
6104 assert!(res.is_err());
6105 });
6106 }
6107
6108 #[test]
6109 fn concurrent_calls_both_error_or_ok() {
6110 TOKIO_SHARED_RT.block_on(async {
6111 let conn = WebsocketConnection::new("id3");
6112 let sig = SignatureGenerator::new(
6113 Some("api_secret".to_string()),
6114 None::<PrivateKey>,
6115 None::<String>,
6116 );
6117 let cfg = ConfigurationWebsocketApi {
6118 api_key: Some("api_key".to_string()),
6119 api_secret: Some("api_secret".to_string()),
6120 private_key: None,
6121 private_key_passphrase: None,
6122 ws_url: Some("wss://invalid-domain".to_string()),
6123 mode: WebsocketMode::Single,
6124 reconnect_delay: 0,
6125 signature_gen: sig,
6126 timeout: 10,
6127 time_unit: None,
6128 auto_session_relogon: false,
6129 agent: None,
6130 handshake: None,
6131 user_agent: build_user_agent("product"),
6132 };
6133 let api = WebsocketApi::new(cfg, vec![conn.clone()]);
6134 let fut1 = tokio::spawn(api.clone().connect());
6135 let fut2 = tokio::spawn(api.clone().connect());
6136 let r1 = fut1.await.unwrap();
6137 let r2 = fut2.await.unwrap();
6138
6139 assert!(r1.is_err());
6140 assert!(r2.is_err() || r2.is_ok());
6141 });
6142 }
6143
6144 #[test]
6145 fn pool_failure_is_propagated() {
6146 TOKIO_SHARED_RT.block_on(async {
6147 let conn = WebsocketConnection::new("w");
6148 let sig = SignatureGenerator::new(
6149 Some("api_secret".to_string()),
6150 None::<PrivateKey>,
6151 None::<String>,
6152 );
6153 let cfg = ConfigurationWebsocketApi {
6154 api_key: Some("api_key".into()),
6155 api_secret: Some("api_secret".to_string()),
6156 private_key: None,
6157 private_key_passphrase: None,
6158 ws_url: Some("ws://doesnotexist:1".to_string()),
6159 mode: WebsocketMode::Single,
6160 reconnect_delay: 0,
6161 signature_gen: sig,
6162 timeout: 10,
6163 time_unit: None,
6164 auto_session_relogon: false,
6165 agent: None,
6166 handshake: None,
6167 user_agent: build_user_agent("product"),
6168 };
6169 let api = WebsocketApi::new(cfg, vec![conn.clone()]);
6170 let res = api.clone().connect().await;
6171 match res {
6172 Err(WebsocketError::Handshake(_) | WebsocketError::Timeout) => {}
6173 _ => panic!("expected handshake or timeout error"),
6174 }
6175 });
6176 }
6177 }
6178
6179 mod send_message {
6180 use super::*;
6181
6182 #[test]
6183 fn unsigned_message() {
6184 TOKIO_SHARED_RT.block_on(async {
6185 let api = create_websocket_api(None, None, None);
6186 let conn = &api.common.connection_pool[0];
6187 let (tx, mut rx) = unbounded_channel::<Message>();
6188 {
6189 let mut st = conn.state.lock().await;
6190 st.ws_write_tx = Some(tx);
6191 }
6192
6193 let fut = tokio::spawn({
6194 let api = api.clone();
6195 async move {
6196 let mut params = BTreeMap::new();
6197 params.insert("foo".into(), Value::String("bar".into()));
6198 let send_res = api
6199 .send_message::<Value>(
6200 "method",
6201 params,
6202 WebsocketMessageSendOptions {
6203 with_api_key: false,
6204 is_signed: false,
6205 ..Default::default()
6206 },
6207 )
6208 .await
6209 .unwrap();
6210
6211 match send_res {
6212 SendWebsocketMessageResult::Single(resp) => resp,
6213 SendWebsocketMessageResult::Multiple(_) => {
6214 panic!("expected single response")
6215 }
6216 }
6217 }
6218 });
6219
6220 let Message::Text(txt) = rx.recv().await.unwrap() else {
6221 panic!()
6222 };
6223 let req: Value = serde_json::from_str(&txt).unwrap();
6224 assert_eq!(req["method"], "method");
6225 assert_eq!(req["params"]["foo"], "bar");
6226 assert!(req["params"].get("apiKey").is_none());
6227 assert!(req["params"].get("timestamp").is_none());
6228 assert!(req["params"].get("signature").is_none());
6229
6230 let id = req["id"].as_str().unwrap().to_string();
6231 let mut st = conn.state.lock().await;
6232 let pending = st.pending_requests.remove(&id).unwrap();
6233 let reply = json!({
6234 "id": id,
6235 "result": { "x": 42 },
6236 "rateLimits": [{ "limit": 7 }]
6237 });
6238 pending.completion.send(Ok(reply)).unwrap();
6239
6240 let resp = fut.await.unwrap();
6241 let rate_limits = resp.rate_limits.unwrap_or_default();
6242
6243 assert!(rate_limits.is_empty());
6244 assert_eq!(resp.raw, json!({"x": 42}));
6245 });
6246 }
6247
6248 #[test]
6249 fn with_api_key_only() {
6250 TOKIO_SHARED_RT.block_on(async {
6251 let api = create_websocket_api(None, None, None);
6252 let conn = &api.common.connection_pool[0];
6253 let (tx, mut rx) = unbounded_channel::<Message>();
6254 {
6255 let mut st = conn.state.lock().await;
6256 st.ws_write_tx = Some(tx);
6257 }
6258
6259 let fut = tokio::spawn({
6260 let api = api.clone();
6261 async move {
6262 let params = BTreeMap::new();
6263 let send_res = api
6264 .send_message::<Value>(
6265 "method",
6266 params,
6267 WebsocketMessageSendOptions {
6268 with_api_key: true,
6269 is_signed: false,
6270 ..Default::default()
6271 },
6272 )
6273 .await
6274 .unwrap();
6275
6276 match send_res {
6277 SendWebsocketMessageResult::Single(resp) => resp,
6278 SendWebsocketMessageResult::Multiple(_) => {
6279 panic!("expected single response")
6280 }
6281 }
6282 }
6283 });
6284
6285 let Message::Text(txt) = rx.recv().await.unwrap() else {
6286 panic!()
6287 };
6288 let req: Value = serde_json::from_str(&txt).unwrap();
6289 assert_eq!(req["params"]["apiKey"], "api_key");
6290
6291 let id = req["id"].as_str().unwrap().to_string();
6292 let mut st = conn.state.lock().await;
6293 let pending = st.pending_requests.remove(&id).unwrap();
6294 pending
6295 .completion
6296 .send(Ok(json!({
6297 "id": id,
6298 "result": {},
6299 "rateLimits": []
6300 })))
6301 .unwrap();
6302
6303 let resp = fut.await.unwrap();
6304
6305 assert_eq!(resp.raw, json!({}));
6306 assert!(st.pending_requests.is_empty());
6307 });
6308 }
6309
6310 #[test]
6311 fn signed_message_has_timestamp_and_signature() {
6312 TOKIO_SHARED_RT.block_on(async {
6313 let api = create_websocket_api(None, None, None);
6314 let conn = &api.common.connection_pool[0];
6315 let (tx, mut rx) = unbounded_channel::<Message>();
6316 {
6317 let mut st = conn.state.lock().await;
6318 st.ws_write_tx = Some(tx);
6319 }
6320
6321 let fut = tokio::spawn({
6322 let api = api.clone();
6323 async move {
6324 let mut params = BTreeMap::new();
6325 params.insert("foo".into(), Value::String("bar".into()));
6326 let send_res = api
6327 .send_message::<Value>(
6328 "method",
6329 params,
6330 WebsocketMessageSendOptions {
6331 with_api_key: true,
6332 is_signed: true,
6333 ..Default::default()
6334 },
6335 )
6336 .await
6337 .unwrap();
6338
6339 match send_res {
6340 SendWebsocketMessageResult::Single(resp) => resp,
6341 SendWebsocketMessageResult::Multiple(_) => {
6342 panic!("expected single response")
6343 }
6344 }
6345 }
6346 });
6347
6348 let Message::Text(txt) = rx.recv().await.unwrap() else {
6349 panic!()
6350 };
6351 let req: Value = serde_json::from_str(&txt).unwrap();
6352 let p = &req["params"];
6353 assert_eq!(p["apiKey"], "api_key");
6354 assert!(p["timestamp"].is_number());
6355 assert!(p["signature"].is_string());
6356
6357 let id = req["id"].as_str().unwrap().to_string();
6358 let mut st = conn.state.lock().await;
6359 let pending = st.pending_requests.remove(&id).unwrap();
6360 pending
6361 .completion
6362 .send(Ok(json!({
6363 "id": id,
6364 "result": { "ok": true },
6365 "rateLimits": []
6366 })))
6367 .unwrap();
6368
6369 let resp = fut.await.unwrap();
6370 assert_eq!(resp.raw, json!({ "ok": true }));
6371 });
6372 }
6373
6374 #[test]
6375 fn multi_session_logon() {
6376 TOKIO_SHARED_RT.block_on(async {
6377 let api = create_websocket_api(None, Some(WebsocketMode::Pool(2)), None);
6378 let conn0 = &api.common.connection_pool[0];
6379 let conn1 = &api.common.connection_pool[1];
6380
6381 let (tx0, mut rx0) = unbounded_channel::<Message>();
6382 let (tx1, mut rx1) = unbounded_channel::<Message>();
6383 {
6384 let mut st0 = conn0.state.lock().await;
6385 st0.ws_write_tx = Some(tx0);
6386 }
6387 {
6388 let mut st1 = conn1.state.lock().await;
6389 st1.ws_write_tx = Some(tx1);
6390 }
6391
6392 let fut = tokio::spawn({
6393 let api = api.clone();
6394 async move {
6395 let params = BTreeMap::new();
6396 let send_res = api
6397 .send_message::<Value>(
6398 "method",
6399 params,
6400 WebsocketMessageSendOptions {
6401 is_session_logon: Some(true),
6402 ..Default::default()
6403 },
6404 )
6405 .await
6406 .unwrap();
6407
6408 match send_res {
6409 SendWebsocketMessageResult::Multiple(v) => v,
6410 SendWebsocketMessageResult::Single(_) => {
6411 panic!("expected multiple responses")
6412 }
6413 }
6414 }
6415 });
6416
6417 let Message::Text(txt0) = rx0.recv().await.unwrap() else {
6418 panic!()
6419 };
6420 let Message::Text(txt1) = rx1.recv().await.unwrap() else {
6421 panic!()
6422 };
6423 let req0: Value = serde_json::from_str(&txt0).unwrap();
6424 let req1: Value = serde_json::from_str(&txt1).unwrap();
6425 assert_eq!(req0["method"], "method");
6426 assert_eq!(req1["method"], "method");
6427 let id = req0["id"].as_str().unwrap().to_string();
6428 assert_eq!(req1["id"].as_str().unwrap(), &id);
6429
6430 {
6431 let mut st0 = conn0.state.lock().await;
6432 let pending0 = st0.pending_requests.remove(&id).unwrap();
6433 pending0
6434 .completion
6435 .send(Ok(json!({
6436 "id": id,
6437 "result": { "ok": true },
6438 "rateLimits": []
6439 })))
6440 .unwrap();
6441 }
6442 {
6443 let mut st1 = conn1.state.lock().await;
6444 let pending1 = st1.pending_requests.remove(&id).unwrap();
6445 pending1
6446 .completion
6447 .send(Ok(json!({
6448 "id": id,
6449 "result": { "ok": true },
6450 "rateLimits": []
6451 })))
6452 .unwrap();
6453 }
6454
6455 let results = fut.await.unwrap();
6456 assert_eq!(results.len(), 2);
6457
6458 for conn in &api.common.connection_pool {
6459 let st = conn.state.lock().await;
6460 assert!(st.is_session_logged_on, "should be logged out");
6461 assert!(st.session_logon_req.is_some(), "req cleared");
6462
6463 }
6479 });
6480 }
6481
6482 #[test]
6483 fn multi_session_logout() {
6484 TOKIO_SHARED_RT.block_on(async {
6485 let api = create_websocket_api(None, Some(WebsocketMode::Pool(2)), None);
6486
6487 for conn in &api.common.connection_pool {
6488 let (tx, _rx) = unbounded_channel::<Message>();
6489 let mut st = conn.state.lock().await;
6490 st.ws_write_tx = Some(tx);
6491 st.is_session_logged_on = true;
6492 st.session_logon_req = Some(WebsocketSessionLogonReq {
6493 method: "method".into(),
6494 payload: BTreeMap::new(),
6495 options: WebsocketMessageSendOptions::default(),
6496 });
6497 }
6498
6499 let mut rxs = Vec::new();
6500 for conn in &api.common.connection_pool {
6501 let rx = {
6502 let (tx, rx) = unbounded_channel::<Message>();
6503 conn.state.lock().await.ws_write_tx = Some(tx);
6504 rx
6505 };
6506 rxs.push(rx);
6507 }
6508
6509 let fut = tokio::spawn({
6510 let api = api.clone();
6511 async move {
6512 let send_res = api
6513 .send_message::<Value>(
6514 "method",
6515 BTreeMap::new(),
6516 WebsocketMessageSendOptions {
6517 is_signed: false,
6518 with_api_key: false,
6519 is_session_logout: Some(true),
6520 ..Default::default()
6521 },
6522 )
6523 .await
6524 .unwrap();
6525
6526 match send_res {
6527 SendWebsocketMessageResult::Multiple(v) => v,
6528 SendWebsocketMessageResult::Single(_) => panic!("expected multi"),
6529 }
6530 }
6531 });
6532
6533 let mut ids = Vec::new();
6534 for mut rx in rxs {
6535 let Message::Text(txt) = rx.recv().await.unwrap() else {
6536 panic!()
6537 };
6538 let req: Value = serde_json::from_str(&txt).unwrap();
6539 assert_eq!(req["method"], "method");
6540 ids.push(req["id"].as_str().unwrap().to_string());
6541 }
6542
6543 assert_eq!(ids[0], ids[1]);
6544
6545 for conn in &api.common.connection_pool {
6546 let id = &ids[0];
6547 let mut st = conn.state.lock().await;
6548 let pending = st.pending_requests.remove(id).unwrap();
6549 pending
6550 .completion
6551 .send(Ok(json!({
6552 "id": id,
6553 "result": {},
6554 "rateLimits": []
6555 })))
6556 .unwrap();
6557 }
6558
6559 let results = fut.await.unwrap();
6560 assert_eq!(results.len(), 2);
6561
6562 for conn in &api.common.connection_pool {
6563 let st = conn.state.lock().await;
6564 assert!(!st.is_session_logged_on, "should be logged out");
6565 assert!(st.session_logon_req.is_none(), "req cleared");
6566 }
6567 });
6568 }
6569
6570 #[test]
6571 fn skip_signature_when_logged_on_and_auto_relogon() {
6572 TOKIO_SHARED_RT.block_on(async {
6573 let api = create_websocket_api(None, Some(WebsocketMode::Single), None);
6574 let conn = &api.common.connection_pool[0];
6575 {
6576 let mut st = conn.state.lock().await;
6577 st.ws_write_tx = Some(unbounded_channel::<Message>().0);
6578 st.is_session_logged_on = true;
6579 }
6580
6581 let mut rx;
6582 {
6583 let mut st = conn.state.lock().await;
6584 let (tx, new_rx) = unbounded_channel::<Message>();
6585 st.ws_write_tx = Some(tx);
6586 rx = new_rx;
6587 }
6588
6589 let fut = tokio::spawn({
6590 let api = api.clone();
6591 async move {
6592 let send_res = api
6593 .send_message::<Value>(
6594 "method",
6595 BTreeMap::new(),
6596 WebsocketMessageSendOptions {
6597 is_signed: true,
6598 ..Default::default()
6599 },
6600 )
6601 .await
6602 .unwrap();
6603
6604 match send_res {
6605 SendWebsocketMessageResult::Single(resp) => resp,
6606 SendWebsocketMessageResult::Multiple(_) => {
6607 panic!("expected single")
6608 }
6609 }
6610 }
6611 });
6612
6613 let Message::Text(txt) = rx.recv().await.unwrap() else {
6614 panic!()
6615 };
6616 let req: Value = serde_json::from_str(&txt).unwrap();
6617 let p = &req["params"];
6618 assert!(p.get("timestamp").is_some());
6619 assert!(p.get("signature").is_none());
6620
6621 let id = req["id"].as_str().unwrap();
6622 let mut st = conn.state.lock().await;
6623 let pending = st.pending_requests.remove(id).unwrap();
6624 pending
6625 .completion
6626 .send(Ok(json!({
6627 "id": id,
6628 "result": {},
6629 "rateLimits": []
6630 })))
6631 .unwrap();
6632
6633 let resp = fut.await.unwrap();
6634 assert_eq!(resp.raw, json!({}));
6635 });
6636 }
6637
6638 #[test]
6639 fn include_signature_when_logged_on_and_no_auto_relogon() {
6640 TOKIO_SHARED_RT.block_on(async {
6641 let api = create_websocket_api(None, Some(WebsocketMode::Single), Some(false));
6642 let conn = &api.common.connection_pool[0];
6643 {
6644 let mut st = conn.state.lock().await;
6645 st.ws_write_tx = Some(unbounded_channel::<Message>().0);
6646 st.is_session_logged_on = true;
6647 }
6648
6649 let mut rx;
6650 {
6651 let mut st = conn.state.lock().await;
6652 let (tx, new_rx) = unbounded_channel::<Message>();
6653 st.ws_write_tx = Some(tx);
6654 rx = new_rx;
6655 }
6656
6657 let fut = tokio::spawn({
6658 let api = api.clone();
6659 async move {
6660 let send_res = api
6661 .send_message::<Value>(
6662 "method",
6663 BTreeMap::new(),
6664 WebsocketMessageSendOptions {
6665 is_signed: true,
6666 ..Default::default()
6667 },
6668 )
6669 .await
6670 .unwrap();
6671
6672 match send_res {
6673 SendWebsocketMessageResult::Single(resp) => resp,
6674 SendWebsocketMessageResult::Multiple(_) => {
6675 panic!("expected single")
6676 }
6677 }
6678 }
6679 });
6680
6681 let Message::Text(txt) = rx.recv().await.unwrap() else {
6682 panic!()
6683 };
6684 let req: Value = serde_json::from_str(&txt).unwrap();
6685 let p = &req["params"];
6686 assert!(p.get("timestamp").is_some());
6687 assert!(p.get("signature").is_some());
6688
6689 let id = req["id"].as_str().unwrap();
6690 let mut st = conn.state.lock().await;
6691 let pending = st.pending_requests.remove(id).unwrap();
6692 pending
6693 .completion
6694 .send(Ok(json!({
6695 "id": id,
6696 "result": {},
6697 "rateLimits": []
6698 })))
6699 .unwrap();
6700
6701 let resp = fut.await.unwrap();
6702 assert_eq!(resp.raw, json!({}));
6703 });
6704 }
6705
6706 #[test]
6707 fn error_if_not_connected() {
6708 TOKIO_SHARED_RT.block_on(async {
6709 let api = create_websocket_api(None, None, None);
6710 let conn = &api.common.connection_pool[0];
6711 {
6712 let mut st = conn.state.lock().await;
6713 st.ws_write_tx = None;
6714 }
6715 let params = BTreeMap::new();
6716 let err = api
6717 .send_message::<Value>(
6718 "method",
6719 params,
6720 WebsocketMessageSendOptions {
6721 with_api_key: false,
6722 is_signed: false,
6723 ..Default::default()
6724 },
6725 )
6726 .await
6727 .unwrap_err();
6728 matches!(err, WebsocketError::NotConnected);
6729 });
6730 }
6731 }
6732
6733 mod prepare_url {
6734 use super::*;
6735
6736 #[test]
6737 fn no_time_unit() {
6738 TOKIO_SHARED_RT.block_on(async {
6739 let api = create_websocket_api(None, None, None);
6740 let url = "wss://example.com/ws".to_string();
6741 assert_eq!(api.prepare_url(&url), url);
6742 });
6743 }
6744
6745 #[test]
6746 fn appends_time_unit() {
6747 TOKIO_SHARED_RT.block_on(async {
6748 let api = create_websocket_api(Some(TimeUnit::Millisecond), None, None);
6749 let base = "wss://example.com/ws".to_string();
6750 let got = api.prepare_url(&base);
6751 assert_eq!(got, format!("{base}?timeUnit=millisecond"));
6752 });
6753 }
6754
6755 #[test]
6756 fn handles_existing_query() {
6757 TOKIO_SHARED_RT.block_on(async {
6758 let api = create_websocket_api(Some(TimeUnit::Microsecond), None, None);
6759 let base = "wss://example.com/ws?foo=bar".to_string();
6760 let got = api.prepare_url(&base);
6761 assert_eq!(got, format!("{base}&timeUnit=microsecond"));
6762 });
6763 }
6764 }
6765
6766 mod on_open {
6767 use super::*;
6768
6769 fn create_websocket_api_and_conn() -> (Arc<WebsocketApi>, Arc<WebsocketConnection>) {
6770 let sig_gen = SignatureGenerator::new(
6771 Some("api_secret".to_string()),
6772 None::<_>,
6773 None::<String>,
6774 );
6775 let config = ConfigurationWebsocketApi {
6776 api_key: Some("api_key".to_string()),
6777 api_secret: Some("api_secret".to_string()),
6778 private_key: None,
6779 private_key_passphrase: None,
6780 ws_url: Some("wss://example".to_string()),
6781 mode: WebsocketMode::Single,
6782 reconnect_delay: 0,
6783 signature_gen: sig_gen,
6784 timeout: 1000,
6785 time_unit: None,
6786 auto_session_relogon: true,
6787 agent: None,
6788 handshake: None,
6789 user_agent: build_user_agent("product"),
6790 };
6791 let conn = WebsocketConnection::new("test-conn");
6792 let api = WebsocketApi::new(config, vec![conn.clone()]);
6793 (api, conn)
6794 }
6795
6796 #[test]
6797 fn session_relogon_on_open() {
6798 TOKIO_SHARED_RT.block_on(async {
6799 let (api, conn) = create_websocket_api_and_conn();
6800
6801 let req = WebsocketSessionLogonReq {
6802 method: "method".into(),
6803 payload: {
6804 let mut m = BTreeMap::new();
6805 m.insert("foo".into(), Value::String("bar".into()));
6806 m
6807 },
6808 options: WebsocketMessageSendOptions {
6809 with_api_key: true,
6810 is_signed: true,
6811 is_session_logon: Some(true),
6812 ..Default::default()
6813 },
6814 };
6815
6816 let (tx, mut rx) = unbounded_channel::<Message>();
6817 {
6818 let mut st = conn.state.lock().await;
6819 st.session_logon_req = Some(req.clone());
6820 st.is_session_logged_on = false;
6821 st.ws_write_tx = Some(tx);
6822 }
6823
6824 api.on_open("wss://example".to_string(), conn.clone()).await;
6825
6826 let Message::Text(raw) = rx.recv().await.unwrap() else {
6827 panic!("expected a Text message");
6828 };
6829 let msg: Value = serde_json::from_str(&raw).unwrap();
6830 assert_eq!(msg["method"], "method");
6831 assert_eq!(msg["params"]["foo"], "bar");
6832
6833 let id = msg["id"].as_str().unwrap().to_string();
6834 {
6835 let mut st = conn.state.lock().await;
6836 let pending = st.pending_requests.remove(&id).expect("pending request");
6837 pending
6838 .completion
6839 .send(Ok(json!({
6840 "id": id,
6841 "result": {},
6842 "rateLimits": []
6843 })))
6844 .unwrap();
6845 }
6846
6847 sleep(Duration::from_millis(10)).await;
6848
6849 let st = conn.state.lock().await;
6850 assert!(st.is_session_logged_on, "should now be logged on");
6851 });
6852 }
6853
6854 #[test]
6855 fn no_relogon_if_already_logged_on() {
6856 TOKIO_SHARED_RT.block_on(async {
6857 let (api, conn) = create_websocket_api_and_conn();
6858
6859 let req = WebsocketSessionLogonReq {
6860 method: "method".into(),
6861 payload: BTreeMap::new(),
6862 options: WebsocketMessageSendOptions {
6863 is_session_logon: Some(true),
6864 ..Default::default()
6865 },
6866 };
6867
6868 let (tx, mut rx) = unbounded_channel::<Message>();
6869 {
6870 let mut st = conn.state.lock().await;
6871 st.session_logon_req = Some(req);
6872 st.is_session_logged_on = true;
6873 st.ws_write_tx = Some(tx);
6874 }
6875
6876 api.on_open("wss://example".to_string(), conn.clone()).await;
6877
6878 assert!(rx.try_recv().is_err(), "no re‐logon when already on");
6879
6880 let st = conn.state.lock().await;
6881 assert!(st.is_session_logged_on);
6882 });
6883 }
6884
6885 #[test]
6886 fn session_relogon_fails_gracefully() {
6887 TOKIO_SHARED_RT.block_on(async {
6888 let (api, conn) = create_websocket_api_and_conn();
6889
6890 let req = WebsocketSessionLogonReq {
6891 method: "method".into(),
6892 payload: {
6893 let mut m = BTreeMap::new();
6894 m.insert("x".into(), Value::Number(1.into()));
6895 m
6896 },
6897 options: WebsocketMessageSendOptions {
6898 is_session_logon: Some(true),
6899 ..Default::default()
6900 },
6901 };
6902 {
6903 let mut st = conn.state.lock().await;
6904 st.session_logon_req = Some(req);
6905 st.is_session_logged_on = false;
6906 st.ws_write_tx = None;
6907 }
6908
6909 api.on_open("wss://example".into(), conn.clone()).await;
6910
6911 let st = conn.state.lock().await;
6912 assert!(
6913 !st.is_session_logged_on,
6914 "should remain logged‐off on failure"
6915 );
6916 });
6917 }
6918
6919 #[test]
6920 fn session_relogon_noop_when_no_req() {
6921 TOKIO_SHARED_RT.block_on(async {
6922 let (api, conn) = create_websocket_api_and_conn();
6923
6924 {
6925 let mut st = conn.state.lock().await;
6926 st.session_logon_req = None;
6927 st.is_session_logged_on = false;
6928 st.ws_write_tx = Some(unbounded_channel::<Message>().0);
6929 }
6930
6931 api.on_open("wss://example".into(), conn.clone()).await;
6932
6933 let st = conn.state.lock().await;
6934 assert!(!st.is_session_logged_on, "still logged‐off");
6935 });
6936 }
6937 }
6938
6939 mod on_message {
6940 use super::*;
6941
6942 fn create_websocket_api_and_conn() -> (Arc<WebsocketApi>, Arc<WebsocketConnection>) {
6943 let sig_gen = SignatureGenerator::new(
6944 Some("api_secret".to_string()),
6945 None::<_>,
6946 None::<String>,
6947 );
6948 let config = ConfigurationWebsocketApi {
6949 api_key: Some("api_key".to_string()),
6950 api_secret: Some("api_secret".to_string()),
6951 private_key: None,
6952 private_key_passphrase: None,
6953 ws_url: Some("wss://example".to_string()),
6954 mode: WebsocketMode::Single,
6955 reconnect_delay: 0,
6956 signature_gen: sig_gen,
6957 timeout: 1000,
6958 time_unit: None,
6959 auto_session_relogon: false,
6960 agent: None,
6961 handshake: None,
6962 user_agent: build_user_agent("product"),
6963 };
6964 let conn = WebsocketConnection::new("test");
6965 let api = WebsocketApi::new(config, vec![conn.clone()]);
6966 (api, conn)
6967 }
6968
6969 #[test]
6970 fn resolves_pending_and_removes_request() {
6971 TOKIO_SHARED_RT.block_on(async {
6972 let (api, conn) = create_websocket_api_and_conn();
6973 let (tx, rx) = oneshot::channel();
6974 {
6975 let mut st = conn.state.lock().await;
6976 st.pending_requests
6977 .insert("id1".to_string(), PendingRequest { completion: tx });
6978 }
6979 let msg = json!({"id":"id1","status":200,"foo":"bar"});
6980 api.on_message(msg.to_string(), conn.clone()).await;
6981 let got = rx.await.unwrap().unwrap();
6982 assert_eq!(got, msg);
6983 let st = conn.state.lock().await;
6984 assert!(!st.pending_requests.contains_key("id1"));
6985 });
6986 }
6987
6988 #[test]
6989 fn uses_result_when_present() {
6990 TOKIO_SHARED_RT.block_on(async {
6991 let (api, conn) = create_websocket_api_and_conn();
6992 let (tx, rx) = oneshot::channel();
6993 {
6994 let mut st = conn.state.lock().await;
6995 st.pending_requests
6996 .insert("id1".to_string(), PendingRequest { completion: tx });
6997 }
6998 let msg = json!({
6999 "id": "id1",
7000 "status": 200,
7001 "response": [1,2],
7002 "result": {"a":1}
7003 });
7004 api.on_message(msg.to_string(), conn.clone()).await;
7005 let got = rx.await.unwrap().unwrap();
7006 assert_eq!(got.get("result").unwrap(), &json!({"a":1}));
7007 });
7008 }
7009
7010 #[test]
7011 fn uses_response_when_no_result() {
7012 TOKIO_SHARED_RT.block_on(async {
7013 let (api, conn) = create_websocket_api_and_conn();
7014 let (tx, rx) = oneshot::channel();
7015 {
7016 let mut st = conn.state.lock().await;
7017 st.pending_requests
7018 .insert("id1".to_string(), PendingRequest { completion: tx });
7019 }
7020 let msg = json!({
7021 "id": "id1",
7022 "status": 200,
7023 "response": ["ok"]
7024 });
7025 api.on_message(msg.to_string(), conn.clone()).await;
7026 let got = rx.await.unwrap().unwrap();
7027 assert_eq!(got.get("response").unwrap(), &json!(["ok"]));
7028 });
7029 }
7030
7031 #[test]
7032 fn errors_for_status_ge_400() {
7033 TOKIO_SHARED_RT.block_on(async {
7034 let (api, conn) = create_websocket_api_and_conn();
7035 let (tx, rx) = oneshot::channel();
7036 {
7037 let mut st = conn.state.lock().await;
7038 st.pending_requests
7039 .insert("bad".to_string(), PendingRequest { completion: tx });
7040 }
7041 let err_obj = json!({"code":123,"msg":"oops"});
7042 let msg = json!({"id":"bad","status":500,"error":err_obj});
7043 api.on_message(msg.to_string(), conn.clone()).await;
7044 match rx.await.unwrap() {
7045 Err(WebsocketError::ResponseError { code, message }) => {
7046 assert_eq!(code, 123);
7047 assert_eq!(message, "oops");
7048 }
7049 other => panic!("expected ResponseError, got {other:?}"),
7050 }
7051 let st = conn.state.lock().await;
7052 assert!(!st.pending_requests.contains_key("bad"));
7053 });
7054 }
7055
7056 #[test]
7057 fn ignores_unknown_id() {
7058 TOKIO_SHARED_RT.block_on(async {
7059 let (api, conn) = create_websocket_api_and_conn();
7060 let msg = json!({"id":"nope","status":200});
7061 api.on_message(msg.to_string(), conn.clone()).await;
7062 let st = conn.state.lock().await;
7063 assert!(st.pending_requests.is_empty());
7064 });
7065 }
7066
7067 #[test]
7068 fn parse_error_ignored() {
7069 TOKIO_SHARED_RT.block_on(async {
7070 let (api, conn) = create_websocket_api_and_conn();
7071 api.on_message("not json".to_string(), conn.clone()).await;
7072 let st = conn.state.lock().await;
7073 assert!(st.pending_requests.is_empty());
7074 });
7075 }
7076
7077 #[test]
7078 fn error_status_sends_error() {
7079 TOKIO_SHARED_RT.block_on(async {
7080 let (api, conn) = create_websocket_api_and_conn();
7081 let (tx, rx) = oneshot::channel();
7082 {
7083 let mut st = conn.state.lock().await;
7084 st.pending_requests
7085 .insert("err".to_string(), PendingRequest { completion: tx });
7086 }
7087 let msg = json!({
7088 "id": "err",
7089 "status": 500,
7090 "error": { "code": 42, "msg": "Bad!" }
7091 });
7092 api.on_message(msg.to_string(), conn.clone()).await;
7093 match rx.await.unwrap() {
7094 Err(WebsocketError::ResponseError { code, message }) => {
7095 assert_eq!(code, 42);
7096 assert_eq!(message, "Bad!");
7097 }
7098 other => panic!("expected ResponseError, got {other:?}"),
7099 }
7100 });
7101 }
7102
7103 #[test]
7104 fn unknown_id_logs_warning_and_leaves_pending() {
7105 TOKIO_SHARED_RT.block_on(async {
7106 let (api, conn) = create_websocket_api_and_conn();
7107 {
7108 let mut st = conn.state.lock().await;
7109 st.pending_requests.insert(
7110 "keep".to_string(),
7111 PendingRequest {
7112 completion: oneshot::channel().0,
7113 },
7114 );
7115 }
7116 api.on_message(
7117 json!({ "id": "foo", "status": 200, "result": 1 }).to_string(),
7118 conn.clone(),
7119 )
7120 .await;
7121 let st = conn.state.lock().await;
7122 assert!(st.pending_requests.contains_key("keep"));
7123 });
7124 }
7125
7126 #[test]
7127 fn server_shutdown_enqueues_reconnect() {
7128 TOKIO_SHARED_RT.block_on(async {
7129 let (api, conn) = create_websocket_api_and_conn();
7130
7131 let msg = json!({
7132 "event": { "e": "serverShutdown" }
7133 });
7134
7135 api.on_message(msg.to_string(), conn.clone()).await;
7136
7137 let st = conn.state.lock().await;
7138 assert!(st.renewal_pending);
7139 });
7140 }
7141
7142 #[test]
7143 fn server_shutdown_ignored_if_renewal_pending() {
7144 TOKIO_SHARED_RT.block_on(async {
7145 let (api, conn) = create_websocket_api_and_conn();
7146
7147 {
7148 let mut st = conn.state.lock().await;
7149 st.renewal_pending = true;
7150 }
7151
7152 let msg = json!({
7153 "event": { "e": "serverShutdown" }
7154 });
7155
7156 api.on_message(msg.to_string(), conn.clone()).await;
7157
7158 let st = conn.state.lock().await;
7159
7160 assert!(st.renewal_pending);
7161 });
7162 }
7163
7164 #[test]
7165 fn server_shutdown_ignored_if_close_initiated() {
7166 TOKIO_SHARED_RT.block_on(async {
7167 let (api, conn) = create_websocket_api_and_conn();
7168
7169 {
7170 let mut st = conn.state.lock().await;
7171 st.close_initiated = true;
7172 }
7173
7174 let msg = json!({
7175 "event": { "e": "serverShutdown" }
7176 });
7177
7178 api.on_message(msg.to_string(), conn.clone()).await;
7179
7180 let st = conn.state.lock().await;
7181
7182 assert!(!st.renewal_pending);
7183 });
7184 }
7185
7186 #[test]
7187 fn server_shutdown_does_not_touch_pending_requests() {
7188 TOKIO_SHARED_RT.block_on(async {
7189 let (api, conn) = create_websocket_api_and_conn();
7190
7191 {
7192 let mut st = conn.state.lock().await;
7193 st.pending_requests.insert(
7194 "keep".to_string(),
7195 PendingRequest {
7196 completion: oneshot::channel().0,
7197 },
7198 );
7199 }
7200
7201 let msg = json!({
7202 "event": { "e": "serverShutdown" }
7203 });
7204
7205 api.on_message(msg.to_string(), conn.clone()).await;
7206
7207 let st = conn.state.lock().await;
7208
7209 assert!(st.pending_requests.contains_key("keep"));
7210 assert!(st.renewal_pending);
7211 });
7212 }
7213 }
7214 }
7215
7216 mod websocket_streams {
7217 use super::*;
7218
7219 mod initialisation {
7220 use super::*;
7221
7222 #[test]
7223 fn new_initializes_fields() {
7224 TOKIO_SHARED_RT.block_on(async {
7225 let config = ConfigurationWebsocketStreams {
7226 ws_url: Some("wss://example".to_string()),
7227 mode: WebsocketMode::Pool(2),
7228 reconnect_delay: 500,
7229 time_unit: None,
7230 agent: None,
7231 handshake: None,
7232 user_agent: build_user_agent("product"),
7233 };
7234 let conn1 = WebsocketConnection::new("c1");
7235 let conn2 = WebsocketConnection::new("c2");
7236 let api = WebsocketStreams::new(
7237 config.clone(),
7238 vec![conn1.clone(), conn2.clone()],
7239 vec![],
7240 );
7241
7242 assert_eq!(api.common.connection_pool.len(), 2);
7243 assert!(Arc::ptr_eq(&api.common.connection_pool[0], &conn1));
7244 assert!(Arc::ptr_eq(&api.common.connection_pool[1], &conn2));
7245 assert_eq!(api.configuration.ws_url, Some("wss://example".to_string()));
7246 let flag = api.is_connecting.lock().await;
7247 assert!(!*flag);
7248 });
7249 }
7250
7251 #[test]
7252 fn new_expands_pool_when_url_paths_present() {
7253 TOKIO_SHARED_RT.block_on(async {
7254 let config = ConfigurationWebsocketStreams {
7255 ws_url: Some("wss://example".to_string()),
7256 mode: WebsocketMode::Pool(2),
7257 reconnect_delay: 500,
7258 time_unit: None,
7259 agent: None,
7260 handshake: None,
7261 user_agent: build_user_agent("product"),
7262 };
7263
7264 let conn1 = WebsocketConnection::new("c1");
7265 let conn2 = WebsocketConnection::new("c2");
7266
7267 let api = WebsocketStreams::new(
7268 config,
7269 vec![conn1.clone(), conn2.clone()],
7270 vec!["path1".to_string(), "path2".to_string()],
7271 );
7272
7273 assert_eq!(api.common.connection_pool.len(), 4);
7274 assert!(Arc::ptr_eq(&api.common.connection_pool[0], &conn1));
7275 assert!(Arc::ptr_eq(&api.common.connection_pool[1], &conn2));
7276 });
7277 }
7278
7279 #[test]
7280 fn new_does_not_expand_pool_when_already_sized_for_url_paths() {
7281 TOKIO_SHARED_RT.block_on(async {
7282 let config = ConfigurationWebsocketStreams {
7283 ws_url: Some("wss://example".to_string()),
7284 mode: WebsocketMode::Pool(2),
7285 reconnect_delay: 500,
7286 time_unit: None,
7287 agent: None,
7288 handshake: None,
7289 user_agent: build_user_agent("product"),
7290 };
7291
7292 let conns = vec![
7293 WebsocketConnection::new("c1"),
7294 WebsocketConnection::new("c2"),
7295 WebsocketConnection::new("c3"),
7296 WebsocketConnection::new("c4"),
7297 ];
7298
7299 let api = WebsocketStreams::new(
7300 config,
7301 conns.clone(),
7302 vec!["path1".to_string(), "path2".to_string()],
7303 );
7304
7305 assert_eq!(api.common.connection_pool.len(), 4);
7306 for (i, c) in conns.iter().enumerate() {
7307 assert!(Arc::ptr_eq(&api.common.connection_pool[i], c));
7308 }
7309 });
7310 }
7311 }
7312
7313 mod connect {
7314 use super::*;
7315
7316 #[test]
7317 fn establishes_successfully() {
7318 TOKIO_SHARED_RT.block_on(async {
7319 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
7320 let port = listener.local_addr().unwrap().port();
7321
7322 tokio::spawn(async move {
7323 for _ in 0..2 {
7324 if let Ok((stream, _)) = listener.accept().await {
7325 let mut ws = accept_async(stream).await.unwrap();
7326 ws.close(None).await.ok();
7327 }
7328 }
7329 });
7330
7331 let create_websocket_streams = |ws_url: &str| {
7332 let c1 = WebsocketConnection::new("c1");
7333 let c2 = WebsocketConnection::new("c2");
7334 let config = ConfigurationWebsocketStreams {
7335 ws_url: Some(ws_url.to_string()),
7336 mode: WebsocketMode::Pool(2),
7337 reconnect_delay: 500,
7338 time_unit: None,
7339 agent: None,
7340 handshake: None,
7341 user_agent: build_user_agent("product"),
7342 };
7343 WebsocketStreams::new(config, vec![c1, c2], vec![])
7344 };
7345
7346 let url = format!("ws://127.0.0.1:{port}");
7347 let ws = create_websocket_streams(&url);
7348
7349 let res = ws.connect(vec!["stream1".into()]).await;
7350 assert!(res.is_ok());
7351 });
7352 }
7353
7354 #[test]
7355 fn establishes_successfully_with_url_paths() {
7356 TOKIO_SHARED_RT.block_on(async {
7357 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
7358 let addr = listener.local_addr().unwrap();
7359
7360 tokio::spawn(async move {
7361 for _ in 0..4 {
7362 if let Ok((stream, _)) = listener.accept().await {
7363 let mut ws = accept_async(stream).await.unwrap();
7364 ws.close(None).await.ok();
7365 }
7366 }
7367 });
7368
7369 let config = ConfigurationWebsocketStreams {
7370 ws_url: Some(format!("ws://{}", addr)),
7371 mode: WebsocketMode::Pool(2),
7372 reconnect_delay: 500,
7373 time_unit: None,
7374 agent: None,
7375 handshake: None,
7376 user_agent: build_user_agent("product"),
7377 };
7378
7379 let ws = WebsocketStreams::new(
7380 config,
7381 vec![],
7382 vec!["path1".to_string(), "path2".to_string()],
7383 );
7384
7385 let res = ws.clone().connect(vec!["stream1".into()]).await;
7386 assert!(res.is_ok());
7387 assert_eq!(ws.common.connection_pool.len(), 4);
7388 });
7389 }
7390
7391 #[test]
7392 fn connect_sets_url_path_on_connections_when_url_paths_present() {
7393 TOKIO_SHARED_RT.block_on(async {
7394 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
7395 let addr = listener.local_addr().unwrap();
7396
7397 tokio::spawn(async move {
7398 for _ in 0..4 {
7399 if let Ok((stream, _)) = listener.accept().await {
7400 let mut ws = accept_async(stream).await.unwrap();
7401 ws.close(None).await.ok();
7402 }
7403 }
7404 });
7405
7406 let config = ConfigurationWebsocketStreams {
7407 ws_url: Some(format!("ws://{}", addr)),
7408 mode: WebsocketMode::Pool(2),
7409 reconnect_delay: 500,
7410 time_unit: None,
7411 agent: None,
7412 handshake: None,
7413 user_agent: build_user_agent("product"),
7414 };
7415
7416 let ws = WebsocketStreams::new(
7417 config,
7418 vec![],
7419 vec!["path1".to_string(), "path2".to_string()],
7420 );
7421
7422 ws.clone().connect(vec!["stream1".into()]).await.unwrap();
7423
7424 let pool_size = ws.configuration.mode.pool_size();
7425
7426 for (i, conn) in ws.common.connection_pool.iter().enumerate() {
7427 let expected = if i < pool_size { "path1" } else { "path2" };
7428 let st = conn.state.lock().await;
7429 assert_eq!(st.url_path.as_deref(), Some(expected));
7430 }
7431 });
7432 }
7433
7434 #[test]
7435 fn refused_returns_error() {
7436 TOKIO_SHARED_RT.block_on(async {
7437 let ws = create_websocket_streams(Some("ws://127.0.0.1:9"), None, None);
7438 let res = ws.connect(vec!["stream1".into()]).await;
7439 assert!(res.is_err());
7440 });
7441 }
7442
7443 #[test]
7444 fn invalid_url_returns_error() {
7445 TOKIO_SHARED_RT.block_on(async {
7446 let ws = create_websocket_streams(Some("not-a-url"), None, None);
7447 let res = ws.connect(vec!["s".into()]).await;
7448 assert!(res.is_err());
7449 });
7450 }
7451 }
7452
7453 mod disconnect {
7454 use super::*;
7455
7456 #[test]
7457 fn disconnect_clears_state_and_streams() {
7458 TOKIO_SHARED_RT.block_on(async {
7459 let ws = create_websocket_streams(None, None, None);
7460 let conn = &ws.common.connection_pool[0];
7461 {
7462 let mut state = conn.state.lock().await;
7463 state.stream_callbacks.insert("s1".to_string(), Vec::new());
7464 state.pending_subscriptions.push_back("s2".to_string());
7465 }
7466 {
7467 let mut map = ws.connection_streams.lock().await;
7468 map.insert("s3".to_string(), Arc::clone(conn));
7469 }
7470
7471 let res = ws.disconnect().await;
7472 assert!(res.is_ok());
7473
7474 let state = conn.state.lock().await;
7475 assert!(state.stream_callbacks.is_empty());
7476 assert!(state.pending_subscriptions.is_empty());
7477
7478 let map = ws.connection_streams.lock().await;
7479 assert!(map.is_empty());
7480 });
7481 }
7482 }
7483
7484 mod subscribe {
7485 use super::*;
7486
7487 #[test]
7488 fn empty_list_does_nothing() {
7489 TOKIO_SHARED_RT.block_on(async {
7490 let ws = create_websocket_streams(None, None, None);
7491 ws.clone().subscribe(Vec::new(), None, None).await;
7492 let map = ws.connection_streams.lock().await;
7493 assert!(map.is_empty());
7494 });
7495 }
7496
7497 #[test]
7498 fn queue_when_not_ready() {
7499 TOKIO_SHARED_RT.block_on(async {
7500 let ws = create_websocket_streams(None, None, None);
7501 let conn = ws.common.connection_pool[0].clone();
7502 ws.clone().subscribe(vec!["s1".into()], None, None).await;
7503 let state = conn.state.lock().await;
7504 let pending: Vec<String> =
7505 state.pending_subscriptions.iter().cloned().collect();
7506 assert_eq!(pending, vec!["s1".to_string()]);
7507 });
7508 }
7509
7510 #[test]
7511 fn only_one_subscription_per_stream() {
7512 TOKIO_SHARED_RT.block_on(async {
7513 let ws = create_websocket_streams(None, None, None);
7514 let conn = ws.common.connection_pool[0].clone();
7515 ws.clone().subscribe(vec!["s1".into()], None, None).await;
7516 ws.clone().subscribe(vec!["s1".into()], None, None).await;
7517 let state = conn.state.lock().await;
7518 let pending: Vec<String> =
7519 state.pending_subscriptions.iter().cloned().collect();
7520 assert_eq!(pending, vec!["s1".to_string()]);
7521 });
7522 }
7523
7524 #[test]
7525 fn multiple_streams_assigned() {
7526 TOKIO_SHARED_RT.block_on(async {
7527 let ws = create_websocket_streams(None, None, None);
7528 ws.clone()
7529 .subscribe(vec!["s1".into(), "s2".into()], None, None)
7530 .await;
7531 let map = ws.connection_streams.lock().await;
7532 assert!(map.contains_key("s1"));
7533 assert!(map.contains_key("s2"));
7534 });
7535 }
7536
7537 #[test]
7538 fn existing_stream_not_reassigned() {
7539 TOKIO_SHARED_RT.block_on(async {
7540 let ws = create_websocket_streams(None, None, None);
7541 ws.clone().subscribe(vec!["s1".into()], None, None).await;
7542 let first_id = {
7543 let map = ws.connection_streams.lock().await;
7544 map.get("s1").unwrap().id.clone()
7545 };
7546 ws.clone()
7547 .subscribe(vec!["s1".into(), "s2".into()], None, None)
7548 .await;
7549 let map = ws.connection_streams.lock().await;
7550 let second_id = map.get("s1").unwrap().id.clone();
7551 assert_eq!(first_id, second_id);
7552 assert!(map.contains_key("s2"));
7553 });
7554 }
7555
7556 #[test]
7557 fn queue_when_not_ready_with_url_path() {
7558 TOKIO_SHARED_RT.block_on(async {
7559 let ws = create_websocket_streams(None, None, None);
7560
7561 let conn = ws.common.connection_pool[0].clone();
7562 {
7563 let mut st = conn.state.lock().await;
7564 st.ws_write_tx = None;
7565 st.url_path = Some("path1".to_string());
7566 st.reconnection_pending = false;
7567 st.close_initiated = false;
7568 }
7569
7570 ws.clone()
7571 .subscribe(vec!["s1".into()], None, Some("path1"))
7572 .await;
7573
7574 let state = conn.state.lock().await;
7575 let pending: Vec<String> =
7576 state.pending_subscriptions.iter().cloned().collect();
7577 assert_eq!(pending, vec!["s1".to_string()]);
7578 });
7579 }
7580
7581 #[test]
7582 fn only_one_subscription_per_stream_per_url_path() {
7583 TOKIO_SHARED_RT.block_on(async {
7584 let ws = create_websocket_streams(None, None, None);
7585
7586 let conn = ws.common.connection_pool[0].clone();
7587 {
7588 let mut st = conn.state.lock().await;
7589 st.ws_write_tx = None;
7590 st.url_path = Some("path1".to_string());
7591 st.reconnection_pending = false;
7592 st.close_initiated = false;
7593 }
7594
7595 ws.clone()
7596 .subscribe(vec!["s1".into()], None, Some("path1"))
7597 .await;
7598 ws.clone()
7599 .subscribe(vec!["s1".into()], None, Some("path1"))
7600 .await;
7601
7602 let state = conn.state.lock().await;
7603 let pending: Vec<String> =
7604 state.pending_subscriptions.iter().cloned().collect();
7605 assert_eq!(pending, vec!["s1".to_string()]);
7606 });
7607 }
7608
7609 #[test]
7610 fn same_stream_can_be_subscribed_on_different_url_paths() {
7611 TOKIO_SHARED_RT.block_on(async {
7612 let ws = create_websocket_streams(None, None, None);
7613
7614 let conn1 = ws.common.connection_pool[0].clone();
7615 let conn2 = ws.common.connection_pool[1].clone();
7616
7617 {
7618 let mut st1 = conn1.state.lock().await;
7619 st1.ws_write_tx = None;
7620 st1.url_path = Some("path1".to_string());
7621 st1.reconnection_pending = false;
7622 st1.close_initiated = false;
7623 }
7624 {
7625 let mut st2 = conn2.state.lock().await;
7626 st2.ws_write_tx = None;
7627 st2.url_path = Some("path2".to_string());
7628 st2.reconnection_pending = false;
7629 st2.close_initiated = false;
7630 }
7631
7632 ws.clone()
7633 .subscribe(vec!["s1".into()], None, Some("path1"))
7634 .await;
7635 ws.clone()
7636 .subscribe(vec!["s1".into()], None, Some("path2"))
7637 .await;
7638
7639 let map = ws.connection_streams.lock().await;
7640 assert!(map.contains_key("path1::s1"));
7641 assert!(map.contains_key("path2::s1"));
7642 });
7643 }
7644 }
7645
7646 mod unsubscribe {
7647 use super::*;
7648
7649 #[test]
7650 fn removes_stream_with_no_callbacks() {
7651 TOKIO_SHARED_RT.block_on(async {
7652 let ws = create_websocket_streams(None, None, None);
7653 let conn = ws.common.connection_pool[0].clone();
7654
7655 {
7656 let (tx, _rx) = unbounded_channel::<Message>();
7657 let mut st = conn.state.lock().await;
7658 st.ws_write_tx = Some(tx);
7659 }
7660
7661 {
7662 let mut map = ws.connection_streams.lock().await;
7663 map.insert("s1".to_string(), conn.clone());
7664 }
7665 {
7666 let mut st = conn.state.lock().await;
7667 st.stream_callbacks.insert("s1".to_string(), Vec::new());
7668 }
7669
7670 ws.unsubscribe(vec!["s1".to_string()], None, None).await;
7671
7672 assert!(!ws.connection_streams.lock().await.contains_key("s1"));
7673 assert!(!conn.state.lock().await.stream_callbacks.contains_key("s1"));
7674 });
7675 }
7676
7677 #[test]
7678 fn preserves_stream_with_callbacks() {
7679 TOKIO_SHARED_RT.block_on(async {
7680 let ws = create_websocket_streams(None, None, None);
7681 let conn = ws.common.connection_pool[1].clone();
7682
7683 {
7684 let mut map = ws.connection_streams.lock().await;
7685 map.insert("s2".to_string(), conn.clone());
7686 }
7687 {
7688 let mut state = conn.state.lock().await;
7689 state
7690 .stream_callbacks
7691 .insert("s2".to_string(), vec![Arc::new(|_: &Value| {})]);
7692 }
7693
7694 ws.unsubscribe(vec!["s2".to_string()], None, None).await;
7695
7696 assert!(ws.connection_streams.lock().await.contains_key("s2"));
7697 assert!(conn.state.lock().await.stream_callbacks.contains_key("s2"));
7698 });
7699 }
7700
7701 #[test]
7702 fn does_not_send_if_callbacks_exist() {
7703 TOKIO_SHARED_RT.block_on(async {
7704 let ws = create_websocket_streams(None, None, None);
7705 let conn = ws.common.connection_pool[0].clone();
7706 {
7707 let mut map = ws.connection_streams.lock().await;
7708 map.insert("s1".to_string(), conn.clone());
7709 }
7710 {
7711 let mut state = conn.state.lock().await;
7712 state.stream_callbacks.insert(
7713 "s1".to_string(),
7714 vec![Arc::new(|_: &Value| {}), Arc::new(|_: &Value| {})],
7715 );
7716 }
7717 ws.unsubscribe(vec!["s1".into()], None, None).await;
7718 assert!(ws.connection_streams.lock().await.contains_key("s1"));
7719 assert!(conn.state.lock().await.stream_callbacks.contains_key("s1"));
7720 });
7721 }
7722
7723 #[test]
7724 fn warns_if_not_associated() {
7725 TOKIO_SHARED_RT.block_on(async {
7726 let ws = create_websocket_streams(None, None, None);
7727 ws.unsubscribe(vec!["nope".into()], None, None).await;
7728 });
7729 }
7730
7731 #[test]
7732 fn empty_list_does_nothing() {
7733 TOKIO_SHARED_RT.block_on(async {
7734 let ws = create_websocket_streams(None, None, None);
7735 let before = ws.connection_streams.lock().await.len();
7736 ws.unsubscribe(Vec::<String>::new(), None, None).await;
7737 let after = ws.connection_streams.lock().await.len();
7738 assert_eq!(before, after);
7739 });
7740 }
7741
7742 #[test]
7743 fn invalid_custom_id_falls_back() {
7744 TOKIO_SHARED_RT.block_on(async {
7745 let ws = create_websocket_streams(None, None, None);
7746 let conn = ws.common.connection_pool[0].clone();
7747 {
7748 let mut map = ws.connection_streams.lock().await;
7749 map.insert("foo".to_string(), conn.clone());
7750 }
7751 {
7752 let mut state = conn.state.lock().await;
7753 let (tx, _rx) = unbounded_channel();
7754 state.ws_write_tx = Some(tx);
7755 state.stream_callbacks.insert("foo".to_string(), Vec::new());
7756 }
7757 ws.unsubscribe(
7758 vec!["foo".into()],
7759 Some(StreamId::Str("bad-id".into())),
7760 None,
7761 )
7762 .await;
7763 assert!(!ws.connection_streams.lock().await.contains_key("foo"));
7764 });
7765 }
7766
7767 #[test]
7768 fn removes_even_without_write_channel() {
7769 TOKIO_SHARED_RT.block_on(async {
7770 let ws = create_websocket_streams(None, None, None);
7771 let conn = ws.common.connection_pool[0].clone();
7772 {
7773 let mut map = ws.connection_streams.lock().await;
7774 map.insert("x".to_string(), conn.clone());
7775 }
7776 {
7777 let mut state = conn.state.lock().await;
7778 let (tx, _rx) = unbounded_channel();
7779 state.ws_write_tx = Some(tx);
7780 state.stream_callbacks.insert("x".to_string(), Vec::new());
7781 }
7782 ws.unsubscribe(vec!["x".into()], None, None).await;
7783 assert!(!ws.connection_streams.lock().await.contains_key("x"));
7784 });
7785 }
7786
7787 #[test]
7788 fn removes_stream_with_no_callbacks_with_url_path() {
7789 TOKIO_SHARED_RT.block_on(async {
7790 let ws = create_websocket_streams(None, None, None);
7791 let conn = ws.common.connection_pool[0].clone();
7792
7793 {
7794 let (tx, _rx) = unbounded_channel::<Message>();
7795 let mut st = conn.state.lock().await;
7796 st.ws_write_tx = Some(tx);
7797 st.url_path = Some("path1".to_string());
7798 }
7799
7800 {
7801 let mut map = ws.connection_streams.lock().await;
7802 map.insert("path1::s1".to_string(), conn.clone());
7803 }
7804 {
7805 let mut st = conn.state.lock().await;
7806 st.stream_callbacks
7807 .insert("path1::s1".to_string(), Vec::new());
7808 }
7809
7810 ws.unsubscribe(vec!["s1".to_string()], None, Some("path1"))
7811 .await;
7812
7813 assert!(!ws.connection_streams.lock().await.contains_key("path1::s1"));
7814 assert!(
7815 !conn
7816 .state
7817 .lock()
7818 .await
7819 .stream_callbacks
7820 .contains_key("path1::s1")
7821 );
7822 });
7823 }
7824
7825 #[test]
7826 fn preserves_stream_with_callbacks_with_url_path() {
7827 TOKIO_SHARED_RT.block_on(async {
7828 let ws = create_websocket_streams(None, None, None);
7829 let conn = ws.common.connection_pool[0].clone();
7830
7831 {
7832 let (tx, _rx) = unbounded_channel::<Message>();
7833 let mut st = conn.state.lock().await;
7834 st.ws_write_tx = Some(tx);
7835 st.url_path = Some("path1".to_string());
7836 }
7837
7838 {
7839 let mut map = ws.connection_streams.lock().await;
7840 map.insert("path1::s2".to_string(), conn.clone());
7841 }
7842 {
7843 let mut state = conn.state.lock().await;
7844 state
7845 .stream_callbacks
7846 .insert("path1::s2".to_string(), vec![Arc::new(|_: &Value| {})]);
7847 }
7848
7849 ws.unsubscribe(vec!["s2".to_string()], None, Some("path1"))
7850 .await;
7851
7852 assert!(ws.connection_streams.lock().await.contains_key("path1::s2"));
7853 assert!(
7854 conn.state
7855 .lock()
7856 .await
7857 .stream_callbacks
7858 .contains_key("path1::s2")
7859 );
7860 });
7861 }
7862
7863 #[test]
7864 fn url_path_mismatch_does_not_remove_other_path_subscription() {
7865 TOKIO_SHARED_RT.block_on(async {
7866 let ws = create_websocket_streams(None, None, None);
7867 let conn = ws.common.connection_pool[0].clone();
7868
7869 {
7870 let (tx, _rx) = unbounded_channel::<Message>();
7871 let mut st = conn.state.lock().await;
7872 st.ws_write_tx = Some(tx);
7873 st.url_path = Some("path1".to_string());
7874 }
7875
7876 {
7877 let mut map = ws.connection_streams.lock().await;
7878 map.insert("path1::s1".to_string(), conn.clone());
7879 }
7880 {
7881 let mut st = conn.state.lock().await;
7882 st.stream_callbacks
7883 .insert("path1::s1".to_string(), Vec::new());
7884 }
7885
7886 ws.unsubscribe(vec!["s1".to_string()], None, Some("path2"))
7887 .await;
7888
7889 assert!(ws.connection_streams.lock().await.contains_key("path1::s1"));
7890 assert!(
7891 conn.state
7892 .lock()
7893 .await
7894 .stream_callbacks
7895 .contains_key("path1::s1")
7896 );
7897 });
7898 }
7899 }
7900
7901 mod is_subscribed {
7902 use super::*;
7903
7904 #[test]
7905 fn returns_false_when_not_subscribed() {
7906 TOKIO_SHARED_RT.block_on(async {
7907 let ws = create_websocket_streams(None, None, None);
7908 assert!(!ws.is_subscribed("unknown").await);
7909 });
7910 }
7911
7912 #[test]
7913 fn returns_true_when_subscribed() {
7914 TOKIO_SHARED_RT.block_on(async {
7915 let ws = create_websocket_streams(None, None, None);
7916 let conn = ws.common.connection_pool[0].clone();
7917 {
7918 let mut map = ws.connection_streams.lock().await;
7919 map.insert("stream1".to_string(), conn);
7920 }
7921 assert!(ws.is_subscribed("stream1").await);
7922 });
7923 }
7924
7925 #[test]
7926 fn returns_true_when_subscribed_with_url_path_key() {
7927 TOKIO_SHARED_RT.block_on(async {
7928 let ws = create_websocket_streams(None, None, None);
7929 let conn = ws.common.connection_pool[0].clone();
7930 {
7931 let mut map = ws.connection_streams.lock().await;
7932 map.insert("path1::stream1".to_string(), conn);
7933 }
7934 assert!(ws.is_subscribed("stream1").await);
7935 });
7936 }
7937
7938 #[test]
7939 fn returns_true_when_same_stream_subscribed_on_multiple_paths() {
7940 TOKIO_SHARED_RT.block_on(async {
7941 let ws = create_websocket_streams(None, None, None);
7942 let conn1 = ws.common.connection_pool[0].clone();
7943 let conn2 = ws.common.connection_pool[1].clone();
7944 {
7945 let mut map = ws.connection_streams.lock().await;
7946 map.insert("path1::stream1".to_string(), conn1);
7947 map.insert("path2::stream1".to_string(), conn2);
7948 }
7949 assert!(ws.is_subscribed("stream1").await);
7950 });
7951 }
7952
7953 #[test]
7954 fn returns_false_when_only_similar_suffix_exists() {
7955 TOKIO_SHARED_RT.block_on(async {
7956 let ws = create_websocket_streams(None, None, None);
7957 let conn = ws.common.connection_pool[0].clone();
7958 {
7959 let mut map = ws.connection_streams.lock().await;
7960 map.insert("path1::stream10".to_string(), conn);
7961 }
7962 assert!(!ws.is_subscribed("stream1").await);
7963 });
7964 }
7965 }
7966
7967 mod stream_key {
7968 use super::*;
7969
7970 #[test]
7971 fn stream_key_without_url_path_returns_stream() {
7972 TOKIO_SHARED_RT.block_on(async {
7973 let ws = create_websocket_streams(None, None, None);
7974 assert_eq!(ws.stream_key("s1", None), "s1");
7975 });
7976 }
7977
7978 #[test]
7979 fn stream_key_with_empty_url_path_returns_stream() {
7980 TOKIO_SHARED_RT.block_on(async {
7981 let ws = create_websocket_streams(None, None, None);
7982 assert_eq!(ws.stream_key("s1", Some("")), "s1");
7983 });
7984 }
7985
7986 #[test]
7987 fn stream_key_with_url_path_prefixes_stream() {
7988 TOKIO_SHARED_RT.block_on(async {
7989 let ws = create_websocket_streams(None, None, None);
7990 assert_eq!(ws.stream_key("s1", Some("path1")), "path1::s1");
7991 });
7992 }
7993
7994 #[test]
7995 fn stream_key_distinguishes_paths() {
7996 TOKIO_SHARED_RT.block_on(async {
7997 let ws = create_websocket_streams(None, None, None);
7998 assert_eq!(ws.stream_key("s1", Some("path1")), "path1::s1");
7999 assert_eq!(ws.stream_key("s1", Some("path2")), "path2::s1");
8000 });
8001 }
8002 }
8003
8004 mod prepare_url {
8005 use super::*;
8006
8007 #[test]
8008 fn without_time_unit_returns_base_url() {
8009 TOKIO_SHARED_RT.block_on(async {
8010 let conns = vec![
8011 WebsocketConnection::new("c1"),
8012 WebsocketConnection::new("c2"),
8013 ];
8014 let config = ConfigurationWebsocketStreams {
8015 ws_url: Some("wss://example".to_string()),
8016 mode: WebsocketMode::Single,
8017 reconnect_delay: 100,
8018 time_unit: None,
8019 agent: None,
8020 handshake: None,
8021 user_agent: build_user_agent("product"),
8022 };
8023 let ws = WebsocketStreams::new(config, conns, vec![]);
8024 let url = ws.prepare_url(&["s1".into(), "s2".into()], None);
8025 assert_eq!(url, "wss://example/stream?streams=s1/s2");
8026 });
8027 }
8028
8029 #[test]
8030 fn with_time_unit_appends_parameter() {
8031 TOKIO_SHARED_RT.block_on(async {
8032 let conns = vec![WebsocketConnection::new("c1")];
8033 let config = ConfigurationWebsocketStreams {
8034 ws_url: Some("wss://example".to_string()),
8035 mode: WebsocketMode::Single,
8036 reconnect_delay: 100,
8037 time_unit: Some(TimeUnit::Millisecond),
8038 agent: None,
8039 handshake: None,
8040 user_agent: build_user_agent("product"),
8041 };
8042 let ws = WebsocketStreams::new(config, conns, vec![]);
8043 let url = ws.prepare_url(&["a".into()], None);
8044 assert_eq!(url, "wss://example/stream?streams=a&timeUnit=millisecond");
8045 });
8046 }
8047
8048 #[test]
8049 fn multiple_streams_and_time_unit() {
8050 TOKIO_SHARED_RT.block_on(async {
8051 let conns = vec![WebsocketConnection::new("c1")];
8052 let config = ConfigurationWebsocketStreams {
8053 ws_url: Some("wss://example".to_string()),
8054 mode: WebsocketMode::Single,
8055 reconnect_delay: 100,
8056 time_unit: Some(TimeUnit::Microsecond),
8057 agent: None,
8058 handshake: None,
8059 user_agent: build_user_agent("product"),
8060 };
8061 let ws = WebsocketStreams::new(config, conns, vec![]);
8062 let url = ws.prepare_url(&["x".into(), "y".into(), "z".into()], None);
8063 assert_eq!(
8064 url,
8065 "wss://example/stream?streams=x/y/z&timeUnit=microsecond"
8066 );
8067 });
8068 }
8069
8070 #[test]
8071 fn with_url_path_prefixes_base_url() {
8072 TOKIO_SHARED_RT.block_on(async {
8073 let conns = vec![WebsocketConnection::new("c1")];
8074 let config = ConfigurationWebsocketStreams {
8075 ws_url: Some("wss://example".to_string()),
8076 mode: WebsocketMode::Single,
8077 reconnect_delay: 100,
8078 time_unit: None,
8079 agent: None,
8080 handshake: None,
8081 user_agent: build_user_agent("product"),
8082 };
8083 let ws = WebsocketStreams::new(config, conns, vec![]);
8084 let url = ws.prepare_url(["s1".into()].as_ref(), Some("path1"));
8085 assert_eq!(url, "wss://example/path1/stream?streams=s1");
8086 });
8087 }
8088
8089 #[test]
8090 fn with_url_path_and_time_unit_appends_parameter() {
8091 TOKIO_SHARED_RT.block_on(async {
8092 let conns = vec![WebsocketConnection::new("c1")];
8093 let config = ConfigurationWebsocketStreams {
8094 ws_url: Some("wss://example".to_string()),
8095 mode: WebsocketMode::Single,
8096 reconnect_delay: 100,
8097 time_unit: Some(TimeUnit::Millisecond),
8098 agent: None,
8099 handshake: None,
8100 user_agent: build_user_agent("product"),
8101 };
8102 let ws = WebsocketStreams::new(config, conns, vec![]);
8103 let url = ws.prepare_url(["a".into()].as_ref(), Some("path1"));
8104 assert_eq!(
8105 url,
8106 "wss://example/path1/stream?streams=a&timeUnit=millisecond"
8107 );
8108 });
8109 }
8110
8111 #[test]
8112 fn url_path_distinguishes_urls_for_same_streams() {
8113 TOKIO_SHARED_RT.block_on(async {
8114 let conns = vec![WebsocketConnection::new("c1")];
8115 let config = ConfigurationWebsocketStreams {
8116 ws_url: Some("wss://example".to_string()),
8117 mode: WebsocketMode::Single,
8118 reconnect_delay: 100,
8119 time_unit: None,
8120 agent: None,
8121 handshake: None,
8122 user_agent: build_user_agent("product"),
8123 };
8124 let ws = WebsocketStreams::new(config, conns, vec![]);
8125 let u1 = ws.prepare_url(["s1".into()].as_ref(), Some("path1"));
8126 let u2 = ws.prepare_url(["s1".into()].as_ref(), Some("path2"));
8127 assert_eq!(u1, "wss://example/path1/stream?streams=s1");
8128 assert_eq!(u2, "wss://example/path2/stream?streams=s1");
8129 });
8130 }
8131 }
8132
8133 mod handle_stream_assignment {
8134 use super::*;
8135
8136 #[test]
8137 fn assigns_new_streams_to_connections() {
8138 TOKIO_SHARED_RT.block_on(async {
8139 let ws = create_websocket_streams(None, None, None);
8140 let groups = ws
8141 .clone()
8142 .handle_stream_assignment(vec!["s1".into(), "s2".into()], None)
8143 .await;
8144 let mut seen_streams = HashSet::new();
8145 for (_conn, streams) in &groups {
8146 for s in streams {
8147 seen_streams.insert(s);
8148 }
8149 }
8150 assert_eq!(
8151 seen_streams,
8152 ["s1".to_string(), "s2".to_string()].iter().collect()
8153 );
8154 assert_eq!(groups.len(), 1);
8155 });
8156 }
8157
8158 #[test]
8159 fn reuses_existing_connection_for_duplicate_stream() {
8160 TOKIO_SHARED_RT.block_on(async {
8161 let ws = create_websocket_streams(None, None, None);
8162 let _ = ws
8163 .clone()
8164 .handle_stream_assignment(vec!["s1".into()], None)
8165 .await;
8166 let groups = ws
8167 .clone()
8168 .handle_stream_assignment(vec!["s1".into(), "s3".into()], None)
8169 .await;
8170 let mut all_streams = Vec::new();
8171 for (_conn, streams) in groups {
8172 all_streams.extend(streams);
8173 }
8174 all_streams.sort();
8175 assert_eq!(all_streams, vec!["s1".to_string(), "s3".to_string()]);
8176 });
8177 }
8178
8179 #[test]
8180 fn empty_stream_list_returns_empty() {
8181 TOKIO_SHARED_RT.block_on(async {
8182 let ws = create_websocket_streams(None, None, None);
8183 let groups = ws.clone().handle_stream_assignment(vec![], None).await;
8184 assert!(groups.is_empty());
8185 });
8186 }
8187
8188 #[test]
8189 fn closed_or_reconnecting_forces_reassignment_of_stream() {
8190 TOKIO_SHARED_RT.block_on(async {
8191 let ws = create_websocket_streams(None, None, None);
8192 let mut groups = ws
8193 .clone()
8194 .handle_stream_assignment(vec!["s1".into()], None)
8195 .await;
8196 let (conn, _) = groups.pop().unwrap();
8197 {
8198 let mut st = conn.state.lock().await;
8199 st.close_initiated = true;
8200 }
8201 let groups2 = ws
8202 .clone()
8203 .handle_stream_assignment(vec!["s2".into()], None)
8204 .await;
8205 assert_eq!(groups2.len(), 1);
8206 let (_new_conn, streams) = &groups2[0];
8207 assert_eq!(streams, &vec!["s2".to_string()]);
8208 });
8209 }
8210
8211 #[test]
8212 fn no_available_connections_falls_back_to_one() {
8213 TOKIO_SHARED_RT.block_on(async {
8214 let ws = create_websocket_streams(None, Some(vec![]), None);
8215 let assigned = ws.handle_stream_assignment(vec!["foo".into()], None).await;
8216 assert_eq!(assigned.len(), 1);
8217 let (_conn, streams) = &assigned[0];
8218 assert_eq!(streams.as_slice(), &["foo".to_string()]);
8219 });
8220 }
8221
8222 #[test]
8223 fn single_connection_groups_multiple_streams() {
8224 TOKIO_SHARED_RT.block_on(async {
8225 let conn = WebsocketConnection::new("c1");
8226 let ws = create_websocket_streams(None, Some(vec![conn.clone()]), None);
8227 let assigned = ws
8228 .handle_stream_assignment(vec!["s1".into(), "s2".into()], None)
8229 .await;
8230 assert_eq!(assigned.len(), 1);
8231 let (assigned_conn, streams) = &assigned[0];
8232 assert!(Arc::ptr_eq(assigned_conn, &conn));
8233 assert_eq!(streams.len(), 2);
8234 assert!(streams.contains(&"s1".to_string()));
8235 assert!(streams.contains(&"s2".to_string()));
8236 });
8237 }
8238
8239 #[test]
8240 fn reuse_existing_healthy_connection() {
8241 TOKIO_SHARED_RT.block_on(async {
8242 let conn = WebsocketConnection::new("c");
8243 let ws = create_websocket_streams(None, Some(vec![conn.clone()]), None);
8244 let _ = ws.handle_stream_assignment(vec!["s1".into()], None).await;
8245 let second = ws.handle_stream_assignment(vec!["s1".into()], None).await;
8246 assert_eq!(second.len(), 1);
8247 let (assigned_conn, streams) = &second[0];
8248 assert!(Arc::ptr_eq(assigned_conn, &conn));
8249 assert_eq!(streams.as_slice(), &["s1".to_string()]);
8250 });
8251 }
8252
8253 #[test]
8254 fn mix_new_and_assigned_streams() {
8255 TOKIO_SHARED_RT.block_on(async {
8256 let conn = WebsocketConnection::new("c");
8257 let ws = create_websocket_streams(None, Some(vec![conn.clone()]), None);
8258 let _ = ws
8259 .handle_stream_assignment(vec!["s1".into(), "s2".into()], None)
8260 .await;
8261 let mixed = ws
8262 .handle_stream_assignment(vec!["s2".into(), "s3".into()], None)
8263 .await;
8264 assert_eq!(mixed.len(), 1);
8265 let (assigned_conn, streams) = &mixed[0];
8266 assert!(Arc::ptr_eq(assigned_conn, &conn));
8267 let mut got = streams.clone();
8268 got.sort();
8269 assert_eq!(got, vec!["s2".to_string(), "s3".to_string()]);
8270 });
8271 }
8272
8273 #[test]
8274 fn assigns_streams_with_url_path_keys() {
8275 TOKIO_SHARED_RT.block_on(async {
8276 let ws = create_websocket_streams(None, None, None);
8277
8278 let conn = ws.common.connection_pool[0].clone();
8279 {
8280 let mut st = conn.state.lock().await;
8281 st.url_path = Some("path1".to_string());
8282 st.ws_write_tx = None;
8283 st.reconnection_pending = false;
8284 st.close_initiated = false;
8285 }
8286
8287 let groups = ws
8288 .handle_stream_assignment(vec!["s1".into(), "s2".into()], Some("path1"))
8289 .await;
8290
8291 let map = ws.connection_streams.lock().await;
8292 assert!(map.contains_key("path1::s1"));
8293 assert!(map.contains_key("path1::s2"));
8294 assert_eq!(groups.len(), 1);
8295
8296 let (_assigned_conn, streams) = &groups[0];
8297 let mut got = streams.clone();
8298 got.sort();
8299 assert_eq!(got, vec!["s1".to_string(), "s2".to_string()]);
8300 });
8301 }
8302
8303 #[test]
8304 fn same_stream_on_different_paths_creates_distinct_keys() {
8305 TOKIO_SHARED_RT.block_on(async {
8306 let ws = create_websocket_streams(None, None, None);
8307
8308 let conn1 = ws.common.connection_pool[0].clone();
8309 let conn2 = ws.common.connection_pool[1].clone();
8310
8311 {
8312 let mut st = conn1.state.lock().await;
8313 st.url_path = Some("path1".to_string());
8314 st.ws_write_tx = None;
8315 st.reconnection_pending = false;
8316 st.close_initiated = false;
8317 }
8318 {
8319 let mut st = conn2.state.lock().await;
8320 st.url_path = Some("path2".to_string());
8321 st.ws_write_tx = None;
8322 st.reconnection_pending = false;
8323 st.close_initiated = false;
8324 }
8325
8326 let g1 = ws
8327 .handle_stream_assignment(vec!["s1".into()], Some("path1"))
8328 .await;
8329 let g2 = ws
8330 .handle_stream_assignment(vec!["s1".into()], Some("path2"))
8331 .await;
8332
8333 assert_eq!(g1.len(), 1);
8334 assert_eq!(g2.len(), 1);
8335
8336 let map = ws.connection_streams.lock().await;
8337 assert!(map.contains_key("path1::s1"));
8338 assert!(map.contains_key("path2::s1"));
8339 });
8340 }
8341
8342 #[test]
8343 fn reuses_existing_connection_for_same_path_and_stream() {
8344 TOKIO_SHARED_RT.block_on(async {
8345 let ws = create_websocket_streams(None, None, None);
8346
8347 let conn = ws.common.connection_pool[0].clone();
8348 {
8349 let mut st = conn.state.lock().await;
8350 st.url_path = Some("path1".to_string());
8351 st.ws_write_tx = None;
8352 st.reconnection_pending = false;
8353 st.close_initiated = false;
8354 }
8355
8356 let first = ws
8357 .handle_stream_assignment(vec!["s1".into()], Some("path1"))
8358 .await;
8359 let second = ws
8360 .handle_stream_assignment(vec!["s1".into(), "s2".into()], Some("path1"))
8361 .await;
8362
8363 assert_eq!(first.len(), 1);
8364 assert_eq!(second.len(), 1);
8365
8366 let map = ws.connection_streams.lock().await;
8367 let c1 = map.get("path1::s1").unwrap().clone();
8368 let c2 = map.get("path1::s2").unwrap().clone();
8369 assert!(Arc::ptr_eq(&c1, &c2));
8370 });
8371 }
8372
8373 #[test]
8374 fn closed_or_reconnecting_forces_reassignment_with_url_path() {
8375 TOKIO_SHARED_RT.block_on(async {
8376 let ws = create_websocket_streams(None, None, None);
8377
8378 let conn1 = ws.common.connection_pool[0].clone();
8379 let conn2 = ws.common.connection_pool[1].clone();
8380
8381 {
8382 let mut st = conn1.state.lock().await;
8383 st.url_path = Some("path1".to_string());
8384 st.ws_write_tx = None;
8385 st.reconnection_pending = false;
8386 st.close_initiated = false;
8387 }
8388 {
8389 let mut st = conn2.state.lock().await;
8390 st.url_path = Some("path1".to_string());
8391 st.ws_write_tx = None;
8392 st.reconnection_pending = false;
8393 st.close_initiated = false;
8394 }
8395
8396 let _ = ws
8397 .handle_stream_assignment(vec!["s1".into()], Some("path1"))
8398 .await;
8399
8400 {
8401 let mut st = conn1.state.lock().await;
8402 st.close_initiated = true;
8403 }
8404
8405 let _ = ws
8406 .handle_stream_assignment(vec!["s1".into()], Some("path1"))
8407 .await;
8408
8409 let map = ws.connection_streams.lock().await;
8410 let assigned = map.get("path1::s1").unwrap().clone();
8411 assert!(!Arc::ptr_eq(&assigned, &conn1));
8412 });
8413 }
8414 }
8415
8416 mod send_subscription_payload {
8417 use super::*;
8418
8419 #[test]
8420 fn subscribe_payload_with_custom_id_fallbacks_if_invalid() {
8421 TOKIO_SHARED_RT.block_on(async {
8422 let ws: Arc<WebsocketStreams> =
8423 create_websocket_streams(Some("ws://example.com"), None, None);
8424 let conn = &ws.common.connection_pool[0];
8425 let (tx, mut rx) = unbounded_channel();
8426 {
8427 let mut st = conn.state.lock().await;
8428 st.ws_write_tx = Some(tx);
8429 }
8430 let id = Some("badid".to_string());
8431 ws.send_subscription_payload(
8432 conn,
8433 &vec!["s1".to_string()],
8434 id.map(StreamId::from),
8435 );
8436 let msg = rx.recv().await.expect("no message sent");
8437 if let Message::Text(txt) = msg {
8438 let v: serde_json::Value = serde_json::from_str(&txt).unwrap();
8439 assert_eq!(v["method"], "SUBSCRIBE");
8440 let id = v["id"].as_str().unwrap();
8441 assert_ne!(id, "badid");
8442 assert!(Regex::new(r"^[0-9a-fA-F]{32}$").unwrap().is_match(id));
8443 } else {
8444 panic!("unexpected message: {msg:?}");
8445 }
8446 });
8447 }
8448
8449 #[test]
8450 fn subscribe_payload_with_and_without_custom_string_id() {
8451 TOKIO_SHARED_RT.block_on(async {
8452 let ws: Arc<WebsocketStreams> =
8453 create_websocket_streams(Some("ws://unused"), None, None);
8454 let conn = &ws.common.connection_pool[0];
8455 let (tx, mut rx) = unbounded_channel();
8456 {
8457 let mut st = conn.state.lock().await;
8458 st.ws_write_tx = Some(tx);
8459 }
8460 let id = Some("deadbeefdeadbeefdeadbeefdeadbeef".to_string());
8461 ws.send_subscription_payload(
8462 conn,
8463 &vec!["a".to_string(), "b".to_string()],
8464 id.map(StreamId::from),
8465 );
8466 let msg1 = rx.recv().await.unwrap();
8467 ws.send_subscription_payload(conn, &vec!["x".to_string()], None);
8468 let msg2 = rx.recv().await.unwrap();
8469
8470 if let Message::Text(txt1) = msg1 {
8471 let v1: serde_json::Value = serde_json::from_str(&txt1).unwrap();
8472 assert_eq!(v1["id"], "deadbeefdeadbeefdeadbeefdeadbeef");
8473 assert_eq!(
8474 v1["params"].as_array().unwrap(),
8475 &vec![serde_json::json!("a"), serde_json::json!("b")]
8476 );
8477 } else {
8478 panic!()
8479 }
8480
8481 if let Message::Text(txt2) = msg2 {
8482 let v2: serde_json::Value = serde_json::from_str(&txt2).unwrap();
8483 assert_eq!(v2["method"], "SUBSCRIBE");
8484 let params = v2["params"].as_array().unwrap();
8485 assert_eq!(params.len(), 1);
8486 assert_eq!(params[0], "x");
8487 let id2 = v2["id"].as_str().unwrap();
8488 assert!(Regex::new(r"^[0-9a-fA-F]{32}$").unwrap().is_match(id2));
8489 } else {
8490 panic!()
8491 }
8492 });
8493 }
8494
8495 #[test]
8496 fn subscribe_payload_with_and_without_custom_integer_id() {
8497 TOKIO_SHARED_RT.block_on(async {
8498 let ws: Arc<WebsocketStreams> =
8499 create_websocket_streams(Some("ws://unused"), None, None);
8500 ws.stream_id_is_strictly_number
8501 .store(true, Ordering::Relaxed);
8502 let conn = &ws.common.connection_pool[0];
8503 let (tx, mut rx) = unbounded_channel();
8504 {
8505 let mut st = conn.state.lock().await;
8506 st.ws_write_tx = Some(tx);
8507 }
8508
8509 let id = Some(123u32);
8510
8511 ws.send_subscription_payload(
8512 conn,
8513 &vec!["a".to_string(), "b".to_string()],
8514 id.map(StreamId::from),
8515 );
8516 let msg1 = rx.recv().await.unwrap();
8517
8518 ws.send_subscription_payload(conn, &vec!["x".to_string()], None);
8519 let msg2 = rx.recv().await.unwrap();
8520
8521 if let Message::Text(txt1) = msg1 {
8522 let v1: serde_json::Value = serde_json::from_str(&txt1).unwrap();
8523 assert_eq!(v1["method"], "SUBSCRIBE");
8524 assert_eq!(v1["id"].as_u64(), Some(123));
8525 assert_eq!(
8526 v1["params"].as_array().unwrap(),
8527 &vec![serde_json::json!("a"), serde_json::json!("b")]
8528 );
8529 } else {
8530 panic!("Expected Message::Text for msg1");
8531 }
8532
8533 if let Message::Text(txt2) = msg2 {
8534 let v2: serde_json::Value = serde_json::from_str(&txt2).unwrap();
8535 assert_eq!(v2["method"], "SUBSCRIBE");
8536
8537 let params = v2["params"].as_array().unwrap();
8538 assert_eq!(params.len(), 1);
8539 assert_eq!(params[0], "x");
8540
8541 let id2 = v2.get("id").expect("payload should contain id");
8542 assert!(
8543 id2.is_number(),
8544 "expected numeric id in strict-number mode, got: {id2:?}"
8545 );
8546 let n = id2.as_u64().unwrap();
8547 assert!(u32::try_from(n).is_ok(), "id should fit u32, got {n}");
8548 } else {
8549 panic!("Expected Message::Text for msg2");
8550 }
8551 });
8552 }
8553 }
8554
8555 mod on_open {
8556 use super::*;
8557
8558 #[test]
8559 fn sends_pending_subscriptions() {
8560 TOKIO_SHARED_RT.block_on(async {
8561 let ws: Arc<WebsocketStreams> =
8562 create_websocket_streams(Some("ws://example.com"), None, None);
8563 let conn = &ws.common.connection_pool[0];
8564 let (tx, mut rx) = unbounded_channel();
8565 {
8566 let mut st = conn.state.lock().await;
8567 st.ws_write_tx = Some(tx);
8568 st.pending_subscriptions.push_back("foo".to_string());
8569 st.pending_subscriptions.push_back("bar".to_string());
8570 }
8571 ws.on_open("ws://example.com".to_string(), conn.clone())
8572 .await;
8573 let msg = rx.recv().await.expect("no subscription sent");
8574 if let Message::Text(txt) = msg {
8575 let v: Value = serde_json::from_str(&txt).unwrap();
8576 assert_eq!(v["method"], "SUBSCRIBE");
8577 let params = v["params"].as_array().unwrap();
8578 assert_eq!(
8579 params,
8580 &vec![Value::String("foo".into()), Value::String("bar".into())]
8581 );
8582 } else {
8583 panic!("unexpected message: {msg:?}");
8584 }
8585 let st_after = conn.state.lock().await;
8586 assert!(st_after.pending_subscriptions.is_empty());
8587 });
8588 }
8589
8590 #[test]
8591 fn with_no_pending_subscriptions_sends_nothing() {
8592 TOKIO_SHARED_RT.block_on(async {
8593 let ws: Arc<WebsocketStreams> =
8594 create_websocket_streams(Some("ws://example.com"), None, None);
8595 let conn = &ws.common.connection_pool[0];
8596 let (tx, mut rx) = unbounded_channel();
8597 {
8598 let mut st = conn.state.lock().await;
8599 st.ws_write_tx = Some(tx);
8600 }
8601 ws.on_open("ws://example.com".to_string(), conn.clone())
8602 .await;
8603 assert!(rx.try_recv().is_err(), "unexpected message sent");
8604 });
8605 }
8606
8607 #[test]
8608 fn clears_pending_without_write_channel() {
8609 TOKIO_SHARED_RT.block_on(async {
8610 let ws: Arc<WebsocketStreams> =
8611 create_websocket_streams(Some("ws://example.com"), None, None);
8612 let conn = &ws.common.connection_pool[0];
8613 {
8614 let mut st = conn.state.lock().await;
8615 st.pending_subscriptions.push_back("solo".to_string());
8616 }
8617 ws.on_open("ws://example.com".to_string(), conn.clone())
8618 .await;
8619 let st_after = conn.state.lock().await;
8620 assert!(st_after.pending_subscriptions.is_empty());
8621 });
8622 }
8623 }
8624
8625 mod on_message {
8626 use super::*;
8627
8628 #[test]
8629 fn invokes_registered_callback() {
8630 TOKIO_SHARED_RT.block_on(async {
8631 let ws: Arc<WebsocketStreams> =
8632 create_websocket_streams(Some("ws://example.com"), None, None);
8633 let conn = &ws.common.connection_pool[0];
8634 let called = Arc::new(AtomicBool::new(false));
8635 let called_clone = called.clone();
8636
8637 {
8638 let mut st = conn.state.lock().await;
8639 st.stream_callbacks
8640 .entry("stream1".to_string())
8641 .or_default()
8642 .push(
8643 (Box::new(move |_: &Value| {
8644 called_clone.store(true, Ordering::SeqCst);
8645 })
8646 as Box<dyn Fn(&Value) + Send + Sync>)
8647 .into(),
8648 );
8649 }
8650
8651 let msg = json!({
8652 "stream": "stream1",
8653 "data": { "key": "value" }
8654 })
8655 .to_string();
8656
8657 ws.on_message(msg, conn.clone()).await;
8658
8659 assert!(called.load(Ordering::SeqCst));
8660 });
8661 }
8662
8663 #[test]
8664 fn invokes_all_registered_callbacks() {
8665 TOKIO_SHARED_RT.block_on(async {
8666 let ws: Arc<WebsocketStreams> =
8667 create_websocket_streams(Some("ws://example.com"), None, None);
8668 let conn = &ws.common.connection_pool[0];
8669 let counter = Arc::new(AtomicUsize::new(0));
8670
8671 {
8672 let mut st = conn.state.lock().await;
8673 let entry = st.stream_callbacks.entry("s".into()).or_default();
8674 let c1 = counter.clone();
8675 entry.push(
8676 (Box::new(move |_: &Value| {
8677 c1.fetch_add(1, Ordering::SeqCst);
8678 }) as Box<dyn Fn(&Value) + Send + Sync>)
8679 .into(),
8680 );
8681 let c2 = counter.clone();
8682 entry.push(
8683 (Box::new(move |_: &Value| {
8684 c2.fetch_add(1, Ordering::SeqCst);
8685 }) as Box<dyn Fn(&Value) + Send + Sync>)
8686 .into(),
8687 );
8688 }
8689
8690 let msg = json!({"stream":"s","data":42}).to_string();
8691 ws.on_message(msg, conn.clone()).await;
8692
8693 assert_eq!(counter.load(Ordering::SeqCst), 2);
8694 });
8695 }
8696
8697 #[test]
8698 fn handles_null_data_field() {
8699 TOKIO_SHARED_RT.block_on(async {
8700 let ws: Arc<WebsocketStreams> =
8701 create_websocket_streams(Some("ws://example.com"), None, None);
8702 let conn = &ws.common.connection_pool[0];
8703 let called = Arc::new(AtomicUsize::new(0));
8704 {
8705 let mut st = conn.state.lock().await;
8706 st.stream_callbacks.entry("n".into()).or_default().push(
8707 (Box::new({
8708 let c = called.clone();
8709 move |data: &Value| {
8710 if data.is_null() {
8711 c.fetch_add(1, Ordering::SeqCst);
8712 }
8713 }
8714 }) as Box<dyn Fn(&Value) + Send + Sync>)
8715 .into(),
8716 );
8717 }
8718 let msg = json!({"stream":"n","data":null}).to_string();
8719 ws.on_message(msg, conn.clone()).await;
8720 assert_eq!(called.load(Ordering::SeqCst), 1);
8721 });
8722 }
8723
8724 #[test]
8725 fn with_invalid_json_does_not_panic() {
8726 TOKIO_SHARED_RT.block_on(async {
8727 let ws: Arc<WebsocketStreams> =
8728 create_websocket_streams(Some("ws://example.com"), None, None);
8729 let conn = &ws.common.connection_pool[0];
8730 let bad = "not a json";
8731 ws.on_message(bad.to_string(), conn.clone()).await;
8732 });
8733 }
8734
8735 #[test]
8736 fn without_stream_field_does_nothing() {
8737 TOKIO_SHARED_RT.block_on(async {
8738 let ws: Arc<WebsocketStreams> =
8739 create_websocket_streams(Some("ws://example.com"), None, None);
8740 let conn = &ws.common.connection_pool[0];
8741 let msg = json!({ "data": { "foo": 1 } }).to_string();
8742 ws.on_message(msg, conn.clone()).await;
8743 });
8744 }
8745
8746 #[test]
8747 fn with_unregistered_stream_does_not_panic() {
8748 TOKIO_SHARED_RT.block_on(async {
8749 let ws: Arc<WebsocketStreams> =
8750 create_websocket_streams(Some("ws://example.com"), None, None);
8751 let conn = &ws.common.connection_pool[0];
8752 let msg = json!({
8753 "stream": "nope",
8754 "data": { "foo": 1 }
8755 })
8756 .to_string();
8757 ws.on_message(msg, conn.clone()).await;
8758 });
8759 }
8760
8761 #[test]
8762 fn invokes_registered_callback_with_url_path_key() {
8763 TOKIO_SHARED_RT.block_on(async {
8764 let ws: Arc<WebsocketStreams> =
8765 create_websocket_streams(Some("ws://example.com"), None, None);
8766 let conn = &ws.common.connection_pool[0];
8767
8768 {
8769 let mut st = conn.state.lock().await;
8770 st.url_path = Some("path1".to_string());
8771 }
8772
8773 let called = Arc::new(AtomicBool::new(false));
8774 let called_clone = called.clone();
8775
8776 {
8777 let mut st = conn.state.lock().await;
8778 st.stream_callbacks
8779 .entry("path1::stream1".to_string())
8780 .or_default()
8781 .push(
8782 (Box::new(move |_: &Value| {
8783 called_clone.store(true, Ordering::SeqCst);
8784 })
8785 as Box<dyn Fn(&Value) + Send + Sync>)
8786 .into(),
8787 );
8788 }
8789
8790 let msg = json!({
8791 "stream": "stream1",
8792 "data": { "key": "value" }
8793 })
8794 .to_string();
8795
8796 ws.on_message(msg, conn.clone()).await;
8797
8798 assert!(called.load(Ordering::SeqCst));
8799 });
8800 }
8801
8802 #[test]
8803 fn does_not_invoke_callback_when_url_path_mismatch() {
8804 TOKIO_SHARED_RT.block_on(async {
8805 let ws: Arc<WebsocketStreams> =
8806 create_websocket_streams(Some("ws://example.com"), None, None);
8807 let conn = &ws.common.connection_pool[0];
8808
8809 {
8810 let mut st = conn.state.lock().await;
8811 st.url_path = Some("path2".to_string());
8812 }
8813
8814 let called = Arc::new(AtomicBool::new(false));
8815 let called_clone = called.clone();
8816
8817 {
8818 let mut st = conn.state.lock().await;
8819 st.stream_callbacks
8820 .entry("path1::stream1".to_string())
8821 .or_default()
8822 .push(
8823 (Box::new(move |_: &Value| {
8824 called_clone.store(true, Ordering::SeqCst);
8825 })
8826 as Box<dyn Fn(&Value) + Send + Sync>)
8827 .into(),
8828 );
8829 }
8830
8831 let msg = json!({
8832 "stream": "stream1",
8833 "data": { "key": "value" }
8834 })
8835 .to_string();
8836
8837 ws.on_message(msg, conn.clone()).await;
8838
8839 assert!(!called.load(Ordering::SeqCst));
8840 });
8841 }
8842
8843 #[test]
8844 fn invokes_only_callbacks_for_current_url_path_when_both_exist() {
8845 TOKIO_SHARED_RT.block_on(async {
8846 let ws: Arc<WebsocketStreams> =
8847 create_websocket_streams(Some("ws://example.com"), None, None);
8848 let conn = &ws.common.connection_pool[0];
8849
8850 {
8851 let mut st = conn.state.lock().await;
8852 st.url_path = Some("path1".to_string());
8853 }
8854
8855 let c1 = Arc::new(AtomicUsize::new(0));
8856 let c2 = Arc::new(AtomicUsize::new(0));
8857
8858 {
8859 let mut st = conn.state.lock().await;
8860
8861 let a = c1.clone();
8862 st.stream_callbacks
8863 .entry("path1::s".to_string())
8864 .or_default()
8865 .push(
8866 (Box::new(move |_: &Value| {
8867 a.fetch_add(1, Ordering::SeqCst);
8868 })
8869 as Box<dyn Fn(&Value) + Send + Sync>)
8870 .into(),
8871 );
8872
8873 let b = c2.clone();
8874 st.stream_callbacks
8875 .entry("path2::s".to_string())
8876 .or_default()
8877 .push(
8878 (Box::new(move |_: &Value| {
8879 b.fetch_add(1, Ordering::SeqCst);
8880 })
8881 as Box<dyn Fn(&Value) + Send + Sync>)
8882 .into(),
8883 );
8884 }
8885
8886 let msg = json!({"stream":"s","data":42}).to_string();
8887 ws.on_message(msg, conn.clone()).await;
8888
8889 assert_eq!(c1.load(Ordering::SeqCst), 1);
8890 assert_eq!(c2.load(Ordering::SeqCst), 0);
8891 });
8892 }
8893 }
8894
8895 mod get_reconnect_url {
8896 use super::*;
8897
8898 #[test]
8899 fn single_stream_reconnect_url() {
8900 TOKIO_SHARED_RT.block_on(async {
8901 let ws: Arc<WebsocketStreams> =
8902 create_websocket_streams(Some("ws://example.com"), None, None);
8903 let c0 = ws.common.connection_pool[0].clone();
8904 {
8905 let mut map = ws.connection_streams.lock().await;
8906 map.insert("s1".to_string(), c0.clone());
8907 }
8908 let url = ws.get_reconnect_url("default_url".into(), c0).await;
8909 assert_eq!(url, "ws://example.com/stream?streams=s1");
8910 });
8911 }
8912
8913 #[test]
8914 fn multiple_streams_same_connection() {
8915 TOKIO_SHARED_RT.block_on(async {
8916 let ws: Arc<WebsocketStreams> =
8917 create_websocket_streams(Some("ws://example.com"), None, None);
8918 let c0 = ws.common.connection_pool[0].clone();
8919 {
8920 let mut map = ws.connection_streams.lock().await;
8921 map.insert("a".to_string(), c0.clone());
8922 map.insert("b".to_string(), c0.clone());
8923 }
8924 let url = ws.get_reconnect_url("default_url".into(), c0).await;
8925 let suffix = url
8926 .strip_prefix("ws://example.com/stream?streams=")
8927 .unwrap();
8928 let parts: Vec<_> = suffix.split('&').next().unwrap().split('/').collect();
8929 let set = parts.into_iter().collect::<std::collections::HashSet<_>>();
8930 assert_eq!(set, ["a", "b"].iter().copied().collect());
8931 });
8932 }
8933
8934 #[test]
8935 fn reconnect_url_with_time_unit() {
8936 TOKIO_SHARED_RT.block_on(async {
8937 let mut ws: Arc<WebsocketStreams> =
8938 create_websocket_streams(Some("ws://example.com"), None, None);
8939 Arc::get_mut(&mut ws).unwrap().configuration.time_unit =
8940 Some(TimeUnit::Microsecond);
8941 let c0 = ws.common.connection_pool[0].clone();
8942 {
8943 let mut map = ws.connection_streams.lock().await;
8944 map.insert("x".to_string(), c0.clone());
8945 }
8946 let url = ws.get_reconnect_url("default_url".into(), c0).await;
8947 assert_eq!(
8948 url,
8949 "ws://example.com/stream?streams=x&timeUnit=microsecond"
8950 );
8951 });
8952 }
8953
8954 #[test]
8955 fn reconnect_url_uses_url_path_from_connection_state() {
8956 TOKIO_SHARED_RT.block_on(async {
8957 let ws: Arc<WebsocketStreams> =
8958 create_websocket_streams(Some("ws://example.com"), None, None);
8959 let c0 = ws.common.connection_pool[0].clone();
8960
8961 {
8962 let mut st = c0.state.lock().await;
8963 st.url_path = Some("path1".to_string());
8964 }
8965
8966 {
8967 let mut map = ws.connection_streams.lock().await;
8968 map.insert("path1::s1".to_string(), c0.clone());
8969 }
8970
8971 let url = ws.get_reconnect_url("default_url".into(), c0).await;
8972 assert_eq!(url, "ws://example.com/path1/stream?streams=s1");
8973 });
8974 }
8975
8976 #[test]
8977 fn reconnect_url_strips_prefix_from_multiple_keys_with_url_path() {
8978 TOKIO_SHARED_RT.block_on(async {
8979 let ws: Arc<WebsocketStreams> =
8980 create_websocket_streams(Some("ws://example.com"), None, None);
8981 let c0 = ws.common.connection_pool[0].clone();
8982
8983 {
8984 let mut st = c0.state.lock().await;
8985 st.url_path = Some("path1".to_string());
8986 }
8987
8988 {
8989 let mut map = ws.connection_streams.lock().await;
8990 map.insert("path1::a".to_string(), c0.clone());
8991 map.insert("path1::b".to_string(), c0.clone());
8992 }
8993
8994 let url = ws.get_reconnect_url("default_url".into(), c0).await;
8995
8996 let suffix = url
8997 .strip_prefix("ws://example.com/path1/stream?streams=")
8998 .unwrap();
8999 let parts: Vec<_> = suffix.split('&').next().unwrap().split('/').collect();
9000 let set = parts.into_iter().collect::<std::collections::HashSet<_>>();
9001 assert_eq!(set, ["a", "b"].iter().copied().collect());
9002 });
9003 }
9004
9005 #[test]
9006 fn reconnect_url_with_url_path_and_time_unit() {
9007 TOKIO_SHARED_RT.block_on(async {
9008 let mut ws: Arc<WebsocketStreams> =
9009 create_websocket_streams(Some("ws://example.com"), None, None);
9010 Arc::get_mut(&mut ws).unwrap().configuration.time_unit =
9011 Some(TimeUnit::Microsecond);
9012
9013 let c0 = ws.common.connection_pool[0].clone();
9014
9015 {
9016 let mut st = c0.state.lock().await;
9017 st.url_path = Some("path1".to_string());
9018 }
9019
9020 {
9021 let mut map = ws.connection_streams.lock().await;
9022 map.insert("path1::x".to_string(), c0.clone());
9023 }
9024
9025 let url = ws.get_reconnect_url("default_url".into(), c0).await;
9026 assert_eq!(
9027 url,
9028 "ws://example.com/path1/stream?streams=x&timeUnit=microsecond"
9029 );
9030 });
9031 }
9032
9033 #[test]
9034 fn reconnect_url_ignores_streams_from_other_connections_even_if_same_path_prefix() {
9035 TOKIO_SHARED_RT.block_on(async {
9036 let ws: Arc<WebsocketStreams> =
9037 create_websocket_streams(Some("ws://example.com"), None, None);
9038 let c0 = ws.common.connection_pool[0].clone();
9039 let c1 = ws.common.connection_pool[1].clone();
9040
9041 {
9042 let mut st = c0.state.lock().await;
9043 st.url_path = Some("path1".to_string());
9044 }
9045 {
9046 let mut st = c1.state.lock().await;
9047 st.url_path = Some("path1".to_string());
9048 }
9049
9050 {
9051 let mut map = ws.connection_streams.lock().await;
9052 map.insert("path1::a".to_string(), c0.clone());
9053 map.insert("path1::b".to_string(), c1.clone());
9054 }
9055
9056 let url = ws.get_reconnect_url("default_url".into(), c0).await;
9057
9058 let suffix = url
9059 .strip_prefix("ws://example.com/path1/stream?streams=")
9060 .unwrap();
9061 let parts: Vec<_> = suffix.split('&').next().unwrap().split('/').collect();
9062 let set = parts.into_iter().collect::<std::collections::HashSet<_>>();
9063 assert_eq!(set, ["a"].iter().copied().collect());
9064 });
9065 }
9066 }
9067 }
9068
9069 mod websocket_stream {
9070 use super::*;
9071
9072 mod on {
9073 use super::*;
9074
9075 #[test]
9076 fn registers_callback_and_stream_callback_for_websocket_streams() {
9077 TOKIO_SHARED_RT.block_on(async {
9078 let ws_base = create_websocket_streams(Some("example.com"), None, None);
9079 let stream_name = "s1".to_string();
9080 let conn = ws_base.common.connection_pool[0].clone();
9081
9082 let key = ws_base.stream_key(&stream_name, None);
9083
9084 {
9085 let mut map = ws_base.connection_streams.lock().await;
9086 map.insert(key.clone(), conn.clone());
9087 }
9088 {
9089 let mut state = conn.state.lock().await;
9090 state.stream_callbacks.insert(key.clone(), Vec::new());
9091 }
9092
9093 let stream = Arc::new(WebsocketStream::<Value> {
9094 websocket_base: WebsocketBase::WebsocketStreams(ws_base.clone()),
9095 stream_or_id: stream_name.clone(),
9096 callback: Mutex::new(None),
9097 url_path: None,
9098 id: None,
9099 _phantom: PhantomData,
9100 });
9101
9102 stream.on("message", |_| {}).await;
9103
9104 let cb_guard = stream.callback.lock().await;
9105 assert!(cb_guard.is_some());
9106
9107 let cbs = {
9108 let state = conn.state.lock().await;
9109 state.stream_callbacks.get(&key).unwrap().clone()
9110 };
9111 assert_eq!(cbs.len(), 1);
9112 });
9113 }
9114
9115 #[test]
9116 fn message_twice_registers_two_wrappers_for_websocket_streams() {
9117 TOKIO_SHARED_RT.block_on(async {
9118 let ws_base = create_websocket_streams(Some("example.com"), None, None);
9119 let stream_name = "s2".to_string();
9120 let conn = ws_base.common.connection_pool[0].clone();
9121
9122 let key = ws_base.stream_key(&stream_name, None);
9123
9124 {
9125 let mut map = ws_base.connection_streams.lock().await;
9126 map.insert(key.clone(), conn.clone());
9127 }
9128 {
9129 let mut state = conn.state.lock().await;
9130 state.stream_callbacks.insert(key.clone(), Vec::new());
9131 }
9132
9133 let stream = Arc::new(WebsocketStream::<Value> {
9134 websocket_base: WebsocketBase::WebsocketStreams(ws_base.clone()),
9135 stream_or_id: stream_name.clone(),
9136 url_path: None,
9137 callback: Mutex::new(None),
9138 id: None,
9139 _phantom: PhantomData,
9140 });
9141
9142 stream.on("message", |_| {}).await;
9143 stream.on("message", |_| {}).await;
9144
9145 let state = conn.state.lock().await;
9146 let callbacks = state.stream_callbacks.get(&key).unwrap();
9147 assert_eq!(callbacks.len(), 2);
9148 });
9149 }
9150
9151 #[test]
9152 fn ignores_non_message_event_for_websocket_streams() {
9153 TOKIO_SHARED_RT.block_on(async {
9154 let ws_base = create_websocket_streams(Some("example.com"), None, None);
9155 let stream = Arc::new(WebsocketStream::<Value> {
9156 websocket_base: WebsocketBase::WebsocketStreams(ws_base.clone()),
9157 stream_or_id: "s".into(),
9158 url_path: None,
9159 callback: Mutex::new(None),
9160 id: None,
9161 _phantom: PhantomData,
9162 });
9163 stream.on("open", |_| {}).await;
9164 let guard = stream.callback.lock().await;
9165 assert!(guard.is_none());
9166 });
9167 }
9168
9169 #[test]
9170 fn registers_callback_and_stream_callback_for_websocket_api() {
9171 TOKIO_SHARED_RT.block_on(async {
9172 let ws_base = create_websocket_api(None, None, None);
9173
9174 {
9175 let mut stream_callbacks = ws_base.stream_callbacks.lock().await;
9176 stream_callbacks.insert("id1".to_string(), Vec::new());
9177 }
9178
9179 let stream = Arc::new(WebsocketStream::<Value> {
9180 websocket_base: WebsocketBase::WebsocketApi(ws_base.clone()),
9181 stream_or_id: "id1".to_string(),
9182 url_path: None,
9183 callback: Mutex::new(None),
9184 id: None,
9185 _phantom: PhantomData,
9186 });
9187
9188 let called = Arc::new(Mutex::new(false));
9189 let called_clone = called.clone();
9190 stream
9191 .on("message", move |v: Value| {
9192 let mut lock = called_clone.blocking_lock();
9193 *lock = v == Value::String("x".into());
9194 })
9195 .await;
9196
9197 let cb_guard = stream.callback.lock().await;
9198 assert!(cb_guard.is_some());
9199
9200 let stream_callbacks = ws_base.stream_callbacks.lock().await;
9201 let callbacks = stream_callbacks.get("id1").unwrap();
9202 assert_eq!(callbacks.len(), 1);
9203 });
9204 }
9205
9206 #[test]
9207 fn message_twice_registers_two_wrappers_for_websocket_api() {
9208 TOKIO_SHARED_RT.block_on(async {
9209 let ws_base = create_websocket_api(None, None, None);
9210
9211 let stream = Arc::new(WebsocketStream::<Value> {
9212 websocket_base: WebsocketBase::WebsocketApi(ws_base.clone()),
9213 stream_or_id: "id2".to_string(),
9214 url_path: None,
9215 callback: Mutex::new(None),
9216 id: None,
9217 _phantom: PhantomData,
9218 });
9219
9220 stream.on("message", |_| {}).await;
9221 stream.on("message", |_| {}).await;
9222
9223 let stream_callbacks = ws_base.stream_callbacks.lock().await;
9224 let callbacks = stream_callbacks.get("id2").unwrap();
9225 assert_eq!(callbacks.len(), 2);
9226 });
9227 }
9228
9229 #[test]
9230 fn ignores_non_message_event_for_websocket_api() {
9231 TOKIO_SHARED_RT.block_on(async {
9232 let ws_base = create_websocket_api(None, None, None);
9233
9234 let stream = Arc::new(WebsocketStream::<Value> {
9235 websocket_base: WebsocketBase::WebsocketApi(ws_base.clone()),
9236 stream_or_id: "id3".into(),
9237 url_path: None,
9238 callback: Mutex::new(None),
9239 id: None,
9240 _phantom: PhantomData,
9241 });
9242
9243 stream.on("open", |_| {}).await;
9244
9245 let guard = stream.callback.lock().await;
9246 assert!(guard.is_none());
9247
9248 let stream_callbacks = ws_base.stream_callbacks.lock().await;
9249 assert!(stream_callbacks.get("id3").is_none());
9250 assert!(stream_callbacks.is_empty());
9251 });
9252 }
9253
9254 #[test]
9255 fn registers_callback_for_websocket_streams_with_url_path() {
9256 TOKIO_SHARED_RT.block_on(async {
9257 let ws_base = create_websocket_streams(Some("example.com"), None, None);
9258 let stream_name = "s1".to_string();
9259 let conn = ws_base.common.connection_pool[0].clone();
9260
9261 let key = ws_base.stream_key(&stream_name, Some("path1"));
9262
9263 {
9264 let mut map = ws_base.connection_streams.lock().await;
9265 map.insert(key.clone(), conn.clone());
9266 }
9267 {
9268 let mut state = conn.state.lock().await;
9269 state.stream_callbacks.insert(key.clone(), Vec::new());
9270 }
9271
9272 let stream = Arc::new(WebsocketStream::<Value> {
9273 websocket_base: WebsocketBase::WebsocketStreams(ws_base.clone()),
9274 stream_or_id: stream_name.clone(),
9275 url_path: Some("path1".to_string()),
9276 callback: Mutex::new(None),
9277 id: None,
9278 _phantom: PhantomData,
9279 });
9280
9281 stream.on("message", |_| {}).await;
9282
9283 let cb_guard = stream.callback.lock().await;
9284 assert!(cb_guard.is_some());
9285
9286 let callbacks = {
9287 let state = conn.state.lock().await;
9288 state.stream_callbacks.get(&key).unwrap().clone()
9289 };
9290 assert_eq!(callbacks.len(), 1);
9291 });
9292 }
9293
9294 #[test]
9295 fn url_path_routes_callback_to_correct_key_when_same_stream_name_used() {
9296 TOKIO_SHARED_RT.block_on(async {
9297 let ws_base = create_websocket_streams(Some("example.com"), None, None);
9298 let stream_name = "s1".to_string();
9299 let conn = ws_base.common.connection_pool[0].clone();
9300
9301 let key1 = ws_base.stream_key(&stream_name, Some("path1"));
9302 let key2 = ws_base.stream_key(&stream_name, Some("path2"));
9303
9304 {
9305 let mut map = ws_base.connection_streams.lock().await;
9306 map.insert(key1.clone(), conn.clone());
9307 map.insert(key2.clone(), conn.clone());
9308 }
9309 {
9310 let mut state = conn.state.lock().await;
9311 state.stream_callbacks.insert(key1.clone(), Vec::new());
9312 state.stream_callbacks.insert(key2.clone(), Vec::new());
9313 }
9314
9315 let stream_path1 = Arc::new(WebsocketStream::<Value> {
9316 websocket_base: WebsocketBase::WebsocketStreams(ws_base.clone()),
9317 stream_or_id: stream_name.clone(),
9318 url_path: Some("path1".to_string()),
9319 callback: Mutex::new(None),
9320 id: None,
9321 _phantom: PhantomData,
9322 });
9323
9324 let stream_path2 = Arc::new(WebsocketStream::<Value> {
9325 websocket_base: WebsocketBase::WebsocketStreams(ws_base.clone()),
9326 stream_or_id: stream_name.clone(),
9327 url_path: Some("path2".to_string()),
9328 callback: Mutex::new(None),
9329 id: None,
9330 _phantom: PhantomData,
9331 });
9332
9333 stream_path1.on("message", |_| {}).await;
9334 stream_path2.on("message", |_| {}).await;
9335
9336 let state = conn.state.lock().await;
9337 assert_eq!(state.stream_callbacks.get(&key1).unwrap().len(), 1);
9338 assert_eq!(state.stream_callbacks.get(&key2).unwrap().len(), 1);
9339 });
9340 }
9341 }
9342
9343 mod on_message {
9344 use super::*;
9345
9346 #[test]
9347 fn on_message_registers_callback_for_websocket_streams() {
9348 TOKIO_SHARED_RT.block_on(async {
9349 let ws_base = create_websocket_streams(Some("example.com"), None, None);
9350 let stream_name = "s".to_string();
9351 let conn = ws_base.common.connection_pool[0].clone();
9352 {
9353 let mut map = ws_base.connection_streams.lock().await;
9354 map.insert(stream_name.clone(), conn.clone());
9355 }
9356 {
9357 let mut state = conn.state.lock().await;
9358 state
9359 .stream_callbacks
9360 .insert(stream_name.clone(), Vec::new());
9361 }
9362 let stream = Arc::new(WebsocketStream::<Value> {
9363 websocket_base: WebsocketBase::WebsocketStreams(ws_base.clone()),
9364 stream_or_id: stream_name.clone(),
9365 url_path: None,
9366 callback: Mutex::new(None),
9367 id: None,
9368 _phantom: PhantomData,
9369 });
9370 stream.on_message(|_v| {});
9371 let callbacks = &conn.state.lock().await.stream_callbacks[&stream_name];
9372 assert_eq!(callbacks.len(), 1);
9373 });
9374 }
9375
9376 #[test]
9377 fn on_message_twice_registers_two_callbacks_for_websocket_streams() {
9378 TOKIO_SHARED_RT.block_on(async {
9379 let ws_base = create_websocket_streams(Some("example.com"), None, None);
9380 let stream_name = "s".to_string();
9381 let conn = ws_base.common.connection_pool[0].clone();
9382 {
9383 let mut map = ws_base.connection_streams.lock().await;
9384 map.insert(stream_name.clone(), conn.clone());
9385 }
9386 {
9387 let mut state = conn.state.lock().await;
9388 state
9389 .stream_callbacks
9390 .insert(stream_name.clone(), Vec::new());
9391 }
9392 let stream = Arc::new(WebsocketStream::<Value> {
9393 websocket_base: WebsocketBase::WebsocketStreams(ws_base.clone()),
9394 stream_or_id: stream_name.clone(),
9395 url_path: None,
9396 callback: Mutex::new(None),
9397 id: None,
9398 _phantom: PhantomData,
9399 });
9400 stream.on_message(|_v| {});
9401 stream.on_message(|_v| {});
9402 let callbacks = &conn.state.lock().await.stream_callbacks[&stream_name];
9403 assert_eq!(callbacks.len(), 2);
9404 });
9405 }
9406
9407 #[test]
9408 fn on_message_registers_callback_for_websocket_api() {
9409 TOKIO_SHARED_RT.block_on(async {
9410 let ws_base = create_websocket_api(None, None, None);
9411 let identifier = "id1".to_string();
9412
9413 let stream = Arc::new(WebsocketStream::<Value> {
9414 websocket_base: WebsocketBase::WebsocketApi(ws_base.clone()),
9415 stream_or_id: identifier.clone(),
9416 url_path: None,
9417 callback: Mutex::new(None),
9418 id: None,
9419 _phantom: PhantomData,
9420 });
9421
9422 stream.on_message(|_v: Value| {});
9423
9424 let stream_callbacks = ws_base.stream_callbacks.lock().await;
9425 let callbacks = stream_callbacks.get(&identifier).unwrap();
9426 assert_eq!(callbacks.len(), 1);
9427 });
9428 }
9429
9430 #[test]
9431 fn on_message_twice_registers_two_callbacks_for_websocket_api() {
9432 TOKIO_SHARED_RT.block_on(async {
9433 let ws_base = create_websocket_api(None, None, None);
9434 let identifier = "id2".to_string();
9435
9436 let stream = Arc::new(WebsocketStream::<Value> {
9437 websocket_base: WebsocketBase::WebsocketApi(ws_base.clone()),
9438 stream_or_id: identifier.clone(),
9439 url_path: None,
9440 callback: Mutex::new(None),
9441 id: None,
9442 _phantom: PhantomData,
9443 });
9444
9445 stream.on_message(|_v: Value| {});
9446 stream.on_message(|_v: Value| {});
9447
9448 let stream_callbacks = ws_base.stream_callbacks.lock().await;
9449 let callbacks = stream_callbacks.get(&identifier).unwrap();
9450 assert_eq!(callbacks.len(), 2);
9451 });
9452 }
9453 }
9454
9455 mod unsubscribe {
9456 use super::*;
9457
9458 #[test]
9459 fn without_callback_does_nothing() {
9460 TOKIO_SHARED_RT.block_on(async {
9461 let ws_base = create_websocket_streams(Some("example.com"), None, None);
9462 let stream_name = "s1".to_string();
9463 let conn = ws_base.common.connection_pool[0].clone();
9464 {
9465 let mut map = ws_base.connection_streams.lock().await;
9466 map.insert(stream_name.clone(), conn.clone());
9467 }
9468 let mut state = conn.state.lock().await;
9469 state.stream_callbacks.insert(stream_name.clone(), vec![]);
9470 drop(state);
9471 let stream = Arc::new(WebsocketStream::<Value> {
9472 websocket_base: WebsocketBase::WebsocketStreams(ws_base.clone()),
9473 stream_or_id: stream_name.clone(),
9474 url_path: None,
9475 callback: Mutex::new(None),
9476 id: None,
9477 _phantom: PhantomData,
9478 });
9479 stream.unsubscribe().await;
9480 let state = conn.state.lock().await;
9481 assert!(state.stream_callbacks.contains_key(&stream_name));
9482 });
9483 }
9484
9485 #[test]
9486 fn removes_registered_callback_and_clears_state() {
9487 TOKIO_SHARED_RT.block_on(async {
9488 let ws_base = create_websocket_streams(Some("example.com"), None, None);
9489 let stream_name = "s2".to_string();
9490 let conn = ws_base.common.connection_pool[0].clone();
9491 {
9492 let mut map = ws_base.connection_streams.lock().await;
9493 map.insert(stream_name.clone(), conn.clone());
9494 }
9495 {
9496 let mut state = conn.state.lock().await;
9497 state
9498 .stream_callbacks
9499 .insert(stream_name.clone(), Vec::new());
9500 }
9501 let stream = Arc::new(WebsocketStream::<Value> {
9502 websocket_base: WebsocketBase::WebsocketStreams(ws_base.clone()),
9503 stream_or_id: stream_name.clone(),
9504 url_path: None,
9505 callback: Mutex::new(None),
9506 id: None,
9507 _phantom: PhantomData,
9508 });
9509 stream.on("message", |_| {}).await;
9510 {
9511 let guard = stream.callback.lock().await;
9512 assert!(guard.is_some());
9513 }
9514 stream.unsubscribe().await;
9515 sleep(Duration::from_millis(10)).await;
9516 let guard = stream.callback.lock().await;
9517 assert!(guard.is_none());
9518 let state = conn.state.lock().await;
9519 assert!(
9520 state
9521 .stream_callbacks
9522 .get(&stream_name)
9523 .is_none_or(std::vec::Vec::is_empty)
9524 );
9525 });
9526 }
9527
9528 #[test]
9529 fn without_callback_does_nothing_for_websocket_api() {
9530 TOKIO_SHARED_RT.block_on(async {
9531 let ws_base = create_websocket_api(None, None, None);
9532 let identifier = "id1".to_string();
9533
9534 {
9535 let mut stream_callbacks = ws_base.stream_callbacks.lock().await;
9536 stream_callbacks.insert(identifier.clone(), Vec::new());
9537 }
9538
9539 let stream = Arc::new(WebsocketStream::<Value> {
9540 websocket_base: WebsocketBase::WebsocketApi(ws_base.clone()),
9541 stream_or_id: identifier.clone(),
9542 url_path: None,
9543 callback: Mutex::new(None),
9544 id: None,
9545 _phantom: PhantomData,
9546 });
9547
9548 stream.unsubscribe().await;
9549
9550 let stream_callbacks = ws_base.stream_callbacks.lock().await;
9551 assert!(stream_callbacks.contains_key(&identifier));
9552 let callbacks = stream_callbacks.get(&identifier).unwrap();
9553 assert!(callbacks.is_empty());
9554 });
9555 }
9556
9557 #[test]
9558 fn removes_registered_callback_and_clears_state_for_websocket_api() {
9559 TOKIO_SHARED_RT.block_on(async {
9560 let ws_base = create_websocket_api(None, None, None);
9561 let identifier = "id2".to_string();
9562
9563 {
9564 let mut stream_callbacks = ws_base.stream_callbacks.lock().await;
9565 stream_callbacks.insert(identifier.clone(), Vec::new());
9566 }
9567
9568 let stream = Arc::new(WebsocketStream::<Value> {
9569 websocket_base: WebsocketBase::WebsocketApi(ws_base.clone()),
9570 stream_or_id: identifier.clone(),
9571 url_path: None,
9572 callback: Mutex::new(None),
9573 id: None,
9574 _phantom: PhantomData,
9575 });
9576
9577 stream.on("message", |_| {}).await;
9578
9579 {
9580 let stream_callbacks = ws_base.stream_callbacks.lock().await;
9581 let callbacks = stream_callbacks
9582 .get(&identifier)
9583 .expect("Entry for 'id2' should exist");
9584 assert_eq!(callbacks.len(), 1);
9585 }
9586
9587 stream.unsubscribe().await;
9588
9589 {
9590 let guard = stream.callback.lock().await;
9591 assert!(guard.is_none());
9592 }
9593
9594 {
9595 let stream_callbacks = ws_base.stream_callbacks.lock().await;
9596 let callbacks = stream_callbacks
9597 .get(&identifier)
9598 .expect("Entry for 'id2' should still exist");
9599 assert!(callbacks.is_empty());
9600 }
9601 });
9602 }
9603 }
9604 }
9605
9606 mod create_stream_handler {
9607 use super::*;
9608
9609 #[test]
9610 fn create_stream_handler_without_id_registers_stream() {
9611 TOKIO_SHARED_RT.block_on(async {
9612 let ws = create_websocket_streams(Some("ws://example.com"), None, None);
9613 let stream_name = "foo".to_string();
9614 let handler = create_stream_handler::<serde_json::Value>(
9615 WebsocketBase::WebsocketStreams(ws.clone()),
9616 stream_name.clone(),
9617 None,
9618 None,
9619 )
9620 .await;
9621 assert_eq!(handler.stream_or_id, stream_name);
9622 assert!(handler.id.is_none());
9623 let map = ws.connection_streams.lock().await;
9624 assert!(map.contains_key(&stream_name));
9625 });
9626 }
9627
9628 #[test]
9629 fn create_stream_handler_with_custom_string_id_registers_stream_and_id() {
9630 TOKIO_SHARED_RT.block_on(async {
9631 let ws = create_websocket_streams(Some("ws://example.com"), None, None);
9632 let stream_name = "bar".to_string();
9633 let custom_id = StreamId::from("my-custom-id".to_string());
9634 let handler = create_stream_handler::<serde_json::Value>(
9635 WebsocketBase::WebsocketStreams(ws.clone()),
9636 stream_name.clone(),
9637 Some(custom_id.clone()),
9638 None,
9639 )
9640 .await;
9641 assert_eq!(handler.stream_or_id, stream_name);
9642 assert_eq!(handler.id, Some(custom_id));
9643 let map = ws.connection_streams.lock().await;
9644 assert!(map.contains_key(&stream_name));
9645 });
9646 }
9647
9648 #[test]
9649 fn create_stream_handler_with_custom_integer_id_registers_stream_and_id() {
9650 TOKIO_SHARED_RT.block_on(async {
9651 let ws = create_websocket_streams(Some("ws://example.com"), None, None);
9652 let stream_name = "bar".to_string();
9653 let custom_id = StreamId::from(123u32);
9654 let handler = create_stream_handler::<serde_json::Value>(
9655 WebsocketBase::WebsocketStreams(ws.clone()),
9656 stream_name.clone(),
9657 Some(custom_id.clone()),
9658 None,
9659 )
9660 .await;
9661 assert_eq!(handler.stream_or_id, stream_name);
9662 assert_eq!(handler.id, Some(custom_id));
9663 let map = ws.connection_streams.lock().await;
9664 assert!(map.contains_key(&stream_name));
9665 });
9666 }
9667
9668 #[test]
9669 fn create_stream_handler_without_id_registers_api_stream() {
9670 TOKIO_SHARED_RT.block_on(async {
9671 let ws_base = create_websocket_api(None, None, None);
9672 let identifier = "foo-api".to_string();
9673
9674 let handler = create_stream_handler::<Value>(
9675 WebsocketBase::WebsocketApi(ws_base.clone()),
9676 identifier.clone(),
9677 None,
9678 None,
9679 )
9680 .await;
9681
9682 assert_eq!(handler.stream_or_id, identifier);
9683 assert!(handler.id.is_none());
9684 });
9685 }
9686
9687 #[test]
9688 fn create_stream_handler_with_custom_string_id_registers_api_stream_and_id() {
9689 TOKIO_SHARED_RT.block_on(async {
9690 let ws_base = create_websocket_api(None, None, None);
9691 let identifier = "bar-api".to_string();
9692 let custom_id = StreamId::from("custom-123".to_string());
9693
9694 let handler = create_stream_handler::<Value>(
9695 WebsocketBase::WebsocketApi(ws_base.clone()),
9696 identifier.clone(),
9697 Some(custom_id.clone()),
9698 None,
9699 )
9700 .await;
9701
9702 assert_eq!(handler.stream_or_id, identifier);
9703 assert_eq!(handler.id, Some(custom_id));
9704 });
9705 }
9706
9707 #[test]
9708 fn create_stream_handler_with_custom_integer_id_registers_api_stream_and_id() {
9709 TOKIO_SHARED_RT.block_on(async {
9710 let ws_base = create_websocket_api(None, None, None);
9711 let identifier = "bar-api".to_string();
9712 let custom_id = StreamId::from(123u32);
9713
9714 let handler = create_stream_handler::<Value>(
9715 WebsocketBase::WebsocketApi(ws_base.clone()),
9716 identifier.clone(),
9717 Some(custom_id.clone()),
9718 None,
9719 )
9720 .await;
9721
9722 assert_eq!(handler.stream_or_id, identifier);
9723 assert_eq!(handler.id, Some(custom_id));
9724 });
9725 }
9726
9727 #[test]
9728 fn websocket_streams_without_url_path_registers_stream_key() {
9729 TOKIO_SHARED_RT.block_on(async {
9730 let ws = create_websocket_streams(Some("ws://example.com"), None, None);
9731 let stream_name = "foo".to_string();
9732
9733 let handler = create_stream_handler::<Value>(
9734 WebsocketBase::WebsocketStreams(ws.clone()),
9735 stream_name.clone(),
9736 None,
9737 None,
9738 )
9739 .await;
9740
9741 assert_eq!(handler.stream_or_id, stream_name);
9742 assert!(handler.id.is_none());
9743
9744 let map = ws.connection_streams.lock().await;
9745 assert!(map.contains_key("foo"));
9746 });
9747 }
9748
9749 #[test]
9750 fn websocket_streams_with_url_path_registers_prefixed_stream_key() {
9751 TOKIO_SHARED_RT.block_on(async {
9752 let ws = create_websocket_streams(Some("ws://example.com"), None, None);
9753
9754 {
9755 let conn = ws.common.connection_pool[0].clone();
9756 let mut st = conn.state.lock().await;
9757 st.url_path = Some("path1".to_string());
9758 }
9759
9760 let stream_name = "foo".to_string();
9761
9762 let handler = create_stream_handler::<Value>(
9763 WebsocketBase::WebsocketStreams(ws.clone()),
9764 stream_name.clone(),
9765 None,
9766 Some("path1".to_string()),
9767 )
9768 .await;
9769
9770 assert_eq!(handler.stream_or_id, stream_name);
9771 assert!(handler.id.is_none());
9772
9773 let map = ws.connection_streams.lock().await;
9774 assert!(map.contains_key("path1::foo"));
9775 });
9776 }
9777
9778 #[test]
9779 fn websocket_streams_with_custom_id_preserves_id_and_registers_prefixed_key() {
9780 TOKIO_SHARED_RT.block_on(async {
9781 let ws = create_websocket_streams(Some("ws://example.com"), None, None);
9782
9783 {
9784 let conn = ws.common.connection_pool[0].clone();
9785 let mut st = conn.state.lock().await;
9786 st.url_path = Some("path1".to_string());
9787 }
9788
9789 let stream_name = "bar".to_string();
9790 let custom_id = StreamId::from("my-custom-id".to_string());
9791
9792 let handler = create_stream_handler::<Value>(
9793 WebsocketBase::WebsocketStreams(ws.clone()),
9794 stream_name.clone(),
9795 Some(custom_id.clone()),
9796 Some("path1".to_string()),
9797 )
9798 .await;
9799
9800 assert_eq!(handler.stream_or_id, stream_name);
9801 assert_eq!(handler.id, Some(custom_id));
9802
9803 let map = ws.connection_streams.lock().await;
9804 assert!(map.contains_key("path1::bar"));
9805 });
9806 }
9807
9808 #[test]
9809 fn websocket_api_does_not_register_stream_in_connection_map() {
9810 TOKIO_SHARED_RT.block_on(async {
9811 let ws_base = create_websocket_api(None, None, None);
9812 let identifier = "foo-api".to_string();
9813
9814 let handler = create_stream_handler::<Value>(
9815 WebsocketBase::WebsocketApi(ws_base.clone()),
9816 identifier.clone(),
9817 None,
9818 Some("path1".to_string()),
9819 )
9820 .await;
9821
9822 assert_eq!(handler.stream_or_id, identifier);
9823 assert!(handler.id.is_none());
9824 });
9825 }
9826 }
9827
9828 mod websocket_connection_failure_reason {
9829 use super::*;
9830 use std::io::{Error as IoError, ErrorKind};
9831 use tokio_tungstenite::tungstenite::Error as TungsteniteError;
9832
9833 #[test]
9834 fn from_tungstenite_error_classifies_connection_closed() {
9835 let error = TungsteniteError::ConnectionClosed;
9836 let reason = WebsocketConnectionFailureReason::from_tungstenite_error(&error);
9837 assert!(matches!(
9838 reason,
9839 WebsocketConnectionFailureReason::ConnectionReset
9840 ));
9841 assert!(reason.should_reconnect());
9842 }
9843
9844 #[test]
9845 fn from_tungstenite_error_classifies_already_closed() {
9846 let error = TungsteniteError::AlreadyClosed;
9847 let reason = WebsocketConnectionFailureReason::from_tungstenite_error(&error);
9848 assert!(matches!(
9849 reason,
9850 WebsocketConnectionFailureReason::ConnectionReset
9851 ));
9852 assert!(reason.should_reconnect());
9853 }
9854
9855 #[test]
9856 fn from_tungstenite_error_classifies_io_errors() {
9857 let io_error = IoError::new(ErrorKind::ConnectionReset, "connection reset");
9859 let error = TungsteniteError::Io(io_error);
9860 let reason = WebsocketConnectionFailureReason::from_tungstenite_error(&error);
9861 assert!(matches!(
9862 reason,
9863 WebsocketConnectionFailureReason::ConnectionReset
9864 ));
9865 assert!(reason.should_reconnect());
9866
9867 let io_error = IoError::new(ErrorKind::ConnectionAborted, "connection aborted");
9869 let error = TungsteniteError::Io(io_error);
9870 let reason = WebsocketConnectionFailureReason::from_tungstenite_error(&error);
9871 assert!(matches!(
9872 reason,
9873 WebsocketConnectionFailureReason::ConnectionReset
9874 ));
9875 assert!(reason.should_reconnect());
9876
9877 let io_error = IoError::new(ErrorKind::TimedOut, "timed out");
9879 let error = TungsteniteError::Io(io_error);
9880 let reason = WebsocketConnectionFailureReason::from_tungstenite_error(&error);
9881 assert!(matches!(
9882 reason,
9883 WebsocketConnectionFailureReason::NetworkInterruption
9884 ));
9885 assert!(reason.should_reconnect());
9886
9887 let io_error = IoError::new(ErrorKind::UnexpectedEof, "unexpected eof");
9889 let error = TungsteniteError::Io(io_error);
9890 let reason = WebsocketConnectionFailureReason::from_tungstenite_error(&error);
9891 assert!(matches!(
9892 reason,
9893 WebsocketConnectionFailureReason::StreamEnded
9894 ));
9895 assert!(reason.should_reconnect());
9896
9897 let io_error = IoError::new(ErrorKind::PermissionDenied, "permission denied");
9899 let error = TungsteniteError::Io(io_error);
9900 let reason = WebsocketConnectionFailureReason::from_tungstenite_error(&error);
9901 assert!(matches!(
9902 reason,
9903 WebsocketConnectionFailureReason::AuthenticationFailure
9904 ));
9905 assert!(!reason.should_reconnect());
9906
9907 let io_error = IoError::other("other error");
9909 let error = TungsteniteError::Io(io_error);
9910 let reason = WebsocketConnectionFailureReason::from_tungstenite_error(&error);
9911 assert!(matches!(
9912 reason,
9913 WebsocketConnectionFailureReason::NetworkInterruption
9914 ));
9915 assert!(reason.should_reconnect());
9916 }
9917
9918 #[test]
9919 fn from_tungstenite_error_classifies_protocol_errors() {
9920 use tokio_tungstenite::tungstenite::error::ProtocolError;
9922 let protocol_error = ProtocolError::ResetWithoutClosingHandshake;
9923 let error = TungsteniteError::Protocol(protocol_error);
9924 let reason = WebsocketConnectionFailureReason::from_tungstenite_error(&error);
9925 assert!(matches!(
9926 reason,
9927 WebsocketConnectionFailureReason::UnexpectedClose
9928 ));
9929 assert!(reason.should_reconnect());
9930
9931 let error = TungsteniteError::Utf8;
9933 let reason = WebsocketConnectionFailureReason::from_tungstenite_error(&error);
9934 assert!(matches!(
9935 reason,
9936 WebsocketConnectionFailureReason::ProtocolViolation
9937 ));
9938 assert!(!reason.should_reconnect());
9939 }
9940
9941 #[test]
9942 fn from_close_code_classifies_standard_codes() {
9943 let reason = WebsocketConnectionFailureReason::from_close_code(1000, false);
9945 assert!(matches!(
9946 reason,
9947 WebsocketConnectionFailureReason::NormalClose
9948 ));
9949 assert!(!reason.should_reconnect());
9950
9951 let reason = WebsocketConnectionFailureReason::from_close_code(1001, false);
9953 assert!(matches!(
9954 reason,
9955 WebsocketConnectionFailureReason::ServerTemporaryError
9956 ));
9957 assert!(reason.should_reconnect());
9958
9959 let reason = WebsocketConnectionFailureReason::from_close_code(1002, false);
9961 assert!(matches!(
9962 reason,
9963 WebsocketConnectionFailureReason::ProtocolViolation
9964 ));
9965 assert!(!reason.should_reconnect());
9966
9967 let reason = WebsocketConnectionFailureReason::from_close_code(1006, false);
9969 assert!(matches!(
9970 reason,
9971 WebsocketConnectionFailureReason::UnexpectedClose
9972 ));
9973 assert!(reason.should_reconnect());
9974
9975 let reason = WebsocketConnectionFailureReason::from_close_code(1008, false);
9977 assert!(matches!(
9978 reason,
9979 WebsocketConnectionFailureReason::PermanentServerError
9980 ));
9981 assert!(!reason.should_reconnect());
9982
9983 let reason = WebsocketConnectionFailureReason::from_close_code(1011, false);
9985 assert!(matches!(
9986 reason,
9987 WebsocketConnectionFailureReason::ServerTemporaryError
9988 ));
9989 assert!(reason.should_reconnect());
9990
9991 let reason = WebsocketConnectionFailureReason::from_close_code(1015, false);
9993 assert!(matches!(
9994 reason,
9995 WebsocketConnectionFailureReason::ConfigurationError
9996 ));
9997 assert!(!reason.should_reconnect());
9998
9999 let reason = WebsocketConnectionFailureReason::from_close_code(4000, false);
10001 assert!(matches!(
10002 reason,
10003 WebsocketConnectionFailureReason::PermanentServerError
10004 ));
10005 assert!(!reason.should_reconnect());
10006
10007 let reason = WebsocketConnectionFailureReason::from_close_code(4999, false);
10008 assert!(matches!(
10009 reason,
10010 WebsocketConnectionFailureReason::PermanentServerError
10011 ));
10012 assert!(!reason.should_reconnect());
10013
10014 let reason = WebsocketConnectionFailureReason::from_close_code(9999, false);
10016 assert!(matches!(
10017 reason,
10018 WebsocketConnectionFailureReason::UnexpectedClose
10019 ));
10020 assert!(reason.should_reconnect());
10021 }
10022
10023 #[test]
10024 fn from_close_code_handles_user_initiated() {
10025 let reason = WebsocketConnectionFailureReason::from_close_code(1000, true);
10027 assert!(matches!(
10028 reason,
10029 WebsocketConnectionFailureReason::UserInitiatedClose
10030 ));
10031 assert!(!reason.should_reconnect());
10032
10033 let reason = WebsocketConnectionFailureReason::from_close_code(1006, true);
10034 assert!(matches!(
10035 reason,
10036 WebsocketConnectionFailureReason::UserInitiatedClose
10037 ));
10038 assert!(!reason.should_reconnect());
10039
10040 let reason = WebsocketConnectionFailureReason::from_close_code(4000, true);
10041 assert!(matches!(
10042 reason,
10043 WebsocketConnectionFailureReason::UserInitiatedClose
10044 ));
10045 assert!(!reason.should_reconnect());
10046 }
10047
10048 #[test]
10049 fn should_reconnect_logic() {
10050 assert!(WebsocketConnectionFailureReason::NetworkInterruption.should_reconnect());
10052 assert!(WebsocketConnectionFailureReason::ConnectionReset.should_reconnect());
10053 assert!(WebsocketConnectionFailureReason::ServerTemporaryError.should_reconnect());
10054 assert!(WebsocketConnectionFailureReason::UnexpectedClose.should_reconnect());
10055 assert!(WebsocketConnectionFailureReason::StreamEnded.should_reconnect());
10056
10057 assert!(!WebsocketConnectionFailureReason::AuthenticationFailure.should_reconnect());
10059 assert!(!WebsocketConnectionFailureReason::ProtocolViolation.should_reconnect());
10060 assert!(!WebsocketConnectionFailureReason::ConfigurationError.should_reconnect());
10061 assert!(!WebsocketConnectionFailureReason::UserInitiatedClose.should_reconnect());
10062 assert!(!WebsocketConnectionFailureReason::PermanentServerError.should_reconnect());
10063 assert!(!WebsocketConnectionFailureReason::NormalClose.should_reconnect());
10064 }
10065
10066 #[test]
10067 fn debug_and_clone_work() {
10068 let reason = WebsocketConnectionFailureReason::NetworkInterruption;
10069 let cloned = reason;
10070 let debug_str = format!("{:?}", reason);
10071
10072 assert!(matches!(
10073 cloned,
10074 WebsocketConnectionFailureReason::NetworkInterruption
10075 ));
10076 assert!(debug_str.contains("NetworkInterruption"));
10077 }
10078 }
10079}