#![cfg(feature = "webrtc-proxy")]
use fandhe_backend_core::{Handler, Server, handle_connection};
use fandhe_backend_http::request::RequestHead;
use fandhe_backend_http::response::Response;
use fandhe_backend_plugin_webrtc_proxy::ProxyConfig;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;
struct NotCalledHandler;
impl Handler for NotCalledHandler {
fn handle(&self, _head: &RequestHead, _body: &[u8]) -> fandhe_backend_routes::HandlerFuture {
panic!("plugin::try_intercept が Some を返したのに既定 Handler が呼ばれた");
}
}
struct FixedOkHandler;
impl Handler for FixedOkHandler {
fn handle(&self, _head: &RequestHead, _body: &[u8]) -> fandhe_backend_routes::HandlerFuture {
Box::pin(std::future::ready(Response::new(200, b"ok".to_vec())))
}
}
async fn spawn_mock_upstream() -> std::net::SocketAddr {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(async move {
if let Ok((mut stream, _)) = listener.accept().await {
let mut buf = vec![0u8; 4096];
let _ = stream.read(&mut buf).await;
let body = b"answer-sdp";
let response = format!("HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n", body.len());
let _ = stream.write_all(response.as_bytes()).await;
let _ = stream.write_all(body).await;
}
});
addr
}
async fn roundtrip(server: &Server, request: &[u8]) -> String {
let (mut client, server_stream) = tokio::io::duplex(8192);
client.write_all(request).await.unwrap();
client.shutdown().await.unwrap();
handle_connection(server, server_stream).await;
let mut out = Vec::new();
client.read_to_end(&mut out).await.unwrap();
String::from_utf8(out).unwrap()
}
#[tokio::test]
async fn intercepted_rtc_offer_bypasses_default_handler_and_forwards_upstream() {
let upstream_addr = spawn_mock_upstream().await;
let config = ProxyConfig::new(upstream_addr.to_string());
let server = Server::new().webrtc_proxy(config).handler(NotCalledHandler);
let body = b"offer-sdp";
let request = format!(
"POST /rtc/offer HTTP/1.1\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
body.len(),
std::str::from_utf8(body).unwrap()
);
let response = roundtrip(&server, request.as_bytes()).await;
assert!(response.starts_with("HTTP/1.1 200 OK\r\n"));
assert!(response.contains("Content-Type: application/sdp\r\n"));
assert!(response.ends_with("answer-sdp"));
}
#[tokio::test]
async fn unrelated_path_falls_through_to_default_handler() {
let config = ProxyConfig::new("127.0.0.1:1");
let server = Server::new().webrtc_proxy(config).handler(FixedOkHandler);
let response = roundtrip(&server, b"GET / HTTP/1.1\r\nConnection: close\r\n\r\n").await;
assert!(response.starts_with("HTTP/1.1 200 OK\r\n"));
assert!(response.ends_with("ok"));
}
#[tokio::test]
async fn upstream_failure_returns_bad_gateway_with_reason_and_content_type() {
let config =
ProxyConfig::new("127.0.0.1:1").with_connect_timeout(std::time::Duration::from_millis(200));
let server = Server::new().webrtc_proxy(config).handler(NotCalledHandler);
let body = b"offer-sdp";
let request = format!(
"POST /rtc/offer HTTP/1.1\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
body.len(),
std::str::from_utf8(body).unwrap()
);
let response = roundtrip(&server, request.as_bytes()).await;
assert!(
response.starts_with("HTTP/1.1 502 Bad Gateway\r\n")
|| response.starts_with("HTTP/1.1 504 Gateway Timeout\r\n"),
"reason phrase が空文字に劣化していないことを含めて検証: {response:?}"
);
assert!(response.contains("Content-Type: text/plain; charset=utf-8\r\n"));
}
#[tokio::test]
async fn config_built_via_core_reexport_forwards_upstream() {
let upstream_addr = spawn_mock_upstream().await;
let config =
fandhe_backend_core::plugin_webrtc_proxy::ProxyConfig::new(upstream_addr.to_string());
let server = Server::new().webrtc_proxy(config).handler(NotCalledHandler);
let body = b"offer-sdp";
let request = format!(
"POST /rtc/offer HTTP/1.1\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
body.len(),
std::str::from_utf8(body).unwrap()
);
let response = roundtrip(&server, request.as_bytes()).await;
assert!(response.starts_with("HTTP/1.1 200 OK\r\n"));
assert!(response.ends_with("answer-sdp"));
}