use libremetaverse_voice_vivox::VivoxControlClient; use std::net::Ipv4Addr; use std::time::Duration; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::net::TcpListener; const EXPECTED_ACTIONS: [&str; 10] = [ "Aux.GetCaptureDevices.1", "Aux.GetRenderDevices.1", "Connector.Create.1", "Account.Login.1", "Session.Create.1", "Session.Connect.1", "Session.SetParticipantVolumeForMe.1", "Session.Terminate.1", "Account.Logout.1", "Connector.InitiateShutdown.1", ]; fn response(id: &str, action: &str, result: &str) -> String { format!( "00OK{result}\n\n\n" ) } #[tokio::test] async fn native_vivox_protocol_preserves_control_order_events_and_teardown() { let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await.unwrap(); let endpoint = listener.local_addr().unwrap(); let server = tokio::spawn(async move { let (stream, _) = listener.accept().await.unwrap(); let (reader, mut writer) = stream.into_split(); let mut reader = BufReader::new(reader); let mut actions = Vec::new(); loop { let mut line = String::new(); assert_ne!(reader.read_line(&mut line).await.unwrap(), 0); let line = line.trim(); if line.is_empty() { continue; } let id = line .split("requestId=\"") .nth(1) .and_then(|value| value.split('"').next()) .unwrap(); let action = line .split("action=\"") .nth(1) .and_then(|value| value.split('"').next()) .unwrap(); actions.push(action.to_owned()); let result = match action { "Aux.GetCaptureDevices.1" => { "MicMic" } "Aux.GetRenderDevices.1" => { "SpeakersSpeakers" } "Connector.Create.1" => "connector-secret", "Account.Login.1" => "account-secret", "Session.Create.1" => "session-secret", "Session.Connect.1" | "Session.SetParticipantVolumeForMe.1" | "Session.Terminate.1" | "Account.Logout.1" | "Connector.InitiateShutdown.1" => "", _ => panic!("unexpected Vivox action {action}"), }; writer .write_all(response(id, action, result).as_bytes()) .await .unwrap(); if action == "Session.Connect.1" { writer.write_all(b"session-secretsip:participant-token@example.testAlice Residenttrue00.25\n\n\n").await.unwrap(); } if action == "Connector.InitiateShutdown.1" { writer.shutdown().await.unwrap(); return actions; } } }); let mut client = VivoxControlClient::connect(endpoint, Duration::from_secs(2)) .await .unwrap(); assert_eq!(client.capture_devices().await.unwrap().available, ["Mic"]); assert_eq!( client.render_devices().await.unwrap().available, ["Speakers"] ); client .create_connector("https://www.voice.example.test/api2/") .await .unwrap(); client.login("voice-user", "voice-password").await.unwrap(); let session = client .create_session("sip:channel-token@example.test", "Parity Region", None) .await .unwrap(); client.connect_session(&session).await.unwrap(); let event = client.next_event().await.unwrap(); assert_eq!(event.kind, "ParticipantPropertiesEvent"); assert_eq!(event.display_name.as_deref(), Some("Alice Resident")); assert_eq!(event.is_speaking, Some(true)); let participant = event.participant_uri.unwrap(); client .set_participant_volume(&session, &participant, 0) .await .unwrap(); client.terminate_session(&session).await.unwrap(); client.logout().await.unwrap(); client.shutdown().await.unwrap(); client.shutdown().await.unwrap(); let actions = server.await.unwrap(); assert_eq!(actions, EXPECTED_ACTIONS); }