use super::e2e_test_server::E2eTestServer;
use super::test_ua::TestUaEvent;
use crate::config::MediaProxyMode;
use anyhow::Result;
use std::sync::Arc;
use std::time::Duration;
use tokio::time::{sleep, timeout};
use tracing::info;
fn pcma_rtp_sdp(ip: &str, port: u16) -> String {
format!(
"v=0\r\n\
o=- 1778630403 1778630404 IN IP4 {ip}\r\n\
s=-\r\n\
c=IN IP4 {ip}\r\n\
t=0 0\r\n\
m=audio {port} RTP/AVP 8 126\r\n\
a=rtpmap:8 PCMA/8000\r\n\
a=rtpmap:126 telephone-event/8000\r\n\
a=sendrecv\r\n"
)
}
fn webrtc_sdp(ip: &str, port: u16) -> String {
format!(
"v=0\r\n\
o=- 654321 654321 IN IP4 {ip}\r\n\
s=-\r\n\
c=IN IP4 {ip}\r\n\
t=0 0\r\n\
m=audio {port} UDP/TLS/RTP/SAVPF 111 8 101\r\n\
a=rtpmap:111 opus/48000/2\r\n\
a=rtpmap:8 PCMA/8000\r\n\
a=rtpmap:101 telephone-event/8000\r\n\
a=fingerprint:sha-256 AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99\r\n\
a=setup:actpass\r\n\
a=ice-ufrag:testufrag\r\n\
a=ice-pwd:testicepwd1234567890\r\n\
a=mid:0\r\n\
a=sendrecv\r\n\
a=rtcp-mux\r\n"
)
}
#[tokio::test]
async fn test_webrtc_rtp_caller_gate_opens_on_same_sdp_200ok() -> Result<()> {
let _ = tracing_subscriber::fmt::try_init();
let server = Arc::new(E2eTestServer::start_with_mode(MediaProxyMode::All).await?);
let alice = Arc::new(server.create_ua("alice").await?);
let bob = server.create_ua("bob").await?;
sleep(Duration::from_millis(200)).await;
let bob_sdp = pcma_rtp_sdp("127.0.0.1", 12345);
let alice_sdp = webrtc_sdp("127.0.0.1", 54321);
let alice_clone = alice.clone();
let caller_handle = crate::utils::spawn(async move {
timeout(
Duration::from_secs(15),
alice_clone.make_call("bob", Some(alice_sdp)),
)
.await
});
let mut bob_dialog_id = None;
for _ in 0..100 {
let events = bob.process_dialog_events().await?;
for event in events {
if let TestUaEvent::IncomingCall(id, _offer) = event {
bob_dialog_id = Some(id.clone());
info!("Bob: sending 183 + early-media SDP");
bob.send_ringing(&id, Some(bob_sdp.clone())).await?;
sleep(Duration::from_millis(400)).await;
info!("Bob: sending 200 OK with SAME SDP (bug trigger)");
bob.answer_call(&id, Some(bob_sdp.clone())).await?;
break;
}
}
if bob_dialog_id.is_some() {
break;
}
sleep(Duration::from_millis(100)).await;
}
let bob_id = bob_dialog_id.ok_or_else(|| anyhow::anyhow!("Bob never received INVITE"))?;
let alice_dialog_id = caller_handle
.await
.map_err(|_| anyhow::anyhow!("Caller task panicked"))?
.map_err(|_| anyhow::anyhow!("Call timed out"))?
.map_err(|e| anyhow::anyhow!("Call setup failed: {}", e))?;
info!(
"Call established: alice={}, bob={}",
alice_dialog_id, bob_id
);
sleep(Duration::from_millis(500)).await;
for _ in 0..10 {
let _ = alice.process_dialog_events().await;
let _ = bob.process_dialog_events().await;
sleep(Duration::from_millis(100)).await;
}
let registry = &server.registry;
let sessions = registry.list_recent(10);
let session_id = sessions
.first()
.map(|s| s.session_id.clone())
.ok_or_else(|| anyhow::anyhow!("No active session found in registry"))?;
info!("Found session: {}", session_id);
let handle = registry
.get_handle(&session_id)
.ok_or_else(|| anyhow::anyhow!("No handle for session"))?;
let snapshot = handle
.snapshot()
.ok_or_else(|| anyhow::anyhow!("No snapshot available"))?;
info!(
session_id = %snapshot.id,
state = ?snapshot.state,
bridge_active = snapshot.bridge_active,
caller_gate_open = snapshot.caller_gate_open,
"Session snapshot after 200 OK with identical SDP"
);
alice.hangup(&alice_dialog_id).await.ok();
sleep(Duration::from_millis(200)).await;
server.stop();
assert!(
snapshot.caller_gate_open,
"BUG REPRODUCED: caller_gate is still closed after 200 OK with same SDP. \
Root cause: prepare_caller_answer_from_callee_sdp returns early at line 3943 \
without calling open_caller_gate(). The bridge's run_forward_loop silently \
drops all WebRTC→RTP audio at bridge.rs:1740-1744."
);
info!("test_webrtc_rtp_caller_gate_opens_on_same_sdp_200ok PASSED");
Ok(())
}