//! End-to-end control-plane test: boots the full sidecar (UDP + control socket //! + SFU loop) in-process and speaks the JSON-lines protocol like PHP does. use std::io::{BufRead, BufReader, Write}; use std::net::IpAddr; use std::os::unix::net::UnixStream; use std::path::PathBuf; use std::thread; use std::time::{Duration, Instant}; use blax_webrtc_sfu::server::{self, Config}; fn start_sidecar(socket: PathBuf) -> thread::JoinHandle<()> { let config = Config { socket, udp_ip: IpAddr::from([127, 0, 0, 1]), udp_port: 0, public_ip: None, }; thread::spawn(move || server::run(config).expect("sidecar to run")) } fn connect(socket: &PathBuf) -> UnixStream { let deadline = Instant::now() + Duration::from_secs(5); loop { if let Ok(stream) = UnixStream::connect(socket) { return stream; } assert!(Instant::now() < deadline, "control socket never came up"); thread::sleep(Duration::from_millis(20)); } } fn roundtrip(reader: &mut impl BufRead, writer: &mut impl Write, req: &str) -> serde_json::Value { writeln!(writer, "{req}").unwrap(); writer.flush().unwrap(); let mut line = String::new(); reader.read_line(&mut line).unwrap(); serde_json::from_str(&line).unwrap_or_else(|e| panic!("bad reply {line:?}: {e}")) } #[test] fn control_protocol_end_to_end() { let socket = std::env::temp_dir().join(format!("blax-sfu-test-{}.sock", std::process::id())); let _ = std::fs::remove_file(&socket); let sidecar = start_sidecar(socket.clone()); let stream = connect(&socket); let mut reader = BufReader::new(stream.try_clone().unwrap()); let mut writer = stream; // ping let reply = roundtrip(&mut reader, &mut writer, r#"{"id":1,"cmd":"ping"}"#); assert_eq!(reply["id"], 1); assert_eq!(reply["ok"], true); // empty stats let reply = roundtrip(&mut reader, &mut writer, r#"{"id":2,"cmd":"stats"}"#); assert_eq!(reply["ok"], true); assert_eq!(reply["rooms"], serde_json::json!([])); // add_peer with a garbage offer errors cleanly let reply = roundtrip( &mut reader, &mut writer, r#"{"id":3,"cmd":"add_peer","room":"r1","peer":"p1","offer":"not sdp"}"#, ); assert_eq!(reply["ok"], false); assert!( reply["error"].as_str().unwrap().contains("offer"), "unexpected error: {reply}" ); // unknown peer errors cleanly let reply = roundtrip( &mut reader, &mut writer, r#"{"id":4,"cmd":"mute_peer","room":"r1","peer":"ghost","muted":true}"#, ); assert_eq!(reply["ok"], false); assert!(reply["error"].as_str().unwrap().contains("no such peer")); // malformed json → error with id 0, connection stays usable let reply = roundtrip(&mut reader, &mut writer, r#"{"id":5,"cmd":"nope"}"#); assert_eq!(reply["ok"], false); let reply = roundtrip(&mut reader, &mut writer, r#"{"id":6,"cmd":"ping"}"#); assert_eq!(reply["ok"], true); // shutdown stops the loop and removes the socket file let reply = roundtrip(&mut reader, &mut writer, r#"{"id":7,"cmd":"shutdown"}"#); assert_eq!(reply["ok"], true); sidecar.join().expect("sidecar thread to finish"); assert!(!socket.exists(), "socket file cleaned up on shutdown"); }