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);
}