use crate::result::{ProbarError, ProbarResult};
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use std::time::Instant;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum WebSocketState {
Connecting,
Open,
Closing,
Closed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum MessageType {
Text,
Binary,
Ping,
Pong,
Close,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum MessageDirection {
Sent,
Received,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebSocketMessage {
pub message_type: MessageType,
pub direction: MessageDirection,
pub data: String,
#[serde(skip)]
pub raw_data: Option<Vec<u8>>,
pub timestamp_ms: u64,
pub connection_id: String,
}
impl WebSocketMessage {
#[must_use]
pub fn text(data: &str, direction: MessageDirection, timestamp_ms: u64) -> Self {
Self {
message_type: MessageType::Text,
direction,
data: data.to_string(),
raw_data: None,
timestamp_ms,
connection_id: String::new(),
}
}
#[must_use]
pub fn binary(data: Vec<u8>, direction: MessageDirection, timestamp_ms: u64) -> Self {
Self {
message_type: MessageType::Binary,
direction,
data: base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &data),
raw_data: Some(data),
timestamp_ms,
connection_id: String::new(),
}
}
#[must_use]
pub fn ping(timestamp_ms: u64) -> Self {
Self {
message_type: MessageType::Ping,
direction: MessageDirection::Sent,
data: String::new(),
raw_data: None,
timestamp_ms,
connection_id: String::new(),
}
}
#[must_use]
pub fn pong(timestamp_ms: u64) -> Self {
Self {
message_type: MessageType::Pong,
direction: MessageDirection::Received,
data: String::new(),
raw_data: None,
timestamp_ms,
connection_id: String::new(),
}
}
#[must_use]
pub fn close(code: u16, reason: &str, timestamp_ms: u64) -> Self {
Self {
message_type: MessageType::Close,
direction: MessageDirection::Received,
data: format!("{}: {}", code, reason),
raw_data: None,
timestamp_ms,
connection_id: String::new(),
}
}
#[must_use]
pub fn with_connection(mut self, connection_id: &str) -> Self {
self.connection_id = connection_id.to_string();
self
}
#[must_use]
pub const fn is_text(&self) -> bool {
matches!(self.message_type, MessageType::Text)
}
#[must_use]
pub const fn is_binary(&self) -> bool {
matches!(self.message_type, MessageType::Binary)
}
#[must_use]
pub const fn is_sent(&self) -> bool {
matches!(self.direction, MessageDirection::Sent)
}
#[must_use]
pub const fn is_received(&self) -> bool {
matches!(self.direction, MessageDirection::Received)
}
pub fn json<T: for<'de> Deserialize<'de>>(&self) -> ProbarResult<T> {
let data = serde_json::from_str(&self.data)?;
Ok(data)
}
#[must_use]
pub fn contains(&self, s: &str) -> bool {
self.data.contains(s)
}
}
#[derive(Debug)]
pub struct WebSocketConnection {
pub id: String,
pub url: String,
pub state: WebSocketState,
messages: Arc<Mutex<Vec<WebSocketMessage>>>,
start_time: Instant,
pub close_code: Option<u16>,
pub close_reason: Option<String>,
}
impl WebSocketConnection {
#[must_use]
pub fn new(id: &str, url: &str) -> Self {
Self {
id: id.to_string(),
url: url.to_string(),
state: WebSocketState::Connecting,
messages: Arc::new(Mutex::new(Vec::new())),
start_time: Instant::now(),
close_code: None,
close_reason: None,
}
}
pub fn open(&mut self) {
self.state = WebSocketState::Open;
}
pub fn close(&mut self, code: u16, reason: &str) {
self.state = WebSocketState::Closed;
self.close_code = Some(code);
self.close_reason = Some(reason.to_string());
}
#[must_use]
pub fn elapsed_ms(&self) -> u64 {
self.start_time.elapsed().as_millis() as u64
}
pub fn record_message(&self, mut message: WebSocketMessage) {
message.connection_id = self.id.clone();
if let Ok(mut messages) = self.messages.lock() {
messages.push(message);
}
}
pub fn send_text(&self, data: &str) {
let message = WebSocketMessage::text(data, MessageDirection::Sent, self.elapsed_ms());
self.record_message(message);
}
pub fn send_binary(&self, data: Vec<u8>) {
let message = WebSocketMessage::binary(data, MessageDirection::Sent, self.elapsed_ms());
self.record_message(message);
}
pub fn receive_text(&self, data: &str) {
let message = WebSocketMessage::text(data, MessageDirection::Received, self.elapsed_ms());
self.record_message(message);
}
pub fn receive_binary(&self, data: Vec<u8>) {
let message = WebSocketMessage::binary(data, MessageDirection::Received, self.elapsed_ms());
self.record_message(message);
}
#[must_use]
pub fn messages(&self) -> Vec<WebSocketMessage> {
self.messages.lock().map(|m| m.clone()).unwrap_or_default()
}
#[must_use]
pub fn sent_messages(&self) -> Vec<WebSocketMessage> {
self.messages()
.into_iter()
.filter(|m| m.is_sent())
.collect()
}
#[must_use]
pub fn received_messages(&self) -> Vec<WebSocketMessage> {
self.messages()
.into_iter()
.filter(|m| m.is_received())
.collect()
}
#[must_use]
pub fn message_count(&self) -> usize {
self.messages.lock().map(|m| m.len()).unwrap_or(0)
}
#[must_use]
pub const fn is_open(&self) -> bool {
matches!(self.state, WebSocketState::Open)
}
#[must_use]
pub const fn is_closed(&self) -> bool {
matches!(self.state, WebSocketState::Closed)
}
}
#[derive(Debug, Clone)]
pub struct MockWebSocketResponse {
pub messages: Vec<WebSocketMessage>,
pub delay_ms: u64,
}
impl MockWebSocketResponse {
#[must_use]
pub fn new() -> Self {
Self {
messages: Vec::new(),
delay_ms: 0,
}
}
#[must_use]
pub fn with_text(mut self, data: &str) -> Self {
self.messages
.push(WebSocketMessage::text(data, MessageDirection::Received, 0));
self
}
#[must_use]
pub fn with_binary(mut self, data: Vec<u8>) -> Self {
self.messages.push(WebSocketMessage::binary(
data,
MessageDirection::Received,
0,
));
self
}
#[must_use]
pub const fn with_delay(mut self, delay_ms: u64) -> Self {
self.delay_ms = delay_ms;
self
}
}
impl Default for MockWebSocketResponse {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct WebSocketMock {
pub url_pattern: String,
pub message_pattern: Option<String>,
pub response: MockWebSocketResponse,
pub once: bool,
pub used: bool,
}
impl WebSocketMock {
#[must_use]
pub fn new(url_pattern: &str) -> Self {
Self {
url_pattern: url_pattern.to_string(),
message_pattern: None,
response: MockWebSocketResponse::new(),
once: false,
used: false,
}
}
#[must_use]
pub fn on_open(mut self, response: MockWebSocketResponse) -> Self {
self.response = response;
self
}
#[must_use]
pub fn on_message(mut self, pattern: &str, response: MockWebSocketResponse) -> Self {
self.message_pattern = Some(pattern.to_string());
self.response = response;
self
}
#[must_use]
pub const fn once(mut self) -> Self {
self.once = true;
self
}
#[must_use]
pub fn matches_url(&self, url: &str) -> bool {
if self.once && self.used {
return false;
}
url.contains(&self.url_pattern)
}
#[must_use]
pub fn matches_message(&self, message: &str) -> bool {
if self.once && self.used {
return false;
}
self.message_pattern
.as_ref()
.is_some_and(|p| message.contains(p))
}
pub fn mark_used(&mut self) {
self.used = true;
}
}
#[derive(Debug)]
pub struct WebSocketMonitor {
connections: Arc<Mutex<Vec<WebSocketConnection>>>,
mocks: Vec<WebSocketMock>,
pending_responses: VecDeque<(String, MockWebSocketResponse)>,
active: bool,
connection_counter: u64,
}
impl Default for WebSocketMonitor {
fn default() -> Self {
Self::new()
}
}
impl WebSocketMonitor {
#[must_use]
pub fn new() -> Self {
Self {
connections: Arc::new(Mutex::new(Vec::new())),
mocks: Vec::new(),
pending_responses: VecDeque::new(),
active: false,
connection_counter: 0,
}
}
pub fn start(&mut self) {
self.active = true;
}
pub fn stop(&mut self) {
self.active = false;
}
#[must_use]
pub const fn is_active(&self) -> bool {
self.active
}
pub fn mock(&mut self, mock: WebSocketMock) {
self.mocks.push(mock);
}
pub fn connect(&mut self, url: &str) -> String {
self.connection_counter += 1;
let id = format!("ws_{}", self.connection_counter);
let mut connection = WebSocketConnection::new(&id, url);
connection.open();
for mock in &mut self.mocks {
if mock.matches_url(url) && mock.message_pattern.is_none() {
self.pending_responses
.push_back((id.clone(), mock.response.clone()));
mock.mark_used();
}
}
if let Ok(mut connections) = self.connections.lock() {
connections.push(connection);
}
id
}
pub fn disconnect(&mut self, connection_id: &str, code: u16, reason: &str) {
if let Ok(mut connections) = self.connections.lock() {
if let Some(conn) = connections.iter_mut().find(|c| c.id == connection_id) {
conn.close(code, reason);
}
}
}
pub fn send(&mut self, connection_id: &str, message: &str) {
if let Ok(connections) = self.connections.lock() {
if let Some(conn) = connections.iter().find(|c| c.id == connection_id) {
conn.send_text(message);
for mock in &mut self.mocks {
if mock.matches_url(&conn.url) && mock.matches_message(message) {
self.pending_responses
.push_back((connection_id.to_string(), mock.response.clone()));
mock.mark_used();
}
}
}
}
}
pub fn receive(&self, connection_id: &str, message: &str) {
if let Ok(connections) = self.connections.lock() {
if let Some(conn) = connections.iter().find(|c| c.id == connection_id) {
conn.receive_text(message);
}
}
}
#[must_use]
pub fn take_pending_responses(&mut self) -> Vec<(String, MockWebSocketResponse)> {
self.pending_responses.drain(..).collect()
}
#[must_use]
pub fn connections(&self) -> Vec<String> {
self.connections
.lock()
.map(|c| c.iter().map(|conn| conn.id.clone()).collect())
.unwrap_or_default()
}
pub fn get_connection(&self, connection_id: &str) -> Option<Vec<WebSocketMessage>> {
self.connections.lock().ok().and_then(|connections| {
connections
.iter()
.find(|c| c.id == connection_id)
.map(|c| c.messages())
})
}
#[must_use]
pub fn all_messages(&self) -> Vec<WebSocketMessage> {
self.connections
.lock()
.map(|connections| connections.iter().flat_map(|c| c.messages()).collect())
.unwrap_or_default()
}
#[must_use]
pub fn connection_count(&self) -> usize {
self.connections.lock().map(|c| c.len()).unwrap_or(0)
}
#[must_use]
pub fn active_connection_count(&self) -> usize {
self.connections
.lock()
.map(|c| c.iter().filter(|conn| conn.is_open()).count())
.unwrap_or(0)
}
pub fn assert_sent(&self, pattern: &str) -> ProbarResult<()> {
let messages = self.all_messages();
let found = messages.iter().any(|m| m.is_sent() && m.contains(pattern));
if !found {
return Err(ProbarError::AssertionFailed {
message: format!(
"Expected sent message containing '{}', but none found",
pattern
),
});
}
Ok(())
}
pub fn assert_received(&self, pattern: &str) -> ProbarResult<()> {
let messages = self.all_messages();
let found = messages
.iter()
.any(|m| m.is_received() && m.contains(pattern));
if !found {
return Err(ProbarError::AssertionError {
message: format!(
"Expected received message containing '{}', but none found",
pattern
),
});
}
Ok(())
}
pub fn assert_connected(&self, url_pattern: &str) -> ProbarResult<()> {
let found = self
.connections
.lock()
.map(|connections| connections.iter().any(|c| c.url.contains(url_pattern)))
.unwrap_or(false);
if !found {
return Err(ProbarError::AssertionError {
message: format!(
"Expected connection to URL containing '{}', but none found",
url_pattern
),
});
}
Ok(())
}
pub fn clear(&mut self) {
if let Ok(mut connections) = self.connections.lock() {
connections.clear();
}
self.mocks.clear();
self.pending_responses.clear();
self.connection_counter = 0;
}
}
#[derive(Debug, Default)]
pub struct WebSocketMonitorBuilder {
monitor: WebSocketMonitor,
}
impl WebSocketMonitorBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn mock_open(mut self, url_pattern: &str, response: MockWebSocketResponse) -> Self {
self.monitor
.mock(WebSocketMock::new(url_pattern).on_open(response));
self
}
#[must_use]
pub fn mock_message(
mut self,
url_pattern: &str,
message_pattern: &str,
response: MockWebSocketResponse,
) -> Self {
self.monitor
.mock(WebSocketMock::new(url_pattern).on_message(message_pattern, response));
self
}
#[must_use]
pub fn build(self) -> WebSocketMonitor {
self.monitor
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
mod websocket_message_tests {
use super::*;
#[test]
fn test_text_message() {
let msg = WebSocketMessage::text("hello", MessageDirection::Sent, 1000);
assert!(msg.is_text());
assert!(msg.is_sent());
assert_eq!(msg.data, "hello");
assert_eq!(msg.timestamp_ms, 1000);
}
#[test]
fn test_binary_message() {
let msg = WebSocketMessage::binary(vec![1, 2, 3], MessageDirection::Received, 500);
assert!(msg.is_binary());
assert!(msg.is_received());
assert!(msg.raw_data.is_some());
}
#[test]
fn test_ping_pong() {
let ping = WebSocketMessage::ping(100);
assert!(matches!(ping.message_type, MessageType::Ping));
let pong = WebSocketMessage::pong(200);
assert!(matches!(pong.message_type, MessageType::Pong));
}
#[test]
fn test_close_message() {
let close = WebSocketMessage::close(1000, "Normal closure", 500);
assert!(matches!(close.message_type, MessageType::Close));
assert!(close.data.contains("1000"));
}
#[test]
fn test_with_connection() {
let msg =
WebSocketMessage::text("test", MessageDirection::Sent, 0).with_connection("conn_1");
assert_eq!(msg.connection_id, "conn_1");
}
#[test]
fn test_contains() {
let msg = WebSocketMessage::text("hello world", MessageDirection::Sent, 0);
assert!(msg.contains("world"));
assert!(!msg.contains("foo"));
}
#[test]
fn test_json() {
let msg = WebSocketMessage::text(r#"{"name":"test"}"#, MessageDirection::Sent, 0);
let data: serde_json::Value = msg.json().unwrap();
assert_eq!(data["name"], "test");
}
}
mod websocket_connection_tests {
use super::*;
#[test]
fn test_new() {
let conn = WebSocketConnection::new("conn_1", "ws://example.com");
assert_eq!(conn.id, "conn_1");
assert_eq!(conn.url, "ws://example.com");
assert!(matches!(conn.state, WebSocketState::Connecting));
}
#[test]
fn test_open() {
let mut conn = WebSocketConnection::new("conn_1", "ws://example.com");
conn.open();
assert!(conn.is_open());
assert!(!conn.is_closed());
}
#[test]
fn test_close() {
let mut conn = WebSocketConnection::new("conn_1", "ws://example.com");
conn.open();
conn.close(1000, "Normal closure");
assert!(conn.is_closed());
assert_eq!(conn.close_code, Some(1000));
assert_eq!(conn.close_reason, Some("Normal closure".to_string()));
}
#[test]
fn test_send_text() {
let conn = WebSocketConnection::new("conn_1", "ws://example.com");
conn.send_text("hello");
let messages = conn.messages();
assert_eq!(messages.len(), 1);
assert!(messages[0].is_sent());
assert_eq!(messages[0].data, "hello");
}
#[test]
fn test_receive_text() {
let conn = WebSocketConnection::new("conn_1", "ws://example.com");
conn.receive_text("response");
let messages = conn.messages();
assert_eq!(messages.len(), 1);
assert!(messages[0].is_received());
}
#[test]
fn test_sent_received_messages() {
let conn = WebSocketConnection::new("conn_1", "ws://example.com");
conn.send_text("request");
conn.receive_text("response");
assert_eq!(conn.sent_messages().len(), 1);
assert_eq!(conn.received_messages().len(), 1);
}
#[test]
fn test_message_count() {
let conn = WebSocketConnection::new("conn_1", "ws://example.com");
conn.send_text("msg1");
conn.send_text("msg2");
assert_eq!(conn.message_count(), 2);
}
}
mod mock_websocket_response_tests {
use super::*;
#[test]
fn test_new() {
let response = MockWebSocketResponse::new();
assert!(response.messages.is_empty());
assert_eq!(response.delay_ms, 0);
}
#[test]
fn test_with_text() {
let response = MockWebSocketResponse::new()
.with_text("message 1")
.with_text("message 2");
assert_eq!(response.messages.len(), 2);
}
#[test]
fn test_with_delay() {
let response = MockWebSocketResponse::new().with_delay(100);
assert_eq!(response.delay_ms, 100);
}
}
mod websocket_mock_tests {
use super::*;
#[test]
fn test_new() {
let mock = WebSocketMock::new("ws://example.com");
assert_eq!(mock.url_pattern, "ws://example.com");
assert!(mock.message_pattern.is_none());
}
#[test]
fn test_matches_url() {
let mock = WebSocketMock::new("example.com");
assert!(mock.matches_url("ws://example.com/socket"));
assert!(!mock.matches_url("ws://other.com"));
}
#[test]
fn test_matches_message() {
let mock =
WebSocketMock::new("example.com").on_message("hello", MockWebSocketResponse::new());
assert!(mock.matches_message("say hello world"));
assert!(!mock.matches_message("goodbye"));
}
#[test]
fn test_once() {
let mut mock = WebSocketMock::new("example.com").once();
assert!(mock.matches_url("ws://example.com"));
mock.mark_used();
assert!(!mock.matches_url("ws://example.com"));
}
}
mod websocket_monitor_tests {
use super::*;
#[test]
fn test_new() {
let monitor = WebSocketMonitor::new();
assert!(!monitor.is_active());
assert_eq!(monitor.connection_count(), 0);
}
#[test]
fn test_start_stop() {
let mut monitor = WebSocketMonitor::new();
monitor.start();
assert!(monitor.is_active());
monitor.stop();
assert!(!monitor.is_active());
}
#[test]
fn test_connect() {
let mut monitor = WebSocketMonitor::new();
let id = monitor.connect("ws://example.com");
assert!(!id.is_empty());
assert_eq!(monitor.connection_count(), 1);
}
#[test]
fn test_disconnect() {
let mut monitor = WebSocketMonitor::new();
let id = monitor.connect("ws://example.com");
monitor.disconnect(&id, 1000, "Normal");
assert_eq!(monitor.active_connection_count(), 0);
}
#[test]
fn test_send() {
let mut monitor = WebSocketMonitor::new();
let id = monitor.connect("ws://example.com");
monitor.send(&id, "hello");
let messages = monitor.get_connection(&id).unwrap();
assert_eq!(messages.len(), 1);
assert!(messages[0].is_sent());
}
#[test]
fn test_receive() {
let mut monitor = WebSocketMonitor::new();
let id = monitor.connect("ws://example.com");
monitor.receive(&id, "response");
let messages = monitor.get_connection(&id).unwrap();
assert_eq!(messages.len(), 1);
assert!(messages[0].is_received());
}
#[test]
fn test_all_messages() {
let mut monitor = WebSocketMonitor::new();
let id1 = monitor.connect("ws://example.com");
let id2 = monitor.connect("ws://other.com");
monitor.send(&id1, "msg1");
monitor.send(&id2, "msg2");
let all = monitor.all_messages();
assert_eq!(all.len(), 2);
}
#[test]
fn test_mock_on_open() {
let mut monitor = WebSocketMonitor::new();
monitor.mock(
WebSocketMock::new("example.com")
.on_open(MockWebSocketResponse::new().with_text("welcome")),
);
let _id = monitor.connect("ws://example.com/socket");
let pending = monitor.take_pending_responses();
assert_eq!(pending.len(), 1);
assert_eq!(pending[0].1.messages.len(), 1);
}
#[test]
fn test_mock_on_message() {
let mut monitor = WebSocketMonitor::new();
monitor.mock(
WebSocketMock::new("example.com")
.on_message("ping", MockWebSocketResponse::new().with_text("pong")),
);
let id = monitor.connect("ws://example.com");
monitor.send(&id, "ping");
let pending = monitor.take_pending_responses();
assert_eq!(pending.len(), 1);
}
#[test]
fn test_assert_sent() {
let mut monitor = WebSocketMonitor::new();
let id = monitor.connect("ws://example.com");
monitor.send(&id, "hello world");
assert!(monitor.assert_sent("hello").is_ok());
assert!(monitor.assert_sent("foo").is_err());
}
#[test]
fn test_assert_received() {
let mut monitor = WebSocketMonitor::new();
let id = monitor.connect("ws://example.com");
monitor.receive(&id, "server response");
assert!(monitor.assert_received("response").is_ok());
assert!(monitor.assert_received("foo").is_err());
}
#[test]
fn test_assert_connected() {
let mut monitor = WebSocketMonitor::new();
let _id = monitor.connect("ws://example.com/socket");
assert!(monitor.assert_connected("example.com").is_ok());
assert!(monitor.assert_connected("other.com").is_err());
}
#[test]
fn test_clear() {
let mut monitor = WebSocketMonitor::new();
let _id = monitor.connect("ws://example.com");
monitor.mock(WebSocketMock::new("test"));
monitor.clear();
assert_eq!(monitor.connection_count(), 0);
}
}
mod websocket_monitor_builder_tests {
use super::*;
#[test]
fn test_builder() {
let monitor = WebSocketMonitorBuilder::new()
.mock_open(
"example.com",
MockWebSocketResponse::new().with_text("hello"),
)
.mock_message(
"example.com",
"ping",
MockWebSocketResponse::new().with_text("pong"),
)
.build();
assert_eq!(monitor.mocks.len(), 2);
}
}
mod additional_coverage_tests {
use super::*;
#[test]
fn test_websocket_connection_send_binary() {
let conn = WebSocketConnection::new("conn_1", "ws://example.com");
conn.send_binary(vec![0x01, 0x02, 0x03, 0x04]);
let messages = conn.messages();
assert_eq!(messages.len(), 1);
assert!(messages[0].is_binary());
assert!(messages[0].is_sent());
assert!(messages[0].raw_data.is_some());
assert_eq!(
messages[0].raw_data.as_ref().unwrap(),
&vec![0x01, 0x02, 0x03, 0x04]
);
}
#[test]
fn test_websocket_connection_receive_binary() {
let conn = WebSocketConnection::new("conn_1", "ws://example.com");
conn.receive_binary(vec![0xDE, 0xAD, 0xBE, 0xEF]);
let messages = conn.messages();
assert_eq!(messages.len(), 1);
assert!(messages[0].is_binary());
assert!(messages[0].is_received());
assert!(messages[0].raw_data.is_some());
}
#[test]
fn test_websocket_connection_elapsed_ms() {
let conn = WebSocketConnection::new("conn_1", "ws://example.com");
let elapsed = conn.elapsed_ms();
assert!(elapsed < 1000); }
#[test]
fn test_mock_websocket_response_with_binary() {
let response = MockWebSocketResponse::new()
.with_binary(vec![1, 2, 3])
.with_binary(vec![4, 5, 6]);
assert_eq!(response.messages.len(), 2);
assert!(response.messages[0].is_binary());
assert!(response.messages[1].is_binary());
}
#[test]
fn test_mock_websocket_response_default() {
let response = MockWebSocketResponse::default();
assert!(response.messages.is_empty());
assert_eq!(response.delay_ms, 0);
}
#[test]
fn test_websocket_monitor_default() {
let monitor = WebSocketMonitor::default();
assert!(!monitor.is_active());
assert_eq!(monitor.connection_count(), 0);
}
#[test]
fn test_websocket_monitor_connections_list() {
let mut monitor = WebSocketMonitor::new();
let id1 = monitor.connect("ws://example1.com");
let id2 = monitor.connect("ws://example2.com");
let connections = monitor.connections();
assert_eq!(connections.len(), 2);
assert!(connections.contains(&id1));
assert!(connections.contains(&id2));
}
#[test]
fn test_websocket_mock_on_open() {
let mock = WebSocketMock::new("example.com")
.on_open(MockWebSocketResponse::new().with_text("welcome"));
assert_eq!(mock.url_pattern, "example.com");
assert!(mock.message_pattern.is_none());
assert_eq!(mock.response.messages.len(), 1);
}
#[test]
fn test_websocket_message_json_error() {
let msg = WebSocketMessage::text("not valid json {{{", MessageDirection::Sent, 0);
let result: Result<serde_json::Value, _> = msg.json();
assert!(result.is_err());
}
#[test]
fn test_websocket_mock_matches_message_no_pattern() {
let mock = WebSocketMock::new("example.com");
assert!(!mock.matches_message("any message"));
}
#[test]
fn test_websocket_mock_once_matches_message() {
let mut mock = WebSocketMock::new("example.com")
.on_message("hello", MockWebSocketResponse::new())
.once();
assert!(mock.matches_message("say hello"));
mock.mark_used();
assert!(!mock.matches_message("say hello"));
}
#[test]
fn test_websocket_monitor_get_connection_not_found() {
let monitor = WebSocketMonitor::new();
let result = monitor.get_connection("nonexistent");
assert!(result.is_none());
}
#[test]
fn test_websocket_monitor_send_to_nonexistent() {
let mut monitor = WebSocketMonitor::new();
monitor.send("nonexistent", "message");
assert_eq!(monitor.all_messages().len(), 0);
}
#[test]
fn test_websocket_monitor_receive_to_nonexistent() {
let monitor = WebSocketMonitor::new();
monitor.receive("nonexistent", "message");
assert_eq!(monitor.all_messages().len(), 0);
}
#[test]
fn test_websocket_monitor_disconnect_nonexistent() {
let mut monitor = WebSocketMonitor::new();
monitor.disconnect("nonexistent", 1000, "Normal");
}
#[test]
fn test_websocket_state_enum_variants() {
let connecting = WebSocketState::Connecting;
let open = WebSocketState::Open;
let closing = WebSocketState::Closing;
let closed = WebSocketState::Closed;
assert!(matches!(connecting, WebSocketState::Connecting));
assert!(matches!(open, WebSocketState::Open));
assert!(matches!(closing, WebSocketState::Closing));
assert!(matches!(closed, WebSocketState::Closed));
}
#[test]
fn test_message_type_enum_variants() {
assert!(matches!(MessageType::Text, MessageType::Text));
assert!(matches!(MessageType::Binary, MessageType::Binary));
assert!(matches!(MessageType::Ping, MessageType::Ping));
assert!(matches!(MessageType::Pong, MessageType::Pong));
assert!(matches!(MessageType::Close, MessageType::Close));
}
#[test]
fn test_message_direction_enum_variants() {
assert!(matches!(MessageDirection::Sent, MessageDirection::Sent));
assert!(matches!(
MessageDirection::Received,
MessageDirection::Received
));
}
#[test]
fn test_websocket_message_is_not_text() {
let msg = WebSocketMessage::binary(vec![1, 2, 3], MessageDirection::Sent, 0);
assert!(!msg.is_text());
}
#[test]
fn test_websocket_message_is_not_binary() {
let msg = WebSocketMessage::text("hello", MessageDirection::Sent, 0);
assert!(!msg.is_binary());
}
#[test]
fn test_websocket_message_is_not_sent() {
let msg = WebSocketMessage::text("hello", MessageDirection::Received, 0);
assert!(!msg.is_sent());
}
#[test]
fn test_websocket_message_is_not_received() {
let msg = WebSocketMessage::text("hello", MessageDirection::Sent, 0);
assert!(!msg.is_received());
}
#[test]
fn test_websocket_connection_state_transitions() {
let mut conn = WebSocketConnection::new("conn_1", "ws://example.com");
assert!(matches!(conn.state, WebSocketState::Connecting));
assert!(!conn.is_open());
assert!(!conn.is_closed());
conn.open();
assert!(matches!(conn.state, WebSocketState::Open));
assert!(conn.is_open());
assert!(!conn.is_closed());
conn.close(1000, "goodbye");
assert!(matches!(conn.state, WebSocketState::Closed));
assert!(!conn.is_open());
assert!(conn.is_closed());
}
#[test]
fn test_websocket_message_close_format() {
let close = WebSocketMessage::close(1001, "Going Away", 100);
assert!(close.data.contains("1001"));
assert!(close.data.contains("Going Away"));
assert_eq!(close.timestamp_ms, 100);
}
#[test]
fn test_websocket_monitor_multiple_mocks_same_url() {
let mut monitor = WebSocketMonitor::new();
monitor.mock(
WebSocketMock::new("example.com")
.on_open(MockWebSocketResponse::new().with_text("welcome1")),
);
monitor.mock(
WebSocketMock::new("example.com")
.on_open(MockWebSocketResponse::new().with_text("welcome2")),
);
let _id = monitor.connect("ws://example.com/socket");
let pending = monitor.take_pending_responses();
assert_eq!(pending.len(), 2);
}
#[test]
fn test_websocket_monitor_mock_message_url_mismatch() {
let mut monitor = WebSocketMonitor::new();
monitor.mock(
WebSocketMock::new("other.com")
.on_message("hello", MockWebSocketResponse::new().with_text("response")),
);
let id = monitor.connect("ws://example.com");
monitor.send(&id, "hello");
let pending = monitor.take_pending_responses();
assert!(pending.is_empty());
}
#[test]
fn test_websocket_connection_record_message_sets_connection_id() {
let conn = WebSocketConnection::new("my_conn", "ws://example.com");
let msg = WebSocketMessage::text("test", MessageDirection::Sent, 0);
conn.record_message(msg);
let messages = conn.messages();
assert_eq!(messages.len(), 1);
assert_eq!(messages[0].connection_id, "my_conn");
}
#[test]
fn test_websocket_monitor_clear_resets_counter() {
let mut monitor = WebSocketMonitor::new();
let _id1 = monitor.connect("ws://example1.com");
let _id2 = monitor.connect("ws://example2.com");
monitor.clear();
let id3 = monitor.connect("ws://example3.com");
assert_eq!(id3, "ws_1"); }
#[test]
fn test_websocket_monitor_builder_default() {
let builder = WebSocketMonitorBuilder::default();
let monitor = builder.build();
assert!(!monitor.is_active());
assert_eq!(monitor.mocks.len(), 0);
}
#[test]
fn test_websocket_message_ping_direction() {
let ping = WebSocketMessage::ping(50);
assert!(ping.is_sent()); assert_eq!(ping.timestamp_ms, 50);
assert!(ping.data.is_empty());
}
#[test]
fn test_websocket_message_pong_direction() {
let pong = WebSocketMessage::pong(75);
assert!(pong.is_received()); assert_eq!(pong.timestamp_ms, 75);
assert!(pong.data.is_empty());
}
#[test]
fn test_websocket_message_binary_base64_encoding() {
let data = vec![0x48, 0x65, 0x6c, 0x6c, 0x6f]; let msg = WebSocketMessage::binary(data.clone(), MessageDirection::Sent, 0);
assert!(!msg.data.is_empty());
let decoded =
base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &msg.data)
.unwrap();
assert_eq!(decoded, data);
}
#[test]
fn test_websocket_connection_empty_messages() {
let conn = WebSocketConnection::new("conn_1", "ws://example.com");
assert!(conn.messages().is_empty());
assert!(conn.sent_messages().is_empty());
assert!(conn.received_messages().is_empty());
assert_eq!(conn.message_count(), 0);
}
#[test]
fn test_websocket_mock_response_chaining() {
let response = MockWebSocketResponse::new()
.with_text("msg1")
.with_binary(vec![1, 2])
.with_text("msg2")
.with_delay(500);
assert_eq!(response.messages.len(), 3);
assert_eq!(response.delay_ms, 500);
assert!(response.messages[0].is_text());
assert!(response.messages[1].is_binary());
assert!(response.messages[2].is_text());
}
#[test]
fn test_websocket_message_json_valid_nested() {
let msg = WebSocketMessage::text(
r#"{"user":{"name":"Alice","age":30},"active":true}"#,
MessageDirection::Received,
0,
);
let data: serde_json::Value = msg.json().unwrap();
assert_eq!(data["user"]["name"], "Alice");
assert_eq!(data["user"]["age"], 30);
assert_eq!(data["active"], true);
}
#[test]
fn test_websocket_monitor_active_vs_total_count() {
let mut monitor = WebSocketMonitor::new();
let id1 = monitor.connect("ws://example1.com");
let id2 = monitor.connect("ws://example2.com");
let _id3 = monitor.connect("ws://example3.com");
assert_eq!(monitor.connection_count(), 3);
assert_eq!(monitor.active_connection_count(), 3);
monitor.disconnect(&id1, 1000, "Normal");
assert_eq!(monitor.connection_count(), 3);
assert_eq!(monitor.active_connection_count(), 2);
monitor.disconnect(&id2, 1000, "Normal");
assert_eq!(monitor.connection_count(), 3);
assert_eq!(monitor.active_connection_count(), 1);
}
#[test]
fn test_websocket_message_clone() {
let msg = WebSocketMessage::text("hello", MessageDirection::Sent, 100)
.with_connection("conn_1");
let cloned = msg.clone();
assert_eq!(cloned.data, msg.data);
assert_eq!(cloned.timestamp_ms, msg.timestamp_ms);
assert_eq!(cloned.connection_id, msg.connection_id);
}
#[test]
fn test_websocket_mock_clone() {
let mock = WebSocketMock::new("example.com")
.on_message("test", MockWebSocketResponse::new().with_text("response"))
.once();
let cloned = mock.clone();
assert_eq!(cloned.url_pattern, mock.url_pattern);
assert_eq!(cloned.message_pattern, mock.message_pattern);
assert_eq!(cloned.once, mock.once);
}
#[test]
fn test_mock_websocket_response_clone() {
let response = MockWebSocketResponse::new()
.with_text("msg")
.with_delay(100);
let cloned = response.clone();
assert_eq!(cloned.messages.len(), response.messages.len());
assert_eq!(cloned.delay_ms, response.delay_ms);
}
#[test]
fn test_websocket_state_serialization() {
let state = WebSocketState::Open;
let json = serde_json::to_string(&state).unwrap();
let deserialized: WebSocketState = serde_json::from_str(&json).unwrap();
assert_eq!(state, deserialized);
}
#[test]
fn test_message_type_serialization() {
let msg_type = MessageType::Binary;
let json = serde_json::to_string(&msg_type).unwrap();
let deserialized: MessageType = serde_json::from_str(&json).unwrap();
assert_eq!(msg_type, deserialized);
}
#[test]
fn test_message_direction_serialization() {
let direction = MessageDirection::Sent;
let json = serde_json::to_string(&direction).unwrap();
let deserialized: MessageDirection = serde_json::from_str(&json).unwrap();
assert_eq!(direction, deserialized);
}
#[test]
fn test_websocket_message_serialization() {
let msg = WebSocketMessage::text("hello", MessageDirection::Sent, 1000)
.with_connection("conn_1");
let json = serde_json::to_string(&msg).unwrap();
let deserialized: WebSocketMessage = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.data, "hello");
assert_eq!(deserialized.timestamp_ms, 1000);
assert_eq!(deserialized.connection_id, "conn_1");
assert!(deserialized.raw_data.is_none());
}
}
}