0.3.4-alpha.4
This commit is contained in:
134
crates/common/game-realtime-websocket-lib/tests/robustness.rs
Normal file
134
crates/common/game-realtime-websocket-lib/tests/robustness.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
// file: crates/common/game-realtime-websocket-lib/tests/robustness.rs
|
||||
// version: 1
|
||||
|
||||
//! Negative and bounded lifecycle tests for the WebSocket transport backend.
|
||||
|
||||
use futures_util::SinkExt; // rust-rules: trait-import
|
||||
use game_realtime_transport_lib::RealtimeConnection; // rust-rules: trait-import
|
||||
use game_realtime_transport_lib::RealtimeReceiver; // rust-rules: trait-import
|
||||
use game_realtime_transport_lib::RealtimeSender; // rust-rules: trait-import
|
||||
|
||||
const SMALL_MESSAGE_LIMIT: usize = 32;
|
||||
const TEST_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3);
|
||||
|
||||
type RawClient = tokio_tungstenite::WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>;
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn outbound_payload_over_the_configured_limit_is_rejected_before_write() {
|
||||
let config = small_message_config();
|
||||
let (server_connection, client_connection) = establish_backend_pair(config).await;
|
||||
let (_server_sender, _server_receiver) = server_connection.split();
|
||||
let (mut client_sender, _client_receiver) = client_connection.split();
|
||||
let oversized = game_realtime_transport_lib::TransportMessage::new(vec![7; SMALL_MESSAGE_LIMIT + 1]);
|
||||
let result = client_sender.send(oversized).await;
|
||||
match result {
|
||||
Ok(()) => panic!("oversized outbound payload was accepted"),
|
||||
Err(error) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::MessageTooLarge),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn inbound_payload_over_the_configured_limit_is_rejected_by_tungstenite() {
|
||||
let config = small_message_config();
|
||||
let (server_connection, mut raw_client) = establish_backend_server_with_raw_client(config).await;
|
||||
let (_server_sender, mut server_receiver) = server_connection.split();
|
||||
let send = raw_client.send(tokio_tungstenite::tungstenite::Message::binary(vec![3; SMALL_MESSAGE_LIMIT + 1])).await;
|
||||
assert!(send.is_ok());
|
||||
let receive = tokio::time::timeout(TEST_TIMEOUT, server_receiver.receive()).await;
|
||||
match receive {
|
||||
Ok(Ok(value)) => panic!("oversized inbound payload produced a successful receive: {value:?}"),
|
||||
Ok(Err(error)) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::MessageTooLarge),
|
||||
Err(_) => panic!("oversized inbound payload did not complete within the test timeout"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn text_message_is_rejected_by_the_binary_transport_contract() {
|
||||
let (server_connection, mut raw_client) = establish_backend_server_with_raw_client(game_realtime_websocket_lib::WebSocketConfig::default()).await;
|
||||
let (_server_sender, mut server_receiver) = server_connection.split();
|
||||
let send = raw_client.send(tokio_tungstenite::tungstenite::Message::text("text is outside the transport contract")).await;
|
||||
assert!(send.is_ok());
|
||||
let receive = tokio::time::timeout(TEST_TIMEOUT, server_receiver.receive()).await;
|
||||
match receive {
|
||||
Ok(Ok(value)) => panic!("text message produced a successful receive: {value:?}"),
|
||||
Ok(Err(error)) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::Protocol),
|
||||
Err(_) => panic!("text-message rejection did not complete within the test timeout"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn peer_drop_without_close_handshake_is_reported_as_protocol_failure() {
|
||||
let (server_connection, raw_client) = establish_backend_server_with_raw_client(game_realtime_websocket_lib::WebSocketConfig::default()).await;
|
||||
let (_server_sender, mut server_receiver) = server_connection.split();
|
||||
drop(raw_client);
|
||||
let receive = tokio::time::timeout(TEST_TIMEOUT, server_receiver.receive()).await;
|
||||
match receive {
|
||||
Ok(Ok(value)) => panic!("abrupt peer drop was reported as a successful receive: {value:?}"),
|
||||
Ok(Err(error)) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::Protocol),
|
||||
Err(_) => panic!("abrupt peer drop was not observed within the test timeout"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn silent_tcp_peer_hits_the_server_handshake_deadline() {
|
||||
let config = game_realtime_websocket_lib::WebSocketConfig::default().with_connect_timeout(std::time::Duration::from_millis(50));
|
||||
let bind_address = std::net::SocketAddr::from(([127, 0, 0, 1], 0));
|
||||
let listener = match game_realtime_websocket_lib::WebSocketListener::bind_with_config(bind_address, config).await {
|
||||
Ok(value) => value,
|
||||
Err(error) => panic!("bounded listener bind failed: {error}"),
|
||||
};
|
||||
let _silent_peer = match tokio::net::TcpStream::connect(listener.local_addr()).await {
|
||||
Ok(value) => value,
|
||||
Err(error) => panic!("silent TCP peer connection failed: {error}"),
|
||||
};
|
||||
let accept = tokio::time::timeout(TEST_TIMEOUT, listener.accept()).await;
|
||||
match accept {
|
||||
Ok(Ok(_connection)) => panic!("silent TCP peer unexpectedly completed a WebSocket handshake"),
|
||||
Ok(Err(error)) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::Timeout),
|
||||
Err(_) => panic!("server handshake timeout did not fire within the outer test timeout"),
|
||||
}
|
||||
}
|
||||
|
||||
fn small_message_config() -> game_realtime_websocket_lib::WebSocketConfig {
|
||||
return game_realtime_websocket_lib::WebSocketConfig::default().with_max_message_size(SMALL_MESSAGE_LIMIT).with_max_frame_size(SMALL_MESSAGE_LIMIT);
|
||||
}
|
||||
|
||||
async fn establish_backend_pair(
|
||||
config: game_realtime_websocket_lib::WebSocketConfig,
|
||||
) -> (game_realtime_websocket_lib::WebSocketConnection, game_realtime_websocket_lib::WebSocketConnection) {
|
||||
let bind_address = std::net::SocketAddr::from(([127, 0, 0, 1], 0));
|
||||
let listener = match game_realtime_websocket_lib::WebSocketListener::bind_with_config(bind_address, config).await {
|
||||
Ok(value) => value,
|
||||
Err(error) => panic!("loopback listener bind failed: {error}"),
|
||||
};
|
||||
let endpoint = format!("ws://{}/", listener.local_addr());
|
||||
let pair = tokio::time::timeout(TEST_TIMEOUT, async {
|
||||
return tokio::join!(listener.accept(), game_realtime_websocket_lib::connect_with_config(endpoint.as_str(), config));
|
||||
})
|
||||
.await;
|
||||
return match pair {
|
||||
Ok((Ok(server), Ok(client))) => (server, client),
|
||||
Ok((_server, _client)) => panic!("loopback connection establishment failed"),
|
||||
Err(_) => panic!("loopback connection establishment timed out"),
|
||||
};
|
||||
}
|
||||
|
||||
async fn establish_backend_server_with_raw_client(
|
||||
config: game_realtime_websocket_lib::WebSocketConfig,
|
||||
) -> (game_realtime_websocket_lib::WebSocketConnection, RawClient) {
|
||||
let bind_address = std::net::SocketAddr::from(([127, 0, 0, 1], 0));
|
||||
let listener = match game_realtime_websocket_lib::WebSocketListener::bind_with_config(bind_address, config).await {
|
||||
Ok(value) => value,
|
||||
Err(error) => panic!("loopback listener bind failed: {error}"),
|
||||
};
|
||||
let endpoint = format!("ws://{}/", listener.local_addr());
|
||||
let pair = tokio::time::timeout(TEST_TIMEOUT, async {
|
||||
return tokio::join!(listener.accept(), tokio_tungstenite::connect_async(endpoint.as_str()));
|
||||
})
|
||||
.await;
|
||||
return match pair {
|
||||
Ok((Ok(server), Ok((client, _response)))) => (server, client),
|
||||
Ok((_server, _client)) => panic!("raw-client loopback connection establishment failed"),
|
||||
Err(_) => panic!("raw-client loopback connection establishment timed out"),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user