Skip to main content

jugar_probar/
websocket.rs

1//! WebSocket Monitoring (Feature 8)
2//!
3//! Monitor and mock WebSocket connections for testing.
4//!
5//! ## EXTREME TDD: Tests written FIRST per spec
6//!
7//! ## Toyota Way Application
8//!
9//! - **Genchi Genbutsu**: See actual WebSocket traffic
10//! - **Jidoka**: Fail-fast on unexpected messages
11//! - **Kaizen**: Continuous improvement through message inspection
12
13use crate::result::{ProbarError, ProbarResult};
14use serde::{Deserialize, Serialize};
15use std::collections::VecDeque;
16use std::sync::{Arc, Mutex};
17use std::time::Instant;
18
19/// WebSocket connection state
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
21pub enum WebSocketState {
22    /// Connection is being established
23    Connecting,
24    /// Connection is open
25    Open,
26    /// Connection is closing
27    Closing,
28    /// Connection is closed
29    Closed,
30}
31
32/// WebSocket message type
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
34pub enum MessageType {
35    /// Text message
36    Text,
37    /// Binary message
38    Binary,
39    /// Ping message
40    Ping,
41    /// Pong message
42    Pong,
43    /// Close message
44    Close,
45}
46
47/// Direction of WebSocket message
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
49pub enum MessageDirection {
50    /// Message sent from client to server
51    Sent,
52    /// Message received from server
53    Received,
54}
55
56/// A captured WebSocket message
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct WebSocketMessage {
59    /// Message type
60    pub message_type: MessageType,
61    /// Message direction
62    pub direction: MessageDirection,
63    /// Message data (text or base64 encoded binary)
64    pub data: String,
65    /// Raw binary data (if binary message)
66    #[serde(skip)]
67    pub raw_data: Option<Vec<u8>>,
68    /// Timestamp (milliseconds since connection start)
69    pub timestamp_ms: u64,
70    /// Connection ID this message belongs to
71    pub connection_id: String,
72}
73
74impl WebSocketMessage {
75    /// Create a new text message
76    #[must_use]
77    pub fn text(data: &str, direction: MessageDirection, timestamp_ms: u64) -> Self {
78        Self {
79            message_type: MessageType::Text,
80            direction,
81            data: data.to_string(),
82            raw_data: None,
83            timestamp_ms,
84            connection_id: String::new(),
85        }
86    }
87
88    /// Create a new binary message
89    #[must_use]
90    pub fn binary(data: Vec<u8>, direction: MessageDirection, timestamp_ms: u64) -> Self {
91        Self {
92            message_type: MessageType::Binary,
93            direction,
94            data: base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &data),
95            raw_data: Some(data),
96            timestamp_ms,
97            connection_id: String::new(),
98        }
99    }
100
101    /// Create a ping message
102    #[must_use]
103    pub fn ping(timestamp_ms: u64) -> Self {
104        Self {
105            message_type: MessageType::Ping,
106            direction: MessageDirection::Sent,
107            data: String::new(),
108            raw_data: None,
109            timestamp_ms,
110            connection_id: String::new(),
111        }
112    }
113
114    /// Create a pong message
115    #[must_use]
116    pub fn pong(timestamp_ms: u64) -> Self {
117        Self {
118            message_type: MessageType::Pong,
119            direction: MessageDirection::Received,
120            data: String::new(),
121            raw_data: None,
122            timestamp_ms,
123            connection_id: String::new(),
124        }
125    }
126
127    /// Create a close message
128    #[must_use]
129    pub fn close(code: u16, reason: &str, timestamp_ms: u64) -> Self {
130        Self {
131            message_type: MessageType::Close,
132            direction: MessageDirection::Received,
133            data: format!("{}: {}", code, reason),
134            raw_data: None,
135            timestamp_ms,
136            connection_id: String::new(),
137        }
138    }
139
140    /// Set connection ID
141    #[must_use]
142    pub fn with_connection(mut self, connection_id: &str) -> Self {
143        self.connection_id = connection_id.to_string();
144        self
145    }
146
147    /// Check if this is a text message
148    #[must_use]
149    pub const fn is_text(&self) -> bool {
150        matches!(self.message_type, MessageType::Text)
151    }
152
153    /// Check if this is a binary message
154    #[must_use]
155    pub const fn is_binary(&self) -> bool {
156        matches!(self.message_type, MessageType::Binary)
157    }
158
159    /// Check if this message was sent by client
160    #[must_use]
161    pub const fn is_sent(&self) -> bool {
162        matches!(self.direction, MessageDirection::Sent)
163    }
164
165    /// Check if this message was received
166    #[must_use]
167    pub const fn is_received(&self) -> bool {
168        matches!(self.direction, MessageDirection::Received)
169    }
170
171    /// Parse data as JSON
172    pub fn json<T: for<'de> Deserialize<'de>>(&self) -> ProbarResult<T> {
173        let data = serde_json::from_str(&self.data)?;
174        Ok(data)
175    }
176
177    /// Check if data contains a string
178    #[must_use]
179    pub fn contains(&self, s: &str) -> bool {
180        self.data.contains(s)
181    }
182}
183
184/// A tracked WebSocket connection
185#[derive(Debug)]
186pub struct WebSocketConnection {
187    /// Connection ID
188    pub id: String,
189    /// WebSocket URL
190    pub url: String,
191    /// Connection state
192    pub state: WebSocketState,
193    /// Messages on this connection
194    messages: Arc<Mutex<Vec<WebSocketMessage>>>,
195    /// Start time
196    start_time: Instant,
197    /// Close code (if closed)
198    pub close_code: Option<u16>,
199    /// Close reason (if closed)
200    pub close_reason: Option<String>,
201}
202
203impl WebSocketConnection {
204    /// Create a new connection
205    #[must_use]
206    pub fn new(id: &str, url: &str) -> Self {
207        Self {
208            id: id.to_string(),
209            url: url.to_string(),
210            state: WebSocketState::Connecting,
211            messages: Arc::new(Mutex::new(Vec::new())),
212            start_time: Instant::now(),
213            close_code: None,
214            close_reason: None,
215        }
216    }
217
218    /// Open the connection
219    pub fn open(&mut self) {
220        self.state = WebSocketState::Open;
221    }
222
223    /// Close the connection
224    pub fn close(&mut self, code: u16, reason: &str) {
225        self.state = WebSocketState::Closed;
226        self.close_code = Some(code);
227        self.close_reason = Some(reason.to_string());
228    }
229
230    /// Get elapsed time in milliseconds
231    #[must_use]
232    pub fn elapsed_ms(&self) -> u64 {
233        self.start_time.elapsed().as_millis() as u64
234    }
235
236    /// Record a message
237    pub fn record_message(&self, mut message: WebSocketMessage) {
238        message.connection_id = self.id.clone();
239        if let Ok(mut messages) = self.messages.lock() {
240            messages.push(message);
241        }
242    }
243
244    /// Send a text message
245    pub fn send_text(&self, data: &str) {
246        let message = WebSocketMessage::text(data, MessageDirection::Sent, self.elapsed_ms());
247        self.record_message(message);
248    }
249
250    /// Send a binary message
251    pub fn send_binary(&self, data: Vec<u8>) {
252        let message = WebSocketMessage::binary(data, MessageDirection::Sent, self.elapsed_ms());
253        self.record_message(message);
254    }
255
256    /// Receive a text message
257    pub fn receive_text(&self, data: &str) {
258        let message = WebSocketMessage::text(data, MessageDirection::Received, self.elapsed_ms());
259        self.record_message(message);
260    }
261
262    /// Receive a binary message
263    pub fn receive_binary(&self, data: Vec<u8>) {
264        let message = WebSocketMessage::binary(data, MessageDirection::Received, self.elapsed_ms());
265        self.record_message(message);
266    }
267
268    /// Get all messages
269    #[must_use]
270    pub fn messages(&self) -> Vec<WebSocketMessage> {
271        self.messages.lock().map(|m| m.clone()).unwrap_or_default()
272    }
273
274    /// Get sent messages
275    #[must_use]
276    pub fn sent_messages(&self) -> Vec<WebSocketMessage> {
277        self.messages()
278            .into_iter()
279            .filter(|m| m.is_sent())
280            .collect()
281    }
282
283    /// Get received messages
284    #[must_use]
285    pub fn received_messages(&self) -> Vec<WebSocketMessage> {
286        self.messages()
287            .into_iter()
288            .filter(|m| m.is_received())
289            .collect()
290    }
291
292    /// Get message count
293    #[must_use]
294    pub fn message_count(&self) -> usize {
295        self.messages.lock().map(|m| m.len()).unwrap_or(0)
296    }
297
298    /// Check if connection is open
299    #[must_use]
300    pub const fn is_open(&self) -> bool {
301        matches!(self.state, WebSocketState::Open)
302    }
303
304    /// Check if connection is closed
305    #[must_use]
306    pub const fn is_closed(&self) -> bool {
307        matches!(self.state, WebSocketState::Closed)
308    }
309}
310
311/// Mock responses for WebSocket
312#[derive(Debug, Clone)]
313pub struct MockWebSocketResponse {
314    /// Messages to send when conditions are met
315    pub messages: Vec<WebSocketMessage>,
316    /// Delay before sending (ms)
317    pub delay_ms: u64,
318}
319
320impl MockWebSocketResponse {
321    /// Create a new mock response
322    #[must_use]
323    pub fn new() -> Self {
324        Self {
325            messages: Vec::new(),
326            delay_ms: 0,
327        }
328    }
329
330    /// Add a text message
331    #[must_use]
332    pub fn with_text(mut self, data: &str) -> Self {
333        self.messages
334            .push(WebSocketMessage::text(data, MessageDirection::Received, 0));
335        self
336    }
337
338    /// Add a binary message
339    #[must_use]
340    pub fn with_binary(mut self, data: Vec<u8>) -> Self {
341        self.messages.push(WebSocketMessage::binary(
342            data,
343            MessageDirection::Received,
344            0,
345        ));
346        self
347    }
348
349    /// Set delay
350    #[must_use]
351    pub const fn with_delay(mut self, delay_ms: u64) -> Self {
352        self.delay_ms = delay_ms;
353        self
354    }
355}
356
357impl Default for MockWebSocketResponse {
358    fn default() -> Self {
359        Self::new()
360    }
361}
362
363/// A WebSocket mock rule
364#[derive(Debug, Clone)]
365pub struct WebSocketMock {
366    /// URL pattern to match
367    pub url_pattern: String,
368    /// Message pattern to match (for triggered responses)
369    pub message_pattern: Option<String>,
370    /// Response to send
371    pub response: MockWebSocketResponse,
372    /// Whether this is a one-time mock
373    pub once: bool,
374    /// Whether this mock has been used
375    pub used: bool,
376}
377
378impl WebSocketMock {
379    /// Create a new mock
380    #[must_use]
381    pub fn new(url_pattern: &str) -> Self {
382        Self {
383            url_pattern: url_pattern.to_string(),
384            message_pattern: None,
385            response: MockWebSocketResponse::new(),
386            once: false,
387            used: false,
388        }
389    }
390
391    /// Set response for when connection opens
392    #[must_use]
393    pub fn on_open(mut self, response: MockWebSocketResponse) -> Self {
394        self.response = response;
395        self
396    }
397
398    /// Set response for when message is received
399    #[must_use]
400    pub fn on_message(mut self, pattern: &str, response: MockWebSocketResponse) -> Self {
401        self.message_pattern = Some(pattern.to_string());
402        self.response = response;
403        self
404    }
405
406    /// Make this a one-time mock
407    #[must_use]
408    pub const fn once(mut self) -> Self {
409        self.once = true;
410        self
411    }
412
413    /// Check if URL matches
414    #[must_use]
415    pub fn matches_url(&self, url: &str) -> bool {
416        if self.once && self.used {
417            return false;
418        }
419        url.contains(&self.url_pattern)
420    }
421
422    /// Check if message matches
423    #[must_use]
424    pub fn matches_message(&self, message: &str) -> bool {
425        if self.once && self.used {
426            return false;
427        }
428        self.message_pattern
429            .as_ref()
430            .is_some_and(|p| message.contains(p))
431    }
432
433    /// Mark as used
434    pub fn mark_used(&mut self) {
435        self.used = true;
436    }
437}
438
439/// WebSocket monitor for tracking connections
440#[derive(Debug)]
441pub struct WebSocketMonitor {
442    /// Active connections
443    connections: Arc<Mutex<Vec<WebSocketConnection>>>,
444    /// Mock rules
445    mocks: Vec<WebSocketMock>,
446    /// Message queue for pending mock responses
447    pending_responses: VecDeque<(String, MockWebSocketResponse)>,
448    /// Whether monitoring is active
449    active: bool,
450    /// Connection counter
451    connection_counter: u64,
452}
453
454impl Default for WebSocketMonitor {
455    fn default() -> Self {
456        Self::new()
457    }
458}
459
460impl WebSocketMonitor {
461    /// Create a new WebSocket monitor
462    #[must_use]
463    pub fn new() -> Self {
464        Self {
465            connections: Arc::new(Mutex::new(Vec::new())),
466            mocks: Vec::new(),
467            pending_responses: VecDeque::new(),
468            active: false,
469            connection_counter: 0,
470        }
471    }
472
473    /// Start monitoring
474    pub fn start(&mut self) {
475        self.active = true;
476    }
477
478    /// Stop monitoring
479    pub fn stop(&mut self) {
480        self.active = false;
481    }
482
483    /// Check if monitoring is active
484    #[must_use]
485    pub const fn is_active(&self) -> bool {
486        self.active
487    }
488
489    /// Add a mock rule
490    pub fn mock(&mut self, mock: WebSocketMock) {
491        self.mocks.push(mock);
492    }
493
494    /// Create a new connection
495    pub fn connect(&mut self, url: &str) -> String {
496        self.connection_counter += 1;
497        let id = format!("ws_{}", self.connection_counter);
498
499        let mut connection = WebSocketConnection::new(&id, url);
500        connection.open();
501
502        // Check for on_open mocks
503        for mock in &mut self.mocks {
504            if mock.matches_url(url) && mock.message_pattern.is_none() {
505                self.pending_responses
506                    .push_back((id.clone(), mock.response.clone()));
507                mock.mark_used();
508            }
509        }
510
511        if let Ok(mut connections) = self.connections.lock() {
512            connections.push(connection);
513        }
514
515        id
516    }
517
518    /// Close a connection
519    pub fn disconnect(&mut self, connection_id: &str, code: u16, reason: &str) {
520        if let Ok(mut connections) = self.connections.lock() {
521            if let Some(conn) = connections.iter_mut().find(|c| c.id == connection_id) {
522                conn.close(code, reason);
523            }
524        }
525    }
526
527    /// Send a message on a connection
528    pub fn send(&mut self, connection_id: &str, message: &str) {
529        if let Ok(connections) = self.connections.lock() {
530            if let Some(conn) = connections.iter().find(|c| c.id == connection_id) {
531                conn.send_text(message);
532
533                // Check for message-triggered mocks
534                for mock in &mut self.mocks {
535                    if mock.matches_url(&conn.url) && mock.matches_message(message) {
536                        self.pending_responses
537                            .push_back((connection_id.to_string(), mock.response.clone()));
538                        mock.mark_used();
539                    }
540                }
541            }
542        }
543    }
544
545    /// Receive a message on a connection
546    pub fn receive(&self, connection_id: &str, message: &str) {
547        if let Ok(connections) = self.connections.lock() {
548            if let Some(conn) = connections.iter().find(|c| c.id == connection_id) {
549                conn.receive_text(message);
550            }
551        }
552    }
553
554    /// Get pending mock responses
555    #[must_use]
556    pub fn take_pending_responses(&mut self) -> Vec<(String, MockWebSocketResponse)> {
557        self.pending_responses.drain(..).collect()
558    }
559
560    /// Get all connections
561    #[must_use]
562    pub fn connections(&self) -> Vec<String> {
563        self.connections
564            .lock()
565            .map(|c| c.iter().map(|conn| conn.id.clone()).collect())
566            .unwrap_or_default()
567    }
568
569    /// Get connection by ID
570    pub fn get_connection(&self, connection_id: &str) -> Option<Vec<WebSocketMessage>> {
571        self.connections.lock().ok().and_then(|connections| {
572            connections
573                .iter()
574                .find(|c| c.id == connection_id)
575                .map(|c| c.messages())
576        })
577    }
578
579    /// Get all messages across all connections
580    #[must_use]
581    pub fn all_messages(&self) -> Vec<WebSocketMessage> {
582        self.connections
583            .lock()
584            .map(|connections| connections.iter().flat_map(|c| c.messages()).collect())
585            .unwrap_or_default()
586    }
587
588    /// Get connection count
589    #[must_use]
590    pub fn connection_count(&self) -> usize {
591        self.connections.lock().map(|c| c.len()).unwrap_or(0)
592    }
593
594    /// Get active connection count
595    #[must_use]
596    pub fn active_connection_count(&self) -> usize {
597        self.connections
598            .lock()
599            .map(|c| c.iter().filter(|conn| conn.is_open()).count())
600            .unwrap_or(0)
601    }
602
603    /// Assert a message was sent
604    pub fn assert_sent(&self, pattern: &str) -> ProbarResult<()> {
605        let messages = self.all_messages();
606        let found = messages.iter().any(|m| m.is_sent() && m.contains(pattern));
607        if !found {
608            return Err(ProbarError::AssertionFailed {
609                message: format!(
610                    "Expected sent message containing '{}', but none found",
611                    pattern
612                ),
613            });
614        }
615        Ok(())
616    }
617
618    /// Assert a message was received
619    pub fn assert_received(&self, pattern: &str) -> ProbarResult<()> {
620        let messages = self.all_messages();
621        let found = messages
622            .iter()
623            .any(|m| m.is_received() && m.contains(pattern));
624        if !found {
625            return Err(ProbarError::AssertionError {
626                message: format!(
627                    "Expected received message containing '{}', but none found",
628                    pattern
629                ),
630            });
631        }
632        Ok(())
633    }
634
635    /// Assert connection was made to URL
636    pub fn assert_connected(&self, url_pattern: &str) -> ProbarResult<()> {
637        let found = self
638            .connections
639            .lock()
640            .map(|connections| connections.iter().any(|c| c.url.contains(url_pattern)))
641            .unwrap_or(false);
642
643        if !found {
644            return Err(ProbarError::AssertionError {
645                message: format!(
646                    "Expected connection to URL containing '{}', but none found",
647                    url_pattern
648                ),
649            });
650        }
651        Ok(())
652    }
653
654    /// Clear all connections
655    pub fn clear(&mut self) {
656        if let Ok(mut connections) = self.connections.lock() {
657            connections.clear();
658        }
659        self.mocks.clear();
660        self.pending_responses.clear();
661        self.connection_counter = 0;
662    }
663}
664
665/// Builder for WebSocket monitor
666#[derive(Debug, Default)]
667pub struct WebSocketMonitorBuilder {
668    monitor: WebSocketMonitor,
669}
670
671impl WebSocketMonitorBuilder {
672    /// Create a new builder
673    #[must_use]
674    pub fn new() -> Self {
675        Self::default()
676    }
677
678    /// Add a mock for connection open
679    #[must_use]
680    pub fn mock_open(mut self, url_pattern: &str, response: MockWebSocketResponse) -> Self {
681        self.monitor
682            .mock(WebSocketMock::new(url_pattern).on_open(response));
683        self
684    }
685
686    /// Add a mock for message
687    #[must_use]
688    pub fn mock_message(
689        mut self,
690        url_pattern: &str,
691        message_pattern: &str,
692        response: MockWebSocketResponse,
693    ) -> Self {
694        self.monitor
695            .mock(WebSocketMock::new(url_pattern).on_message(message_pattern, response));
696        self
697    }
698
699    /// Build the monitor
700    #[must_use]
701    pub fn build(self) -> WebSocketMonitor {
702        self.monitor
703    }
704}
705
706#[cfg(test)]
707#[allow(clippy::unwrap_used, clippy::expect_used)]
708mod tests {
709    use super::*;
710
711    mod websocket_message_tests {
712        use super::*;
713
714        #[test]
715        fn test_text_message() {
716            let msg = WebSocketMessage::text("hello", MessageDirection::Sent, 1000);
717            assert!(msg.is_text());
718            assert!(msg.is_sent());
719            assert_eq!(msg.data, "hello");
720            assert_eq!(msg.timestamp_ms, 1000);
721        }
722
723        #[test]
724        fn test_binary_message() {
725            let msg = WebSocketMessage::binary(vec![1, 2, 3], MessageDirection::Received, 500);
726            assert!(msg.is_binary());
727            assert!(msg.is_received());
728            assert!(msg.raw_data.is_some());
729        }
730
731        #[test]
732        fn test_ping_pong() {
733            let ping = WebSocketMessage::ping(100);
734            assert!(matches!(ping.message_type, MessageType::Ping));
735
736            let pong = WebSocketMessage::pong(200);
737            assert!(matches!(pong.message_type, MessageType::Pong));
738        }
739
740        #[test]
741        fn test_close_message() {
742            let close = WebSocketMessage::close(1000, "Normal closure", 500);
743            assert!(matches!(close.message_type, MessageType::Close));
744            assert!(close.data.contains("1000"));
745        }
746
747        #[test]
748        fn test_with_connection() {
749            let msg =
750                WebSocketMessage::text("test", MessageDirection::Sent, 0).with_connection("conn_1");
751            assert_eq!(msg.connection_id, "conn_1");
752        }
753
754        #[test]
755        fn test_contains() {
756            let msg = WebSocketMessage::text("hello world", MessageDirection::Sent, 0);
757            assert!(msg.contains("world"));
758            assert!(!msg.contains("foo"));
759        }
760
761        #[test]
762        fn test_json() {
763            let msg = WebSocketMessage::text(r#"{"name":"test"}"#, MessageDirection::Sent, 0);
764            let data: serde_json::Value = msg.json().unwrap();
765            assert_eq!(data["name"], "test");
766        }
767    }
768
769    mod websocket_connection_tests {
770        use super::*;
771
772        #[test]
773        fn test_new() {
774            let conn = WebSocketConnection::new("conn_1", "ws://example.com");
775            assert_eq!(conn.id, "conn_1");
776            assert_eq!(conn.url, "ws://example.com");
777            assert!(matches!(conn.state, WebSocketState::Connecting));
778        }
779
780        #[test]
781        fn test_open() {
782            let mut conn = WebSocketConnection::new("conn_1", "ws://example.com");
783            conn.open();
784            assert!(conn.is_open());
785            assert!(!conn.is_closed());
786        }
787
788        #[test]
789        fn test_close() {
790            let mut conn = WebSocketConnection::new("conn_1", "ws://example.com");
791            conn.open();
792            conn.close(1000, "Normal closure");
793
794            assert!(conn.is_closed());
795            assert_eq!(conn.close_code, Some(1000));
796            assert_eq!(conn.close_reason, Some("Normal closure".to_string()));
797        }
798
799        #[test]
800        fn test_send_text() {
801            let conn = WebSocketConnection::new("conn_1", "ws://example.com");
802            conn.send_text("hello");
803
804            let messages = conn.messages();
805            assert_eq!(messages.len(), 1);
806            assert!(messages[0].is_sent());
807            assert_eq!(messages[0].data, "hello");
808        }
809
810        #[test]
811        fn test_receive_text() {
812            let conn = WebSocketConnection::new("conn_1", "ws://example.com");
813            conn.receive_text("response");
814
815            let messages = conn.messages();
816            assert_eq!(messages.len(), 1);
817            assert!(messages[0].is_received());
818        }
819
820        #[test]
821        fn test_sent_received_messages() {
822            let conn = WebSocketConnection::new("conn_1", "ws://example.com");
823            conn.send_text("request");
824            conn.receive_text("response");
825
826            assert_eq!(conn.sent_messages().len(), 1);
827            assert_eq!(conn.received_messages().len(), 1);
828        }
829
830        #[test]
831        fn test_message_count() {
832            let conn = WebSocketConnection::new("conn_1", "ws://example.com");
833            conn.send_text("msg1");
834            conn.send_text("msg2");
835
836            assert_eq!(conn.message_count(), 2);
837        }
838    }
839
840    mod mock_websocket_response_tests {
841        use super::*;
842
843        #[test]
844        fn test_new() {
845            let response = MockWebSocketResponse::new();
846            assert!(response.messages.is_empty());
847            assert_eq!(response.delay_ms, 0);
848        }
849
850        #[test]
851        fn test_with_text() {
852            let response = MockWebSocketResponse::new()
853                .with_text("message 1")
854                .with_text("message 2");
855            assert_eq!(response.messages.len(), 2);
856        }
857
858        #[test]
859        fn test_with_delay() {
860            let response = MockWebSocketResponse::new().with_delay(100);
861            assert_eq!(response.delay_ms, 100);
862        }
863    }
864
865    mod websocket_mock_tests {
866        use super::*;
867
868        #[test]
869        fn test_new() {
870            let mock = WebSocketMock::new("ws://example.com");
871            assert_eq!(mock.url_pattern, "ws://example.com");
872            assert!(mock.message_pattern.is_none());
873        }
874
875        #[test]
876        fn test_matches_url() {
877            let mock = WebSocketMock::new("example.com");
878            assert!(mock.matches_url("ws://example.com/socket"));
879            assert!(!mock.matches_url("ws://other.com"));
880        }
881
882        #[test]
883        fn test_matches_message() {
884            let mock =
885                WebSocketMock::new("example.com").on_message("hello", MockWebSocketResponse::new());
886            assert!(mock.matches_message("say hello world"));
887            assert!(!mock.matches_message("goodbye"));
888        }
889
890        #[test]
891        fn test_once() {
892            let mut mock = WebSocketMock::new("example.com").once();
893            assert!(mock.matches_url("ws://example.com"));
894            mock.mark_used();
895            assert!(!mock.matches_url("ws://example.com"));
896        }
897    }
898
899    mod websocket_monitor_tests {
900        use super::*;
901
902        #[test]
903        fn test_new() {
904            let monitor = WebSocketMonitor::new();
905            assert!(!monitor.is_active());
906            assert_eq!(monitor.connection_count(), 0);
907        }
908
909        #[test]
910        fn test_start_stop() {
911            let mut monitor = WebSocketMonitor::new();
912            monitor.start();
913            assert!(monitor.is_active());
914            monitor.stop();
915            assert!(!monitor.is_active());
916        }
917
918        #[test]
919        fn test_connect() {
920            let mut monitor = WebSocketMonitor::new();
921            let id = monitor.connect("ws://example.com");
922            assert!(!id.is_empty());
923            assert_eq!(monitor.connection_count(), 1);
924        }
925
926        #[test]
927        fn test_disconnect() {
928            let mut monitor = WebSocketMonitor::new();
929            let id = monitor.connect("ws://example.com");
930            monitor.disconnect(&id, 1000, "Normal");
931
932            assert_eq!(monitor.active_connection_count(), 0);
933        }
934
935        #[test]
936        fn test_send() {
937            let mut monitor = WebSocketMonitor::new();
938            let id = monitor.connect("ws://example.com");
939            monitor.send(&id, "hello");
940
941            let messages = monitor.get_connection(&id).unwrap();
942            assert_eq!(messages.len(), 1);
943            assert!(messages[0].is_sent());
944        }
945
946        #[test]
947        fn test_receive() {
948            let mut monitor = WebSocketMonitor::new();
949            let id = monitor.connect("ws://example.com");
950            monitor.receive(&id, "response");
951
952            let messages = monitor.get_connection(&id).unwrap();
953            assert_eq!(messages.len(), 1);
954            assert!(messages[0].is_received());
955        }
956
957        #[test]
958        fn test_all_messages() {
959            let mut monitor = WebSocketMonitor::new();
960            let id1 = monitor.connect("ws://example.com");
961            let id2 = monitor.connect("ws://other.com");
962
963            monitor.send(&id1, "msg1");
964            monitor.send(&id2, "msg2");
965
966            let all = monitor.all_messages();
967            assert_eq!(all.len(), 2);
968        }
969
970        #[test]
971        fn test_mock_on_open() {
972            let mut monitor = WebSocketMonitor::new();
973            monitor.mock(
974                WebSocketMock::new("example.com")
975                    .on_open(MockWebSocketResponse::new().with_text("welcome")),
976            );
977
978            let _id = monitor.connect("ws://example.com/socket");
979            let pending = monitor.take_pending_responses();
980
981            assert_eq!(pending.len(), 1);
982            assert_eq!(pending[0].1.messages.len(), 1);
983        }
984
985        #[test]
986        fn test_mock_on_message() {
987            let mut monitor = WebSocketMonitor::new();
988            monitor.mock(
989                WebSocketMock::new("example.com")
990                    .on_message("ping", MockWebSocketResponse::new().with_text("pong")),
991            );
992
993            let id = monitor.connect("ws://example.com");
994            monitor.send(&id, "ping");
995
996            let pending = monitor.take_pending_responses();
997            assert_eq!(pending.len(), 1);
998        }
999
1000        #[test]
1001        fn test_assert_sent() {
1002            let mut monitor = WebSocketMonitor::new();
1003            let id = monitor.connect("ws://example.com");
1004            monitor.send(&id, "hello world");
1005
1006            assert!(monitor.assert_sent("hello").is_ok());
1007            assert!(monitor.assert_sent("foo").is_err());
1008        }
1009
1010        #[test]
1011        fn test_assert_received() {
1012            let mut monitor = WebSocketMonitor::new();
1013            let id = monitor.connect("ws://example.com");
1014            monitor.receive(&id, "server response");
1015
1016            assert!(monitor.assert_received("response").is_ok());
1017            assert!(monitor.assert_received("foo").is_err());
1018        }
1019
1020        #[test]
1021        fn test_assert_connected() {
1022            let mut monitor = WebSocketMonitor::new();
1023            let _id = monitor.connect("ws://example.com/socket");
1024
1025            assert!(monitor.assert_connected("example.com").is_ok());
1026            assert!(monitor.assert_connected("other.com").is_err());
1027        }
1028
1029        #[test]
1030        fn test_clear() {
1031            let mut monitor = WebSocketMonitor::new();
1032            let _id = monitor.connect("ws://example.com");
1033            monitor.mock(WebSocketMock::new("test"));
1034
1035            monitor.clear();
1036
1037            assert_eq!(monitor.connection_count(), 0);
1038        }
1039    }
1040
1041    mod websocket_monitor_builder_tests {
1042        use super::*;
1043
1044        #[test]
1045        fn test_builder() {
1046            let monitor = WebSocketMonitorBuilder::new()
1047                .mock_open(
1048                    "example.com",
1049                    MockWebSocketResponse::new().with_text("hello"),
1050                )
1051                .mock_message(
1052                    "example.com",
1053                    "ping",
1054                    MockWebSocketResponse::new().with_text("pong"),
1055                )
1056                .build();
1057
1058            assert_eq!(monitor.mocks.len(), 2);
1059        }
1060    }
1061
1062    mod additional_coverage_tests {
1063        use super::*;
1064
1065        #[test]
1066        fn test_websocket_connection_send_binary() {
1067            let conn = WebSocketConnection::new("conn_1", "ws://example.com");
1068            conn.send_binary(vec![0x01, 0x02, 0x03, 0x04]);
1069
1070            let messages = conn.messages();
1071            assert_eq!(messages.len(), 1);
1072            assert!(messages[0].is_binary());
1073            assert!(messages[0].is_sent());
1074            assert!(messages[0].raw_data.is_some());
1075            assert_eq!(
1076                messages[0].raw_data.as_ref().unwrap(),
1077                &vec![0x01, 0x02, 0x03, 0x04]
1078            );
1079        }
1080
1081        #[test]
1082        fn test_websocket_connection_receive_binary() {
1083            let conn = WebSocketConnection::new("conn_1", "ws://example.com");
1084            conn.receive_binary(vec![0xDE, 0xAD, 0xBE, 0xEF]);
1085
1086            let messages = conn.messages();
1087            assert_eq!(messages.len(), 1);
1088            assert!(messages[0].is_binary());
1089            assert!(messages[0].is_received());
1090            assert!(messages[0].raw_data.is_some());
1091        }
1092
1093        #[test]
1094        fn test_websocket_connection_elapsed_ms() {
1095            let conn = WebSocketConnection::new("conn_1", "ws://example.com");
1096            // elapsed_ms should return some value >= 0
1097            let elapsed = conn.elapsed_ms();
1098            assert!(elapsed < 1000); // Should be very small, just created
1099        }
1100
1101        #[test]
1102        fn test_mock_websocket_response_with_binary() {
1103            let response = MockWebSocketResponse::new()
1104                .with_binary(vec![1, 2, 3])
1105                .with_binary(vec![4, 5, 6]);
1106            assert_eq!(response.messages.len(), 2);
1107            assert!(response.messages[0].is_binary());
1108            assert!(response.messages[1].is_binary());
1109        }
1110
1111        #[test]
1112        fn test_mock_websocket_response_default() {
1113            let response = MockWebSocketResponse::default();
1114            assert!(response.messages.is_empty());
1115            assert_eq!(response.delay_ms, 0);
1116        }
1117
1118        #[test]
1119        fn test_websocket_monitor_default() {
1120            let monitor = WebSocketMonitor::default();
1121            assert!(!monitor.is_active());
1122            assert_eq!(monitor.connection_count(), 0);
1123        }
1124
1125        #[test]
1126        fn test_websocket_monitor_connections_list() {
1127            let mut monitor = WebSocketMonitor::new();
1128            let id1 = monitor.connect("ws://example1.com");
1129            let id2 = monitor.connect("ws://example2.com");
1130
1131            let connections = monitor.connections();
1132            assert_eq!(connections.len(), 2);
1133            assert!(connections.contains(&id1));
1134            assert!(connections.contains(&id2));
1135        }
1136
1137        #[test]
1138        fn test_websocket_mock_on_open() {
1139            let mock = WebSocketMock::new("example.com")
1140                .on_open(MockWebSocketResponse::new().with_text("welcome"));
1141
1142            assert_eq!(mock.url_pattern, "example.com");
1143            assert!(mock.message_pattern.is_none());
1144            assert_eq!(mock.response.messages.len(), 1);
1145        }
1146
1147        #[test]
1148        fn test_websocket_message_json_error() {
1149            let msg = WebSocketMessage::text("not valid json {{{", MessageDirection::Sent, 0);
1150            let result: Result<serde_json::Value, _> = msg.json();
1151            assert!(result.is_err());
1152        }
1153
1154        #[test]
1155        fn test_websocket_mock_matches_message_no_pattern() {
1156            let mock = WebSocketMock::new("example.com");
1157            // When message_pattern is None, matches_message should return false
1158            assert!(!mock.matches_message("any message"));
1159        }
1160
1161        #[test]
1162        fn test_websocket_mock_once_matches_message() {
1163            let mut mock = WebSocketMock::new("example.com")
1164                .on_message("hello", MockWebSocketResponse::new())
1165                .once();
1166
1167            assert!(mock.matches_message("say hello"));
1168            mock.mark_used();
1169            assert!(!mock.matches_message("say hello"));
1170        }
1171
1172        #[test]
1173        fn test_websocket_monitor_get_connection_not_found() {
1174            let monitor = WebSocketMonitor::new();
1175            let result = monitor.get_connection("nonexistent");
1176            assert!(result.is_none());
1177        }
1178
1179        #[test]
1180        fn test_websocket_monitor_send_to_nonexistent() {
1181            let mut monitor = WebSocketMonitor::new();
1182            // Should not panic when sending to nonexistent connection
1183            monitor.send("nonexistent", "message");
1184            assert_eq!(monitor.all_messages().len(), 0);
1185        }
1186
1187        #[test]
1188        fn test_websocket_monitor_receive_to_nonexistent() {
1189            let monitor = WebSocketMonitor::new();
1190            // Should not panic when receiving on nonexistent connection
1191            monitor.receive("nonexistent", "message");
1192            assert_eq!(monitor.all_messages().len(), 0);
1193        }
1194
1195        #[test]
1196        fn test_websocket_monitor_disconnect_nonexistent() {
1197            let mut monitor = WebSocketMonitor::new();
1198            // Should not panic when disconnecting nonexistent connection
1199            monitor.disconnect("nonexistent", 1000, "Normal");
1200        }
1201
1202        #[test]
1203        fn test_websocket_state_enum_variants() {
1204            // Test all WebSocketState variants
1205            let connecting = WebSocketState::Connecting;
1206            let open = WebSocketState::Open;
1207            let closing = WebSocketState::Closing;
1208            let closed = WebSocketState::Closed;
1209
1210            assert!(matches!(connecting, WebSocketState::Connecting));
1211            assert!(matches!(open, WebSocketState::Open));
1212            assert!(matches!(closing, WebSocketState::Closing));
1213            assert!(matches!(closed, WebSocketState::Closed));
1214        }
1215
1216        #[test]
1217        fn test_message_type_enum_variants() {
1218            // Test all MessageType variants
1219            assert!(matches!(MessageType::Text, MessageType::Text));
1220            assert!(matches!(MessageType::Binary, MessageType::Binary));
1221            assert!(matches!(MessageType::Ping, MessageType::Ping));
1222            assert!(matches!(MessageType::Pong, MessageType::Pong));
1223            assert!(matches!(MessageType::Close, MessageType::Close));
1224        }
1225
1226        #[test]
1227        fn test_message_direction_enum_variants() {
1228            // Test all MessageDirection variants
1229            assert!(matches!(MessageDirection::Sent, MessageDirection::Sent));
1230            assert!(matches!(
1231                MessageDirection::Received,
1232                MessageDirection::Received
1233            ));
1234        }
1235
1236        #[test]
1237        fn test_websocket_message_is_not_text() {
1238            let msg = WebSocketMessage::binary(vec![1, 2, 3], MessageDirection::Sent, 0);
1239            assert!(!msg.is_text());
1240        }
1241
1242        #[test]
1243        fn test_websocket_message_is_not_binary() {
1244            let msg = WebSocketMessage::text("hello", MessageDirection::Sent, 0);
1245            assert!(!msg.is_binary());
1246        }
1247
1248        #[test]
1249        fn test_websocket_message_is_not_sent() {
1250            let msg = WebSocketMessage::text("hello", MessageDirection::Received, 0);
1251            assert!(!msg.is_sent());
1252        }
1253
1254        #[test]
1255        fn test_websocket_message_is_not_received() {
1256            let msg = WebSocketMessage::text("hello", MessageDirection::Sent, 0);
1257            assert!(!msg.is_received());
1258        }
1259
1260        #[test]
1261        fn test_websocket_connection_state_transitions() {
1262            let mut conn = WebSocketConnection::new("conn_1", "ws://example.com");
1263            assert!(matches!(conn.state, WebSocketState::Connecting));
1264            assert!(!conn.is_open());
1265            assert!(!conn.is_closed());
1266
1267            conn.open();
1268            assert!(matches!(conn.state, WebSocketState::Open));
1269            assert!(conn.is_open());
1270            assert!(!conn.is_closed());
1271
1272            conn.close(1000, "goodbye");
1273            assert!(matches!(conn.state, WebSocketState::Closed));
1274            assert!(!conn.is_open());
1275            assert!(conn.is_closed());
1276        }
1277
1278        #[test]
1279        fn test_websocket_message_close_format() {
1280            let close = WebSocketMessage::close(1001, "Going Away", 100);
1281            assert!(close.data.contains("1001"));
1282            assert!(close.data.contains("Going Away"));
1283            assert_eq!(close.timestamp_ms, 100);
1284        }
1285
1286        #[test]
1287        fn test_websocket_monitor_multiple_mocks_same_url() {
1288            let mut monitor = WebSocketMonitor::new();
1289            monitor.mock(
1290                WebSocketMock::new("example.com")
1291                    .on_open(MockWebSocketResponse::new().with_text("welcome1")),
1292            );
1293            monitor.mock(
1294                WebSocketMock::new("example.com")
1295                    .on_open(MockWebSocketResponse::new().with_text("welcome2")),
1296            );
1297
1298            let _id = monitor.connect("ws://example.com/socket");
1299            let pending = monitor.take_pending_responses();
1300
1301            // Both mocks should trigger
1302            assert_eq!(pending.len(), 2);
1303        }
1304
1305        #[test]
1306        fn test_websocket_monitor_mock_message_url_mismatch() {
1307            let mut monitor = WebSocketMonitor::new();
1308            monitor.mock(
1309                WebSocketMock::new("other.com")
1310                    .on_message("hello", MockWebSocketResponse::new().with_text("response")),
1311            );
1312
1313            let id = monitor.connect("ws://example.com");
1314            monitor.send(&id, "hello");
1315
1316            // Mock should not trigger because URL doesn't match
1317            let pending = monitor.take_pending_responses();
1318            assert!(pending.is_empty());
1319        }
1320
1321        #[test]
1322        fn test_websocket_connection_record_message_sets_connection_id() {
1323            let conn = WebSocketConnection::new("my_conn", "ws://example.com");
1324            let msg = WebSocketMessage::text("test", MessageDirection::Sent, 0);
1325
1326            conn.record_message(msg);
1327
1328            let messages = conn.messages();
1329            assert_eq!(messages.len(), 1);
1330            assert_eq!(messages[0].connection_id, "my_conn");
1331        }
1332
1333        #[test]
1334        fn test_websocket_monitor_clear_resets_counter() {
1335            let mut monitor = WebSocketMonitor::new();
1336            let _id1 = monitor.connect("ws://example1.com");
1337            let _id2 = monitor.connect("ws://example2.com");
1338
1339            monitor.clear();
1340
1341            // After clear, connection counter resets
1342            let id3 = monitor.connect("ws://example3.com");
1343            assert_eq!(id3, "ws_1"); // Counter should be reset to 0, so first new conn is ws_1
1344        }
1345
1346        #[test]
1347        fn test_websocket_monitor_builder_default() {
1348            let builder = WebSocketMonitorBuilder::default();
1349            let monitor = builder.build();
1350            assert!(!monitor.is_active());
1351            assert_eq!(monitor.mocks.len(), 0);
1352        }
1353
1354        #[test]
1355        fn test_websocket_message_ping_direction() {
1356            let ping = WebSocketMessage::ping(50);
1357            assert!(ping.is_sent()); // Ping is sent from client
1358            assert_eq!(ping.timestamp_ms, 50);
1359            assert!(ping.data.is_empty());
1360        }
1361
1362        #[test]
1363        fn test_websocket_message_pong_direction() {
1364            let pong = WebSocketMessage::pong(75);
1365            assert!(pong.is_received()); // Pong is received from server
1366            assert_eq!(pong.timestamp_ms, 75);
1367            assert!(pong.data.is_empty());
1368        }
1369
1370        #[test]
1371        fn test_websocket_message_binary_base64_encoding() {
1372            let data = vec![0x48, 0x65, 0x6c, 0x6c, 0x6f]; // "Hello" in bytes
1373            let msg = WebSocketMessage::binary(data.clone(), MessageDirection::Sent, 0);
1374
1375            // Verify the data is base64 encoded
1376            assert!(!msg.data.is_empty());
1377            // Decode and verify
1378            let decoded =
1379                base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &msg.data)
1380                    .unwrap();
1381            assert_eq!(decoded, data);
1382        }
1383
1384        #[test]
1385        fn test_websocket_connection_empty_messages() {
1386            let conn = WebSocketConnection::new("conn_1", "ws://example.com");
1387            assert!(conn.messages().is_empty());
1388            assert!(conn.sent_messages().is_empty());
1389            assert!(conn.received_messages().is_empty());
1390            assert_eq!(conn.message_count(), 0);
1391        }
1392
1393        #[test]
1394        fn test_websocket_mock_response_chaining() {
1395            let response = MockWebSocketResponse::new()
1396                .with_text("msg1")
1397                .with_binary(vec![1, 2])
1398                .with_text("msg2")
1399                .with_delay(500);
1400
1401            assert_eq!(response.messages.len(), 3);
1402            assert_eq!(response.delay_ms, 500);
1403            assert!(response.messages[0].is_text());
1404            assert!(response.messages[1].is_binary());
1405            assert!(response.messages[2].is_text());
1406        }
1407
1408        #[test]
1409        fn test_websocket_message_json_valid_nested() {
1410            let msg = WebSocketMessage::text(
1411                r#"{"user":{"name":"Alice","age":30},"active":true}"#,
1412                MessageDirection::Received,
1413                0,
1414            );
1415            let data: serde_json::Value = msg.json().unwrap();
1416            assert_eq!(data["user"]["name"], "Alice");
1417            assert_eq!(data["user"]["age"], 30);
1418            assert_eq!(data["active"], true);
1419        }
1420
1421        #[test]
1422        fn test_websocket_monitor_active_vs_total_count() {
1423            let mut monitor = WebSocketMonitor::new();
1424            let id1 = monitor.connect("ws://example1.com");
1425            let id2 = monitor.connect("ws://example2.com");
1426            let _id3 = monitor.connect("ws://example3.com");
1427
1428            assert_eq!(monitor.connection_count(), 3);
1429            assert_eq!(monitor.active_connection_count(), 3);
1430
1431            monitor.disconnect(&id1, 1000, "Normal");
1432            assert_eq!(monitor.connection_count(), 3);
1433            assert_eq!(monitor.active_connection_count(), 2);
1434
1435            monitor.disconnect(&id2, 1000, "Normal");
1436            assert_eq!(monitor.connection_count(), 3);
1437            assert_eq!(monitor.active_connection_count(), 1);
1438        }
1439
1440        #[test]
1441        fn test_websocket_message_clone() {
1442            let msg = WebSocketMessage::text("hello", MessageDirection::Sent, 100)
1443                .with_connection("conn_1");
1444            let cloned = msg.clone();
1445
1446            assert_eq!(cloned.data, msg.data);
1447            assert_eq!(cloned.timestamp_ms, msg.timestamp_ms);
1448            assert_eq!(cloned.connection_id, msg.connection_id);
1449        }
1450
1451        #[test]
1452        fn test_websocket_mock_clone() {
1453            let mock = WebSocketMock::new("example.com")
1454                .on_message("test", MockWebSocketResponse::new().with_text("response"))
1455                .once();
1456            let cloned = mock.clone();
1457
1458            assert_eq!(cloned.url_pattern, mock.url_pattern);
1459            assert_eq!(cloned.message_pattern, mock.message_pattern);
1460            assert_eq!(cloned.once, mock.once);
1461        }
1462
1463        #[test]
1464        fn test_mock_websocket_response_clone() {
1465            let response = MockWebSocketResponse::new()
1466                .with_text("msg")
1467                .with_delay(100);
1468            let cloned = response.clone();
1469
1470            assert_eq!(cloned.messages.len(), response.messages.len());
1471            assert_eq!(cloned.delay_ms, response.delay_ms);
1472        }
1473
1474        #[test]
1475        fn test_websocket_state_serialization() {
1476            let state = WebSocketState::Open;
1477            let json = serde_json::to_string(&state).unwrap();
1478            let deserialized: WebSocketState = serde_json::from_str(&json).unwrap();
1479            assert_eq!(state, deserialized);
1480        }
1481
1482        #[test]
1483        fn test_message_type_serialization() {
1484            let msg_type = MessageType::Binary;
1485            let json = serde_json::to_string(&msg_type).unwrap();
1486            let deserialized: MessageType = serde_json::from_str(&json).unwrap();
1487            assert_eq!(msg_type, deserialized);
1488        }
1489
1490        #[test]
1491        fn test_message_direction_serialization() {
1492            let direction = MessageDirection::Sent;
1493            let json = serde_json::to_string(&direction).unwrap();
1494            let deserialized: MessageDirection = serde_json::from_str(&json).unwrap();
1495            assert_eq!(direction, deserialized);
1496        }
1497
1498        #[test]
1499        fn test_websocket_message_serialization() {
1500            let msg = WebSocketMessage::text("hello", MessageDirection::Sent, 1000)
1501                .with_connection("conn_1");
1502            let json = serde_json::to_string(&msg).unwrap();
1503            let deserialized: WebSocketMessage = serde_json::from_str(&json).unwrap();
1504
1505            assert_eq!(deserialized.data, "hello");
1506            assert_eq!(deserialized.timestamp_ms, 1000);
1507            assert_eq!(deserialized.connection_id, "conn_1");
1508            // Note: raw_data is skipped in serialization
1509            assert!(deserialized.raw_data.is_none());
1510        }
1511    }
1512}