Co-authored-by: Aaron D. Lee <himself@adlee.work> Co-committed-by: Aaron D. Lee <himself@adlee.work>
70 lines
2.8 KiB
Rust
70 lines
2.8 KiB
Rust
//! App-level WS ping emission on the trunk media-stream WS (deploy
|
|
//! slice A §5.2).
|
|
//!
|
|
//! Spins the real router on an ephemeral listener, performs the Twilio
|
|
//! connected/start handshake as a tokio-tungstenite client, then asserts
|
|
//! an engine-originated Ping frame arrives well within CI slack of the
|
|
//! configured interval. Twilio documents no keepalive of its own — this
|
|
//! test is the contract that WE originate the pings.
|
|
|
|
use futures_util::{SinkExt, StreamExt};
|
|
use rutster_trunk::twilio_media_streams::{RegisterTrunkInboundChannel, TwilioMediaStreamsServer};
|
|
use tokio::sync::mpsc;
|
|
use tokio_tungstenite::tungstenite::Message;
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn engine_originates_ws_ping_at_configured_interval() {
|
|
let (register_tx, mut register_rx) = mpsc::channel::<RegisterTrunkInboundChannel>(4);
|
|
let app = TwilioMediaStreamsServer::router(register_tx, std::time::Duration::from_millis(100));
|
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
|
let addr = listener.local_addr().unwrap();
|
|
tokio::spawn(async move {
|
|
axum::serve(listener, app).await.unwrap();
|
|
});
|
|
|
|
// Stub media thread: ack the registration and hold the channel ends
|
|
// alive so the pump loop stays up for the whole test.
|
|
let registered = tokio::spawn(async move {
|
|
let register = register_rx.recv().await.expect("pump registers");
|
|
let RegisterTrunkInboundChannel {
|
|
inbound_from_twilio_rx,
|
|
outbound_to_twilio_tx,
|
|
reply,
|
|
..
|
|
} = register;
|
|
let _ = reply.send(rutster_call_model::ChannelId::new());
|
|
(inbound_from_twilio_rx, outbound_to_twilio_tx)
|
|
});
|
|
|
|
let (mut ws, _resp) =
|
|
tokio_tungstenite::connect_async(format!("ws://{addr}/twilio/media-stream"))
|
|
.await
|
|
.expect("WS connect");
|
|
ws.send(Message::Text(
|
|
r#"{"event":"connected","protocol":"twilio-media-stream","version":"1.0.0"}"#.into(),
|
|
))
|
|
.await
|
|
.unwrap();
|
|
ws.send(Message::Text(
|
|
r#"{"event":"start","start":{"streamSid":"MZping","callSid":"CAping"}}"#.into(),
|
|
))
|
|
.await
|
|
.unwrap();
|
|
let _register = registered.await.unwrap();
|
|
|
|
// 100 ms interval → a Ping must land within 2 s (20 intervals of
|
|
// slack for a loaded CI runner; the point is emission, not cadence
|
|
// precision).
|
|
let got_ping = tokio::time::timeout(std::time::Duration::from_secs(2), async {
|
|
while let Some(msg) = ws.next().await {
|
|
if matches!(msg.expect("WS frame"), Message::Ping(_)) {
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
})
|
|
.await
|
|
.expect("a Ping frame must arrive before the deadline");
|
|
assert!(got_ping, "expected an engine-originated Ping frame");
|
|
}
|