Skip to main content

binance_sdk/common/
websocket.rs

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    /// Cancels the ongoing WebSocket event subscription and stops the event processing task.
60    ///
61    /// This method aborts the background task responsible for receiving and processing
62    /// WebSocket events, effectively unsubscribing from further event notifications.
63    ///
64    /// # Examples
65    ///
66    ///
67    /// let emitter = `WebsocketEventEmitter::new()`;
68    /// let subscription = emitter.subscribe(|event| {
69    ///     // Handle WebSocket event
70    /// });
71    /// `subscription.unsubscribe()`; // Stop receiving events
72    ///
73    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    /// Subscribes to WebSocket events and returns a `Subscription` that allows event processing.
103    ///
104    /// This method creates an unbounded channel for receiving WebSocket events and
105    /// spawns an asynchronous task to process these events using the provided callback function.
106    ///
107    /// # Arguments
108    ///
109    /// * `callback` - A mutable function that will be called for each received WebSocket event.
110    ///   The callback must be thread-safe and have a static lifetime.
111    ///
112    /// # Returns
113    ///
114    /// A `Subscription` that can be used to unsubscribe and stop event processing.
115    ///
116    /// # Examples
117    ///
118    ///
119    /// let emitter = `WebsocketEventEmitter::new()`;
120    /// let subscription = emitter.subscribe(|event| {
121    ///     // Handle WebSocket event
122    ///     println!("Received event: {:?}", event);
123    /// });
124    ///
125    /// // Later, when no longer needed
126    /// `subscription.unsubscribe()`;
127    ///
128    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    /// Emits a WebSocket event to all registered subscribers.
149    ///
150    /// This method sends the given event to all active subscribers. If a subscriber
151    /// has been dropped without unsubscribing, a warning is logged and the subscriber
152    /// is removed from the list.
153    ///
154    /// # Arguments
155    ///
156    /// * `event` - The WebSocket event to be emitted to all subscribers.
157    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/// A trait defining the lifecycle and behavior of a WebSocket connection.
175///
176/// This trait provides methods for handling WebSocket connection events,
177/// including connection opening, message handling, and reconnection URL retrieval.
178///
179/// # Methods
180///
181/// * `on_open`: Called when a WebSocket connection is established
182/// * `on_message`: Called when a message is received over the WebSocket
183/// * `get_reconnect_url`: Determines the URL to use for reconnecting
184///
185/// # Thread Safety
186///
187/// Implementors must be safely shareable across threads, as indicated by the `Send + Sync + 'static` bounds.
188#[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    /// Same as [`Self::new`], but additionally accepts a caller-supplied
302    /// [`WebsocketHandshakeFn`] used in place of the SDK's default handshake path.
303    /// Kept private: reachable only via `ConfigurationWebsocketApi::handshake` /
304    /// `ConfigurationWebsocketStreams::handshake` to avoid growing the public
305    /// constructor surface further.
306    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    /// Spawns an asynchronous loop to handle websocket reconnection attempts
344    ///
345    /// This method manages reconnection logic for websocket connections, including:
346    /// - Scheduling reconnects with a configurable delay
347    /// - Finding the appropriate connection in the connection pool
348    /// - Attempting to reinitialize the connection
349    /// - Logging reconnection failures or warnings
350    ///
351    /// # Arguments
352    /// * `common` - A shared reference to the `WebsocketCommon` instance
353    /// * `reconnect_rx` - A receiver channel for reconnection entries
354    ///
355    /// # Behavior
356    /// - Waits for reconnection entries from the channel
357    /// - Applies a configurable delay before attempting reconnection
358    /// - Attempts to reinitialize the connection with the provided URL
359    /// - Handles and logs any reconnection errors
360    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    /// Spawns an asynchronous loop to manage connection renewals
395    ///
396    /// This method handles the periodic renewal of websocket connections by:
397    /// - Maintaining a delay queue for connection expiration
398    /// - Receiving renewal requests for specific connections
399    /// - Triggering reconnection when a connection reaches its maximum duration
400    /// - Attempting to find and renew connections in the connection pool
401    ///
402    /// # Behavior
403    /// - Listens for renewal requests on a channel
404    /// - Tracks connection expiration using a delay queue
405    /// - Initiates reconnection process when a connection expires
406    /// - Handles and logs any renewal failures
407    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    /// Checks if a WebSocket connection is ready for use.
453    ///
454    /// # Arguments
455    ///
456    /// * `connection` - The WebSocket connection to check
457    /// * `allow_non_established` - If true, allows connections that are not fully established
458    ///
459    /// # Returns
460    ///
461    /// `true` if the connection is ready, `false` otherwise
462    ///
463    /// # Behavior
464    ///
465    /// A connection is considered ready if:
466    /// - It has a write channel (unless `allow_non_established` is true)
467    /// - No reconnection is pending
468    /// - No close has been initiated
469    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    /// Checks if a WebSocket connection is established.
481    ///
482    /// # Arguments
483    ///
484    /// * `connection` - Optional specific WebSocket connection to check
485    ///
486    /// # Returns
487    ///
488    /// `true` if a connection is ready and established, `false` otherwise
489    ///
490    /// # Behavior
491    ///
492    /// - If a specific connection is provided, checks only that connection
493    /// - If no connection is provided, checks all connections in the pool
494    /// - A connection is considered established if it is ready and not in a non-established state
495    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    /// Retrieves available WebSocket connections from the connection pool.
510    ///
511    /// # Arguments
512    ///
513    /// * `allow_non_established` - If `true`, includes connections that are not fully established
514    /// * `url_path` - Optional URL path to filter connections
515    ///
516    /// # Returns
517    ///
518    /// A vector of `Arc<WebsocketConnection>` that are ready based on the `allow_non_established` flag
519    ///
520    /// # Behavior
521    ///
522    /// - For single connection mode, returns the first connection
523    /// - For multi-connection mode, filters connections based on readiness
524    /// - Uses `is_connection_ready` to determine connection availability
525    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    /// Retrieves a WebSocket connection from the connection pool.
545    ///
546    /// # Arguments
547    ///
548    /// * `allow_non_established` - If `true`, allows selecting a connection that is not fully established
549    /// * `url_path` - Optional URL path to filter connections
550    ///
551    /// # Returns
552    ///
553    /// An `Arc` to a `WebsocketConnection` from the pool, selected using round-robin strategy
554    ///
555    /// # Errors
556    ///
557    /// Returns `WebsocketError::NotConnected` if no suitable connection is available
558    ///
559    /// # Behavior
560    ///
561    /// - For single connection mode, returns the first connection
562    /// - For multi-connection mode, selects a ready connection using round-robin
563    /// - Filters connections based on `allow_non_established` parameter
564    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    /// Gracefully closes a WebSocket connection by waiting for pending requests to complete.
594    ///
595    /// # Arguments
596    ///
597    /// * `ws_write_tx_to_close` - Sender channel for sending close message
598    /// * `connection` - Shared reference to the WebSocket connection
599    ///
600    /// # Behavior
601    ///
602    /// - Waits up to 30 seconds for all pending requests to complete
603    /// - Logs debug and warning messages during the closing process
604    /// - Sends a normal close frame to the WebSocket
605    ///
606    /// # Returns
607    ///
608    /// `Ok(())` if connection closes successfully, otherwise a `WebsocketError`
609    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    /// Retrieves the URL to use for reconnecting to the WebSocket.
643    ///
644    /// # Arguments
645    ///
646    /// * `default_url` - The default URL to use if no custom reconnect URL is provided
647    /// * `connection` - A shared reference to the WebSocket connection
648    ///
649    /// # Returns
650    ///
651    /// The URL to use for reconnecting, either from a custom handler or the default URL
652    ///
653    /// # Behavior
654    ///
655    /// - Checks if a connection handler is available
656    /// - If a handler exists, calls its `get_reconnect_url` method
657    /// - Otherwise, returns the default URL
658    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    /// Handles the WebSocket connection opening event.
676    ///
677    /// This method is called when a WebSocket connection is successfully established. It performs
678    /// the following key actions:
679    /// - Invokes the connection handler's `on_open` method if a handler is present
680    /// - Logs connection information
681    /// - Handles connection renewal and close scenarios
682    /// - Emits a WebSocket open event
683    ///
684    /// # Arguments
685    ///
686    /// * `url` - The URL of the WebSocket server
687    /// * `connection` - A shared reference to the WebSocket connection
688    /// * `old_ws_writer` - Optional previous WebSocket writer for graceful connection handling
689    ///
690    /// # Behavior
691    ///
692    /// - If a connection handler exists, calls its `on_open` method
693    /// - Checks for pending renewal or close states
694    /// - Closes the previous connection if renewal is in progress
695    /// - Emits an open event if the connection is successfully established
696    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    /// Handles an incoming WebSocket message
743    ///
744    /// # Arguments
745    ///
746    /// * `msg` - The received message as a string
747    /// * `connection` - A shared reference to the WebSocket connection
748    ///
749    /// # Behavior
750    ///
751    /// - If a connection handler exists, awaits its `on_message` method
752    /// - Emits a `WebsocketEvent::Message` event with the received message
753    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    /// Creates a WebSocket connection with optional configuration and agent
764    ///
765    /// # Arguments
766    ///
767    /// * `url` - The WebSocket server URL to connect to
768    /// * `agent` - Optional agent connector for configuring the connection
769    /// * `user_agent` - Optional custom user agent string
770    ///
771    /// # Returns
772    ///
773    /// A `Result` containing the established WebSocket stream or a `WebsocketError`
774    ///
775    /// # Errors
776    ///
777    /// Returns a `WebsocketError` if:
778    /// - The WebSocket handshake fails
779    /// - The connection times out after 10 seconds
780    ///
781    /// # Behavior
782    ///
783    /// Attempts to establish a WebSocket connection with a configurable timeout,
784    /// supporting optional TLS, custom user agent, and connection connectors
785    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    /// Connects to a WebSocket URL for all connections in the connection pool concurrently
834    ///
835    /// # Arguments
836    ///
837    /// * `url` - The WebSocket server URL to connect to
838    /// * `connections` - Optional specific connections to use, otherwise uses the entire pool
839    ///
840    /// # Returns
841    ///
842    /// A `Result` indicating whether all connections were successfully established
843    ///
844    /// # Errors
845    ///
846    /// Returns a `WebsocketError` if any connection in the pool fails to establish
847    ///
848    /// # Behavior
849    ///
850    /// Attempts to initialize a WebSocket connection for each connection in the pool
851    /// concurrently, logging successes and failures for each connection attempt
852    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    /// Initializes a WebSocket connection for a specific connection in the pool
890    ///
891    /// # Arguments
892    ///
893    /// * `url` - The WebSocket server URL to connect to
894    /// * `is_renewal` - Flag indicating whether this is a connection renewal attempt
895    /// * `connection` - Optional specific WebSocket connection to use, otherwise selects from the pool
896    ///
897    /// # Returns
898    ///
899    /// A `Result` indicating whether the connection was successfully established
900    ///
901    /// # Errors
902    ///
903    /// Returns a `WebsocketError` if the connection fails to initialize or establish
904    ///
905    /// # Behavior
906    ///
907    /// Handles connection establishment, splitting read/write streams, spawning reader/writer tasks,
908    /// and managing connection state including renewal, reconnection, and error handling
909    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                        // Apply same reconnection logic as reader errors
979                        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                            // Classify the close reason
1076                            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                                // Emit detailed error for permanent failures
1123                                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                            // Classify the error type for reconnection decision
1133                            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                            // Apply the same reconnection logic as Close frames
1145                            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                                // Emit critical error for non-reconnectable failures
1176                                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                // Handle case where stream ends unexpectedly (e.g., network disconnection)
1189                info!("WebSocket stream ended for connection {}", reader_conn.id);
1190
1191                // Handle possibly unexpected stream end with same logic as other errors
1192                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    /// Gracefully disconnects all active WebSocket connections.
1236    ///
1237    /// This method attempts to close all connections in the connection pool within a 30-second timeout.
1238    /// It marks each connection as close-initiated and attempts to close them gracefully.
1239    ///
1240    /// # Returns
1241    ///
1242    /// - `Ok(())` if all connections are successfully closed
1243    /// - `Err(WebsocketError)` if there are errors during disconnection or a timeout occurs
1244    ///
1245    /// # Errors
1246    ///
1247    /// Returns `WebsocketError::Timeout` if disconnection takes longer than 30 seconds
1248    ///
1249    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    /// Sends a PING message to all ready WebSocket connections.
1295    ///
1296    /// This method iterates through the connection pool, identifies ready connections,
1297    /// and sends a PING message to each of them. It logs the number of connections
1298    /// being pinged and handles any send errors individually.
1299    ///
1300    /// # Behavior
1301    ///
1302    /// - Skips connections that are not ready
1303    /// - Logs a warning if no connections are ready
1304    /// - Sends PING messages concurrently
1305    /// - Logs debug/error messages for each PING attempt
1306    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    /// Sends a WebSocket message and optionally waits for a reply.
1344    ///
1345    /// # Arguments
1346    ///
1347    /// * `payload` - The message payload to send
1348    /// * `id` - Optional request identifier, required when waiting for a reply
1349    /// * `wait_for_reply` - Whether to wait for a response to the message
1350    /// * `timeout` - Maximum duration to wait for a reply
1351    /// * `connection` - Optional specific WebSocket connection to use
1352    ///
1353    /// # Returns
1354    ///
1355    /// A receiver for the response if `wait_for_reply` is true, otherwise `None`
1356    ///
1357    /// # Errors
1358    ///
1359    /// Returns a `WebsocketError` if the connection is not ready or the send fails
1360    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    /// Creates a new WebSocket API instance with the given configuration and connection pool.
1505    ///
1506    /// # Arguments
1507    ///
1508    /// * `configuration` - Configuration settings for the WebSocket API
1509    /// * `connection_pool` - A vector of WebSocket connections to be used
1510    ///
1511    /// # Returns
1512    ///
1513    /// An `Arc`-wrapped `WebsocketApi` instance ready for use
1514    ///
1515    /// # Panics
1516    ///
1517    /// This function will panic if the configuration is not valid.
1518    ///
1519    /// # Examples
1520    ///
1521    ///
1522    /// let api = `WebsocketApi::new(config`, `connection_pool`);
1523    ///
1524    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    /// Connects to a WebSocket server with a configurable timeout and connection handling.
1550    ///
1551    /// This method attempts to establish a WebSocket connection if not already connected.
1552    /// It prevents multiple simultaneous connection attempts and supports a connection pool.
1553    ///
1554    /// # Errors
1555    ///
1556    /// Returns a `WebsocketError` if:
1557    /// - Connection fails
1558    /// - Connection times out after 10 seconds
1559    ///
1560    /// # Behavior
1561    ///
1562    /// - Checks if already connected and returns early if so
1563    /// - Prevents multiple concurrent connection attempts
1564    /// - Sets a WebSocket handler for the connection pool
1565    /// - Attempts to connect with a 10-second timeout
1566    ///
1567    /// # Returns
1568    ///
1569    /// `Ok(())` if connection is successful, otherwise a `WebsocketError`
1570    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    /// Disconnects the WebSocket connection.
1606    ///
1607    /// # Returns
1608    ///
1609    /// `Ok(())` if disconnection is successful, otherwise a `WebsocketError`
1610    ///
1611    /// # Errors
1612    ///
1613    /// Returns a `WebsocketError` if:
1614    /// - Disconnection fails
1615    /// - Connection is not established
1616    ///
1617    pub async fn disconnect(&self) -> Result<(), WebsocketError> {
1618        self.common.disconnect().await
1619    }
1620
1621    /// Checks if the WebSocket connection is currently established.
1622    ///
1623    /// # Returns
1624    ///
1625    /// `true` if the connection is active, `false` otherwise.
1626    pub async fn is_connected(&self) -> bool {
1627        self.common.is_connected(None).await
1628    }
1629
1630    /// Sends a ping to the WebSocket server to maintain the connection.
1631    ///
1632    /// This method calls the underlying connection's ping mechanism to check
1633    /// and keep the WebSocket connection alive.
1634    pub async fn ping_server(&self) {
1635        self.common.ping_server().await;
1636    }
1637
1638    /// Sends a WebSocket message with the specified method and payload.
1639    ///
1640    /// This method prepares and sends a WebSocket request with optional API key and signature.
1641    /// It handles connection status, generates a unique request ID, and processes the response.
1642    ///
1643    /// # Arguments
1644    ///
1645    /// * `method` - The WebSocket API method to be called
1646    /// * `payload` - A map of parameters to be sent with the request
1647    /// * `options` - Configuration options for message sending (API key, signing)
1648    ///
1649    /// # Returns
1650    ///
1651    /// A deserialized response of type `R` or a `WebsocketError` if the request fails
1652    ///
1653    /// # Panics
1654    ///
1655    /// Panics if:
1656    ///
1657    /// - The WebSocket is not connected
1658    /// - The request cannot be processed
1659    /// - No response is received within the timeout
1660    ///
1661    /// # Errors
1662    ///
1663    /// Returns `WebsocketError` if:
1664    /// - The WebSocket is not connected
1665    /// - The request cannot be processed
1666    /// - No response is received within the timeout
1667    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    /// Prepares a WebSocket URL by appending a validated time unit parameter.
1778    ///
1779    /// This method checks if a time unit is configured and validates it. If valid,
1780    /// the time unit is appended to the URL as a query parameter. If no time unit
1781    /// is specified or the validation fails, the original URL is returned.
1782    ///
1783    /// # Arguments
1784    ///
1785    /// * `ws_url` - The base WebSocket URL to be modified
1786    ///
1787    /// # Returns
1788    ///
1789    /// A modified URL with the time unit parameter, or the original URL if no
1790    /// modification is possible
1791    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    /// Handles the WebSocket connection opening event, attempting to re-establish a session logon if needed.
1819    ///
1820    /// This method checks if a session logon request exists and has not already been logged on.
1821    /// If conditions are met, it attempts to send a session re-logon message and update the connection state.
1822    ///
1823    /// # Arguments
1824    ///
1825    /// * `_url` - The WebSocket connection URL (unused)
1826    /// * `connection` - The WebSocket connection context
1827    ///
1828    /// # Behavior
1829    ///
1830    /// - Checks for an existing session logon request
1831    /// - Verifies the session is not already logged on
1832    /// - Attempts to send a re-logon message
1833    /// - Updates connection state upon successful re-logon
1834    /// - Logs errors if re-logon dispatch fails
1835    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    /// Handles incoming WebSocket messages by parsing the JSON payload and processing pending requests.
1949    ///
1950    /// This method is responsible for:
1951    /// - Parsing the received WebSocket message as JSON
1952    /// - Matching the message to a pending request by its ID
1953    /// - Sending the response back to the original request's completion channel
1954    /// - Handling both successful and error responses
1955    ///
1956    /// # Arguments
1957    ///
1958    /// * `data` - The raw WebSocket message as a string
1959    /// * `connection` - The WebSocket connection context associated with the message
1960    ///
1961    /// # Behavior
1962    ///
1963    /// - If message parsing fails, logs an error and returns
1964    /// - For known request IDs, sends the response to the corresponding completion channel
1965    /// - Warns about responses for unknown or timed-out requests
1966    /// - Differentiates between successful (status < 400) and error responses
1967    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    /// Generates the URL to use for reconnecting to a WebSocket connection.
2067    ///
2068    /// # Arguments
2069    ///
2070    /// * `default_url` - The original URL to potentially modify for reconnection
2071    /// * `_connection` - The WebSocket connection context (currently unused)
2072    ///
2073    /// # Returns
2074    ///
2075    /// A `String` representing the URL to use for reconnecting
2076    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    /// Creates a new `WebsocketStreams` instance with the given configuration and connection pool.
2096    ///
2097    /// # Arguments
2098    ///
2099    /// * `configuration` - Configuration settings for the WebSocket streams
2100    /// * `connection_pool` - A vector of WebSocket connections to use
2101    /// * `url_paths` - A vector of URL paths for the streams
2102    ///
2103    /// # Returns
2104    ///
2105    /// An `Arc`-wrapped `WebsocketStreams` instance
2106    ///
2107    /// # Panics
2108    ///
2109    /// Panics if the `reconnect_delay` cannot be converted to `usize`
2110    #[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    /// Establishes a WebSocket connection for the given streams.
2148    ///
2149    /// This method attempts to connect to a WebSocket server using the connection pool.
2150    /// If a connection is already established or in progress, it returns immediately.
2151    ///
2152    /// # Arguments
2153    ///
2154    /// * `streams` - A vector of stream identifiers to connect to
2155    ///
2156    /// # Returns
2157    ///
2158    /// A `Result` indicating whether the connection was successful or an error occurred
2159    ///
2160    /// # Errors
2161    ///
2162    /// Returns a `WebsocketError` if the connection fails or times out after 10 seconds
2163    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    /// Disconnects all WebSocket connections and clears associated state.
2243    ///
2244    /// # Returns
2245    ///
2246    /// A `Result` indicating whether the disconnection was successful or an error occurred
2247    ///
2248    /// # Errors
2249    ///
2250    /// Returns a `WebsocketError` if there are issues during the disconnection process
2251    ///
2252    /// # Side Effects
2253    ///
2254    /// - Clears stream callbacks for all connections
2255    /// - Clears pending subscriptions for all connections
2256    /// - Removes all connection stream mappings
2257    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    /// Checks if the WebSocket connection is currently active.
2268    ///
2269    /// # Returns
2270    ///
2271    /// `true` if the WebSocket connection is established, `false` otherwise.
2272    pub async fn is_connected(&self) -> bool {
2273        self.common.is_connected(None).await
2274    }
2275
2276    /// Sends a ping to the WebSocket server to maintain the connection.
2277    ///
2278    /// This method delegates the ping operation to the underlying common WebSocket connection.
2279    /// It is typically used to keep the connection alive and check its status.
2280    ///
2281    /// # Side Effects
2282    ///
2283    /// Sends a ping request to the WebSocket server through the common connection.
2284    pub async fn ping_server(&self) {
2285        self.common.ping_server().await;
2286    }
2287
2288    /// Subscribes to multiple WebSocket streams, handling connection and queuing logic.
2289    ///
2290    /// # Arguments
2291    ///
2292    /// * `streams` - A vector of stream names to subscribe to
2293    /// * `id` - An optional request identifier for the subscription
2294    /// * `url_path` - An optional URL path for the subscription
2295    ///
2296    /// # Behavior
2297    ///
2298    /// - Filters out streams already subscribed
2299    /// - Assigns streams to appropriate connections
2300    /// - Handles subscription for active connections
2301    /// - Queues subscriptions for inactive connections
2302    ///
2303    /// # Side Effects
2304    ///
2305    /// - Sends subscription payloads for active connections
2306    /// - Adds pending subscriptions for inactive connections
2307    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    /// Unsubscribes from specified WebSocket streams.
2350    ///
2351    /// # Arguments
2352    ///
2353    /// * `streams` - A vector of stream names to unsubscribe from
2354    /// * `id` - An optional request identifier for the unsubscription
2355    /// * `url_path` - An optional URL path for the unsubscription
2356    ///
2357    /// # Behavior
2358    ///
2359    /// - Validates the request identifier or generates a random one
2360    /// - Checks for active connections and subscribed streams
2361    /// - Sends unsubscribe payload for streams with active callbacks
2362    /// - Removes stream from connection streams and callbacks
2363    ///
2364    /// # Side Effects
2365    ///
2366    /// - Sends unsubscribe request to WebSocket server
2367    /// - Removes stream tracking from internal state
2368    ///
2369    /// # Async
2370    ///
2371    /// This method is asynchronous and requires `.await` when called
2372    ///
2373    /// # Panics
2374    ///
2375    /// This method may panic if the request identifier is not valid.
2376    ///
2377    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    /// Checks if a specific stream is currently subscribed.
2450    ///
2451    /// # Arguments
2452    ///
2453    /// * `stream` - The stream identifier to check for subscription status
2454    ///
2455    /// # Returns
2456    ///
2457    /// `true` if the stream is subscribed, `false` otherwise
2458    ///
2459    /// # Async
2460    ///
2461    /// This method is asynchronous and requires `.await` when called
2462    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    /// Generates a unique key for a stream based on its name and optional URL path.
2474    ///
2475    /// # Arguments
2476    ///
2477    /// * `stream` - The name of the stream
2478    /// * `url_path` - An optional URL path associated with the stream
2479    ///
2480    /// # Returns
2481    ///
2482    /// A `String` representing the unique key for the stream
2483    ///
2484    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    /// Prepares a WebSocket URL for streaming with optional stream names and time unit configuration.
2492    ///
2493    /// # Arguments
2494    ///
2495    /// * `streams` - A slice of stream names to be included in the URL
2496    /// * `url_path` - An optional path to append to the base WebSocket URL
2497    ///
2498    /// # Returns
2499    ///
2500    /// A fully constructed WebSocket URL with optional stream and time unit parameters
2501    ///
2502    /// # Notes
2503    ///
2504    /// - If no time unit is specified, the base URL is returned
2505    /// - Validates and appends the time unit parameter if provided and valid
2506    /// - Handles URL parameter separator based on existing query parameters
2507    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    /// Handles stream assignment by finding or creating WebSocket connections for a list of streams.
2548    ///
2549    /// This method attempts to assign streams to existing WebSocket connections or creates new
2550    /// connections if needed. It groups streams by their assigned connections and handles scenarios
2551    /// such as closed or pending reconnection connections.
2552    ///
2553    /// # Arguments
2554    ///
2555    /// * `streams` - A vector of stream names to be assigned
2556    /// * `url_path` - An optional URL path associated with the streams
2557    ///
2558    /// # Returns
2559    ///
2560    /// A vector of tuples containing WebSocket connections and their associated streams
2561    ///
2562    /// # Errors
2563    ///
2564    /// Returns an empty result if no connections can be established for the streams
2565    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    /// Sends a WebSocket subscription payload for the specified streams.
2626    ///
2627    /// # Arguments
2628    ///
2629    /// * `connection` - The WebSocket connection to send the subscription on
2630    /// * `streams` - A vector of stream names to subscribe to
2631    /// * `id` - An optional request ID for the subscription (will be randomly generated if not provided)
2632    ///
2633    /// # Remarks
2634    ///
2635    /// This method constructs a SUBSCRIBE payload, logs it, and sends it asynchronously using the WebSocket connection.
2636    /// If serialization fails, an error is logged and the method returns without sending.
2637    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    /// Handles the WebSocket connection opening by processing any pending subscriptions.
2677    ///
2678    /// This method is called when a WebSocket connection is established. It retrieves
2679    /// any pending stream subscriptions from the connection state and sends them
2680    /// immediately using the `send_subscription_payload` method.
2681    ///
2682    /// # Arguments
2683    ///
2684    /// * `_url` - The URL of the WebSocket connection (unused)
2685    /// * `connection` - The WebSocket connection that has just been opened
2686    ///
2687    /// # Remarks
2688    ///
2689    /// If there are any pending subscriptions, they are sent as a batch subscription
2690    /// payload. The method uses a lock to safely access and clear the pending subscriptions
2691    /// from the connection state.
2692    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    /// Handles incoming WebSocket stream messages by parsing the JSON payload and invoking registered stream callbacks.
2707    ///
2708    /// This method processes WebSocket messages with a specific structure, extracting the stream name and data.
2709    /// It retrieves and executes any registered callbacks associated with the stream name.
2710    ///
2711    /// # Arguments
2712    ///
2713    /// * `data` - The raw WebSocket message as a JSON-formatted string
2714    /// * `connection` - The WebSocket connection through which the message was received
2715    ///
2716    /// # Behavior
2717    ///
2718    /// - Parses the JSON message
2719    /// - Extracts the stream name and data payload
2720    /// - Looks up and invokes any registered callbacks for the stream
2721    /// - Silently returns if message parsing or stream extraction fails
2722    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    /// Retrieves the reconnection URL for a specific WebSocket connection by identifying all streams associated with that connection.
2758    ///
2759    /// # Arguments
2760    ///
2761    /// * `_default_url` - A default URL that can be used if no specific reconnection URL is determined
2762    /// * `connection` - The WebSocket connection for which to generate a reconnection URL
2763    ///
2764    /// # Returns
2765    ///
2766    /// A URL string that can be used to reconnect to the WebSocket, based on the streams associated with the given connection
2767    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    /// Registers a callback function for a specific event on the WebSocket stream.
2811    ///
2812    /// This method currently only supports the "message" event. When a message is received,
2813    /// the provided callback function will be invoked with the deserialized payload.
2814    ///
2815    /// # Arguments
2816    ///
2817    /// * `event` - The event type to listen for (currently only "message" is supported)
2818    /// * `callback_fn` - A function that will be called with the deserialized message payload
2819    ///
2820    /// # Errors
2821    ///
2822    /// Logs an error if the payload cannot be deserialized into the expected type
2823    ///
2824    /// # Examples
2825    ///
2826    ///
2827    /// stream.on("message", |data: `MyType`| {
2828    ///     // Handle the deserialized message
2829    /// });
2830    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    /// Synchronously sets a message callback for the WebSocket stream on the current thread.
2888    ///
2889    /// # Arguments
2890    ///
2891    /// * `callback_fn` - A function that will be called with the deserialized message payload
2892    ///
2893    /// # Panics
2894    ///
2895    /// Panics if the thread runtime fails to be created or if the thread join fails
2896    ///
2897    /// # Examples
2898    ///
2899    ///
2900    /// let stream = `Arc::new(WebsocketStream::new())`;
2901    /// `stream.on_message(|data`: `MyType`| {
2902    ///     // Handle the deserialized message
2903    /// });
2904    ///
2905    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    /// Unsubscribes from the current WebSocket stream and removes the associated callback.
2925    ///
2926    /// This method performs the following actions:
2927    /// - Removes the current callback associated with the stream
2928    /// - Removes the callback from the connection's stream callbacks
2929    /// - Asynchronously unsubscribes from the stream using the WebSocket streams base
2930    ///
2931    /// # Panics
2932    ///
2933    /// Panics if the stream is not subscribed to
2934    ///
2935    /// # Notes
2936    /// - If no callback is present, no action is taken
2937    /// - Spawns an asynchronous task to handle the unsubscription process
2938    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
2983/// Creates a new WebSocket stream handler for the specified stream or ID.
2984/// This function subscribes to the stream if the WebSocket base is of type `WebsocketStreams`.
2985///
2986/// # Arguments
2987///
2988/// * `websocket_base` - The base WebSocket instance (either `WebsocketStreams` or `WebsocketApi`)
2989/// * `stream_or_id` - The stream name or identifier to subscribe to
2990/// * `id` - An optional request identifier for the subscription
2991/// * `url_path` - An optional URL path for the subscription
2992///
2993/// # Returns
2994///
2995/// A new `WebsocketStream` instance.
2996///
2997pub 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    /// RAII guard that aborts a spawned task when dropped.
3064    ///
3065    /// Used by tests to bound the lifetime of mock listener / spawned helper tasks
3066    /// to the scope of the test, so they don't leak into `TOKIO_SHARED_RT` and
3067    /// add nondeterministic background load to subsequent tests.
3068    struct AbortOnDrop(tokio::task::JoinHandle<()>);
3069
3070    impl Drop for AbortOnDrop {
3071        fn drop(&mut self) {
3072            self.0.abort();
3073        }
3074    }
3075
3076    /// Spawn a mock WebSocket listener that accepts a single connection and
3077    /// holds it open until the client disconnects, then exits. The returned
3078    /// guard aborts the task when dropped.
3079    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                    // 1. Real echo WebSocket server (the "origin").
4395                    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                            // Echo one message back, prefixed so we can prove
4403                            // the round-trip actually reached the origin.
4404                            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                    // 2. A separate TCP relay standing in for a custom transport
4414                    //    (e.g. a SOCKS5 tunnel). It blindly pipes bytes between
4415                    //    the client and the origin, and counts the bytes it
4416                    //    carried so the test can assert traffic really flowed
4417                    //    through it rather than directly to the origin.
4418                    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                            // Client -> origin: count bytes as they flow so the
4432                            // test can observe traffic live, not just at close.
4433                            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                    // 3. Custom handshake: rewrite the request URI so the
4453                    //    transport connects to the relay instead of the origin.
4454                    //    This is the essence of the feature — the caller decides
4455                    //    how/where the underlying connection is opened.
4456                    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                                // Redirect the transport target to the relay.
4464                                *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                    // 4. Drive the SDK's real create_websocket with the custom
4476                    //    handshake, pointing at the origin address (which the
4477                    //    handshake will override to the relay).
4478                    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                    // 5. Exchange a real message round-trip over the tunneled
4488                    //    connection and verify it reached the echo origin.
4489                    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                            // Explicitly sending an AbnormalClose (1006) is not allowed by the websocket
5277                            // protocol and will be converted into Protocol (1002), which the client receives.
5278                            //
5279                            // Abruptly dropping the connection simulates a real abnormal close, which is
5280                            // handled as an error in the websocket protocol
5281                            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                // Regression test: when wait_for_reply=true, send() registers a
5826                // pending_requests entry BEFORE writing to ws_write_tx so that a
5827                // racing response can never be lost. If the write itself fails
5828                // afterwards, the pending entry must be removed so it doesn't
5829                // leak (otherwise close_connection_gracefully would block on it
5830                // until the 30s drain timeout, and on_message could match a
5831                // future request that reuses the id).
5832                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                        // let req = st
6464                        //     .session_logon_req
6465                        //     .as_ref()
6466                        //     .expect("session_logon_req should be Some(_)");
6467                        // assert_eq!(req.method, "method");
6468                        // let mut expected = BTreeMap::new();
6469                        // expected.insert("ok".to_string(), Value::Bool(true));
6470                        // assert_eq!(
6471                        //     req.payload, expected,
6472                        //     "stored payload should be {{ \"ok\": true }}"
6473                        // );
6474                        // assert!(
6475                        //     req.options.is_session_logon.unwrap_or(false),
6476                        //     "expected options.is_session_logon = true"
6477                        // );
6478                    }
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            // Test ConnectionReset
9858            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            // Test ConnectionAborted
9868            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            // Test TimedOut
9878            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            // Test UnexpectedEof
9888            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            // Test PermissionDenied
9898            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            // Test other IO errors default to NetworkInterruption
9908            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            // Protocol error -> ProtocolViolation
9921            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            // UTF8 error -> ProtocolViolation
9932            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            // Normal closure
9944            let reason = WebsocketConnectionFailureReason::from_close_code(1000, false);
9945            assert!(matches!(
9946                reason,
9947                WebsocketConnectionFailureReason::NormalClose
9948            ));
9949            assert!(!reason.should_reconnect());
9950
9951            // Going away (server restart) -> ServerTemporaryError
9952            let reason = WebsocketConnectionFailureReason::from_close_code(1001, false);
9953            assert!(matches!(
9954                reason,
9955                WebsocketConnectionFailureReason::ServerTemporaryError
9956            ));
9957            assert!(reason.should_reconnect());
9958
9959            // Protocol error -> ProtocolViolation
9960            let reason = WebsocketConnectionFailureReason::from_close_code(1002, false);
9961            assert!(matches!(
9962                reason,
9963                WebsocketConnectionFailureReason::ProtocolViolation
9964            ));
9965            assert!(!reason.should_reconnect());
9966
9967            // Abnormal closure -> UnexpectedClose
9968            let reason = WebsocketConnectionFailureReason::from_close_code(1006, false);
9969            assert!(matches!(
9970                reason,
9971                WebsocketConnectionFailureReason::UnexpectedClose
9972            ));
9973            assert!(reason.should_reconnect());
9974
9975            // Policy violation -> PermanentServerError
9976            let reason = WebsocketConnectionFailureReason::from_close_code(1008, false);
9977            assert!(matches!(
9978                reason,
9979                WebsocketConnectionFailureReason::PermanentServerError
9980            ));
9981            assert!(!reason.should_reconnect());
9982
9983            // Server error -> ServerTemporaryError
9984            let reason = WebsocketConnectionFailureReason::from_close_code(1011, false);
9985            assert!(matches!(
9986                reason,
9987                WebsocketConnectionFailureReason::ServerTemporaryError
9988            ));
9989            assert!(reason.should_reconnect());
9990
9991            // TLS handshake failure -> ConfigurationError
9992            let reason = WebsocketConnectionFailureReason::from_close_code(1015, false);
9993            assert!(matches!(
9994                reason,
9995                WebsocketConnectionFailureReason::ConfigurationError
9996            ));
9997            assert!(!reason.should_reconnect());
9998
9999            // Business/application errors -> PermanentServerError
10000            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            // Unknown codes default to UnexpectedClose
10015            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            // Any code with user_initiated=true should return UserInitiatedClose
10026            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            // Reconnectable failures
10051            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            // Non-reconnectable failures
10058            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}