websocket_toolkit/
lib.rs

1#![allow(dead_code)]
2#![allow(unused_imports)]
3#![allow(unused_variables)]
4
5/// Module for WebSocket connection handling.
6///
7/// This module contains functionality to manage WebSocket connections,
8/// including connection establishment, message sending, and graceful disconnection.
9pub mod connection;
10
11/// Module for reconnection strategies.
12///
13/// This module defines strategies for handling reconnection attempts
14/// with retry logic and exponential backoff mechanisms.
15pub mod reconnection;
16
17/// Module for message handling, including serialization and deserialization.
18///
19/// This module supports handling messages in different formats, such as JSON
20/// and CBOR, for serialization and deserialization operations.
21pub mod messages;
22
23/// Module for WebSocket keep-alive mechanisms.
24///
25/// This module provides a mechanism to maintain active WebSocket connections
26/// by sending periodic pings to the server to prevent timeouts.
27pub mod keep_alive;
28
29/// Module for WebSocket controller logic, managing connections and communication.
30///
31/// This module defines a controller that centralizes WebSocket connection
32/// management, message handling, and reconnection strategies.
33pub mod controller;
34
35use crate::reconnection::Connectable;
36use tokio_tungstenite::tungstenite::protocol::Message;
37use futures_util::{StreamExt, SinkExt};
38use tokio::sync::Mutex;
39
40/// A mock WebSocket client for testing purposes.
41///
42/// This struct simulates a WebSocket client that always fails to connect,
43/// which is useful for testing reconnection logic and error handling.
44pub struct MockWebSocketClient;
45
46#[async_trait::async_trait]
47impl Connectable for MockWebSocketClient {
48    /// Simulates a connection failure for the mock WebSocket client.
49    ///
50    /// This method always returns a `ConnectionClosed` error, simulating
51    /// a scenario where the WebSocket client cannot establish a connection.
52    async fn connect(&self) -> Result<(), tokio_tungstenite::tungstenite::Error> {
53        Err(tokio_tungstenite::tungstenite::Error::ConnectionClosed)
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60    use super::connection::WebSocketClient;
61    use super::messages::{MessageHandler, MessageFormat};
62    use super::reconnection::ReconnectStrategy;
63    use super::controller::WebSocketController;
64    use tokio::net::TcpListener;
65    use tokio::time::Duration;
66    use log::error;
67    use tokio_tungstenite::accept_async;
68    use tokio_tungstenite::tungstenite::protocol::Message;
69    use std::sync::Arc;
70    use futures_util::{StreamExt, SinkExt};
71
72    /// Tests the ability of `WebSocketClient` to establish a connection with a mock server.
73    ///
74    /// This test sets up a mock WebSocket server, attempts to connect using the client,
75    /// and verifies successful connection after retries.
76    #[tokio::test]
77    async fn test_websocket_client_connection() {
78        // Bind to an available port.
79        let listener = TcpListener::bind("127.0.0.1:0")
80            .await
81            .expect("Failed to bind server");
82
83        let local_addr = listener.local_addr().expect("Failed to get local address");
84        let client_url = format!("ws://{}", local_addr);
85
86        // Set up the mock server.
87        let server_handle = tokio::spawn(async move {
88            if let Ok((stream, _)) = listener.accept().await {
89                let mut ws_stream = accept_async(stream)
90                    .await
91                    .expect("Failed to accept WebSocket connection");
92
93                while let Some(Ok(Message::Ping(_))) = ws_stream.next().await {
94                    ws_stream
95                        .send(Message::Pong(Vec::new()))
96                        .await
97                        .expect("Failed to send pong");
98                }
99            }
100        });
101
102        tokio::time::sleep(Duration::from_secs(1)).await;
103
104        let client = WebSocketClient::new(&client_url, 3);
105
106        let mut attempts = 0;
107        let max_attempts = 3;
108        let mut connected = false;
109
110        while attempts < max_attempts {
111            match client.connect().await {
112                Ok(_) => {
113                    connected = true;
114                    break;
115                }
116                Err(e) => {
117                    error!("Attempt {} failed to connect to WebSocket server: {}", attempts + 1, e);
118                    tokio::time::sleep(Duration::from_secs(4)).await;
119                    attempts += 1;
120                }
121            }
122        }
123
124        assert!(connected, "Expected successful WebSocket connection after retries");
125
126        // Cleanup after the test.
127        server_handle.abort();
128    }
129
130    /// Tests the reconnection strategy using exponential backoff.
131    ///
132    /// This test verifies that the reconnection strategy stops after the
133    /// maximum number of retries if the connection cannot be re-established.
134    #[tokio::test]
135    async fn test_reconnect_strategy_with_backoff() {
136        let reconnect_strategy = ReconnectStrategy::new(3, 1);
137        let client = Arc::new(MockWebSocketClient);
138
139        let reconnect_result = reconnect_strategy.reconnect(client).await;
140        assert!(reconnect_result.is_none(), "Expected reconnection to stop after max retries");
141    }
142
143    /// Tests the full lifecycle of a WebSocket controller.
144    ///
145    /// This test verifies the controller's ability to manage WebSocket connections,
146    /// including initial connection, reconnection, and keep-alive mechanisms.
147    #[tokio::test]
148    async fn test_websocket_controller_lifecycle() {
149        let mut controller = WebSocketController::new("ws://node_server:9001", 3, Some(5));
150        let connect_result = controller.connect_and_send_message(b"Hello, WebSocket!").await;
151        assert!(
152            connect_result.is_ok(),
153            "Expected connection to succeed, but it failed: {:?}",
154            connect_result.err()
155        );
156
157        let reconnect_result = controller.reconnect_if_needed().await;
158        assert!(
159            reconnect_result.is_ok(),
160            "Reconnection logic failed: {:?}",
161            reconnect_result.err()
162        );
163
164        let ws_stream = Arc::new(Mutex::new(
165            controller.connect().await.expect("Failed to connect to WebSocket server"),
166        ));
167
168        let maintain_result = controller.maintain_connection(ws_stream.clone()).await;
169        assert!(
170            maintain_result.is_ok(),
171            "Failed to maintain WebSocket connection: {:?}",
172            maintain_result.err()
173        );
174    }
175
176    /// Tests serialization and deserialization of messages in JSON and CBOR formats.
177    ///
178    /// This test ensures that messages are correctly serialized into both JSON and CBOR
179    /// formats and can be deserialized back into their original structure.
180    #[test]
181    fn test_message_serialization_and_deserialization() {
182        let message = "Hello, WebSocket!";
183
184        let serialized_json = MessageHandler::serialize(&message, MessageFormat::Json).unwrap();
185        assert!(!serialized_json.is_empty(), "Expected non-empty serialized JSON data");
186
187        let deserialized_json: Option<String> =
188            MessageHandler::deserialize(&serialized_json, MessageFormat::Json).expect("Failed to deserialize JSON");
189        assert_eq!(deserialized_json, Some(message.to_string()), "Expected deserialized JSON to match original message");
190
191        let serialized_cbor = MessageHandler::serialize(&message, MessageFormat::Cbor).unwrap();
192        assert!(!serialized_cbor.is_empty(), "Expected non-empty serialized CBOR data");
193
194        let deserialized_cbor: Option<String> =
195            MessageHandler::deserialize(&serialized_cbor, MessageFormat::Cbor).expect("Failed to deserialize CBOR");
196        assert_eq!(deserialized_cbor, Some(message.to_string()), "Expected deserialized CBOR to match original message");
197    }
198}