// file: crates/common/game-realtime-webtransport-lib/tests/robustness.rs // version: 2 //! Negative and bounded lifecycle tests for the native reliable WebTransport backend. const SHORT_OPERATION_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(50); const SMALL_MESSAGE_LIMIT: usize = 32; const TEST_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3); #[tokio::test(flavor = "current_thread")] async fn outbound_payload_over_the_configured_limit_is_rejected_before_write() { let config = game_realtime_webtransport_lib::WebTransportConfig::default().with_max_message_size(SMALL_MESSAGE_LIMIT); let (_server_connection, client_connection) = establish_backend_pair(config, config).await; let (mut client_sender, _client_receiver) = game_realtime_transport_lib::RealtimeConnection::split(client_connection); let oversized = game_realtime_transport_lib::TransportMessage::new(vec![7; SMALL_MESSAGE_LIMIT + 1]); let result = game_realtime_transport_lib::RealtimeSender::send(&mut client_sender, oversized).await; match result { Ok(()) => panic!("oversized outbound WebTransport 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_before_allocation() { let server_config = game_realtime_webtransport_lib::WebTransportConfig::default().with_max_message_size(SMALL_MESSAGE_LIMIT); let client_config = game_realtime_webtransport_lib::WebTransportConfig::default().with_max_message_size(SMALL_MESSAGE_LIMIT + 1); let (server_connection, client_connection) = establish_backend_pair(server_config, client_config).await; let (_server_sender, mut server_receiver) = game_realtime_transport_lib::RealtimeConnection::split(server_connection); let (mut client_sender, _client_receiver) = game_realtime_transport_lib::RealtimeConnection::split(client_connection); let payload = game_realtime_transport_lib::TransportMessage::new(vec![3; SMALL_MESSAGE_LIMIT + 1]); if let Err(error) = game_realtime_transport_lib::RealtimeSender::send(&mut client_sender, payload).await { panic!("client failed to send payload allowed by its local bound: {error}"); } let receive = tokio::time::timeout(TEST_TIMEOUT, game_realtime_transport_lib::RealtimeReceiver::receive(&mut server_receiver)).await; match receive { Ok(Ok(value)) => panic!("oversized inbound WebTransport payload produced a successful receive: {value:?}"), Ok(Err(error)) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::MessageTooLarge), Err(_) => panic!("oversized inbound WebTransport payload did not complete within the test timeout"), } } #[tokio::test(flavor = "current_thread")] async fn explicit_sender_abort_is_observed_as_aborted_receive() { let config = game_realtime_webtransport_lib::WebTransportConfig::default(); let (server_connection, client_connection) = establish_backend_pair(config, config).await; let (_server_sender, mut server_receiver) = game_realtime_transport_lib::RealtimeConnection::split(server_connection); let (mut client_sender, _client_receiver) = game_realtime_transport_lib::RealtimeConnection::split(client_connection); if let Err(error) = client_sender.abort(0x41) { panic!("client sender abort failed: {error}"); } let receive = tokio::time::timeout(TEST_TIMEOUT, game_realtime_transport_lib::RealtimeReceiver::receive(&mut server_receiver)).await; match receive { Ok(Ok(value)) => panic!("reset WebTransport stream produced a successful receive: {value:?}"), Ok(Err(error)) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::Aborted), Err(_) => panic!("peer reset was not observed within the test timeout"), } } #[tokio::test(flavor = "current_thread")] async fn explicit_receiver_abort_makes_the_local_receive_half_terminal() { let config = game_realtime_webtransport_lib::WebTransportConfig::default(); let (server_connection, _client_connection) = establish_backend_pair(config, config).await; let (_server_sender, mut server_receiver) = game_realtime_transport_lib::RealtimeConnection::split(server_connection); if let Err(error) = server_receiver.abort(0x42) { panic!("server receiver abort failed: {error}"); } let result = game_realtime_transport_lib::RealtimeReceiver::receive(&mut server_receiver).await; match result { Ok(value) => panic!("aborted WebTransport receiver produced a successful receive: {value:?}"), Err(error) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::Aborted), } } #[tokio::test(flavor = "current_thread")] async fn dropping_sender_without_close_resets_the_stream_instead_of_synthesizing_fin() { let config = game_realtime_webtransport_lib::WebTransportConfig::default(); let (server_connection, client_connection) = establish_backend_pair(config, config).await; let (_server_sender, mut server_receiver) = game_realtime_transport_lib::RealtimeConnection::split(server_connection); let (client_sender, _client_receiver) = game_realtime_transport_lib::RealtimeConnection::split(client_connection); drop(client_sender); let receive = tokio::time::timeout(TEST_TIMEOUT, game_realtime_transport_lib::RealtimeReceiver::receive(&mut server_receiver)).await; match receive { Ok(Ok(value)) => panic!("dropped WebTransport sender produced a clean receive result: {value:?}"), Ok(Err(error)) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::Aborted), Err(_) => panic!("sender drop reset was not observed within the test timeout"), } } #[tokio::test(flavor = "current_thread")] async fn primary_stream_accept_honors_the_configured_deadline() { let config = game_realtime_webtransport_lib::WebTransportConfig::default().with_primary_stream_timeout(SHORT_OPERATION_TIMEOUT); let (server_session, _client_session) = establish_sessions(config, config).await; let result = server_session.accept_primary_connection().await; match result { Ok(_) => panic!("primary stream accept unexpectedly succeeded without a peer-created stream"), Err(error) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::Timeout), } } async fn establish_backend_pair( server_transport: game_realtime_webtransport_lib::WebTransportConfig, client_transport: game_realtime_webtransport_lib::WebTransportConfig, ) -> (game_realtime_webtransport_lib::WebTransportConnection, game_realtime_webtransport_lib::WebTransportConnection) { let (server_session, client_session) = establish_sessions(server_transport, client_transport).await; let client_connection = match client_session.open_primary_connection().await { Ok(value) => value, Err(error) => panic!("client primary stream open failed: {error}"), }; let server_connection = match server_session.accept_primary_connection().await { Ok(value) => value, Err(error) => panic!("server primary stream accept failed: {error}"), }; return (server_connection, client_connection); } async fn establish_sessions( server_transport: game_realtime_webtransport_lib::WebTransportConfig, client_transport: game_realtime_webtransport_lib::WebTransportConfig, ) -> (game_realtime_webtransport_lib::WebTransportSession, game_realtime_webtransport_lib::WebTransportSession) { let identity = match game_realtime_webtransport_lib::WebTransportServerIdentity::generate_loopback() { Ok(value) => value, Err(error) => panic!("loopback identity generation failed: {error}"), }; let certificate_hash = identity.certificate_hash().clone(); let server_config = game_realtime_webtransport_lib::WebTransportServerConfig::new(std::net::SocketAddr::from(([127, 0, 0, 1], 0)), identity) .with_transport_config(server_transport); let mut listener = match game_realtime_webtransport_lib::WebTransportListener::bind(server_config) { Ok(value) => value, Err(error) => panic!("WebTransport listener bind failed: {error}"), }; let endpoint = format!("https://{}/robustness", listener.local_addr()); let client_config = match game_realtime_webtransport_lib::WebTransportClientConfig::new(endpoint.as_str(), certificate_hash) { Ok(value) => value.with_transport_config(client_transport), Err(error) => panic!("WebTransport client configuration failed: {error}"), }; let sessions = tokio::time::timeout(TEST_TIMEOUT, async { return tokio::join!(listener.accept(), game_realtime_webtransport_lib::connect(&client_config)); }) .await; return match sessions { Ok((Ok(server), Ok(client))) => (server, client), Ok((Err(error), _)) => panic!("WebTransport server establishment failed: {error}"), Ok((_, Err(error))) => panic!("WebTransport client establishment failed: {error}"), Err(_) => panic!("WebTransport loopback establishment timed out"), }; }