use anyhow::Result;
use openrtc::{
client::Client,
signaling::{Device, DeviceEvent, SessionEvent, SignalingSession},
};
use serde_json::json;
const PROJECT_ID: &str = "pluto-rtc-prod";
const API_KEY: &str = "AIzaSyAxyDE1xSYNAk5Ohe8VvKWi3xHOxB4cNV8";
async fn get_test_token() -> Result<(String, String)> {
let url = format!(
"https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key={}",
API_KEY
);
let client = reqwest::Client::new();
let res = client
.post(&url)
.json(&json!({
"email": "test@gmail.com",
"password": "testing",
"returnSecureToken": true
}))
.send()
.await?;
if !res.status().is_success() {
let err = res.text().await?;
return Err(anyhow::anyhow!("Firebase auth failed: {}", err));
}
let json: serde_json::Value = res.json().await?;
let token = json["idToken"].as_str().unwrap().to_string();
let uid = json["localId"].as_str().unwrap().to_string();
Ok((token, uid))
}
fn init_crypto() {
rustls::crypto::ring::default_provider()
.install_default()
.ok();
}
fn live_enabled() -> bool {
std::env::var("OPENRTC_LIVE_TESTS")
.map(|value| value == "1" || value.eq_ignore_ascii_case("true"))
.unwrap_or(false)
}
fn make_client(token: String, api_key: &str) -> Client {
Client::new(
PROJECT_ID.into(),
api_key.into(),
Box::new(move || Some(token.clone())),
)
}
#[tokio::test]
async fn test_client_creation_without_panic() {
init_crypto();
let client = make_client(
"test-token".to_string(),
"pk_test_1111111111111111111111111111111111111111",
);
assert!(client.current_node_id().await.is_none());
}
#[tokio::test]
async fn test_client_set_node_id() {
init_crypto();
let client = make_client("test-token".to_string(), "parity-test");
client.set_node_id("test-node-001".into()).await;
assert_eq!(client.current_node_id().await, Some("test-node-001".into()));
}
#[tokio::test]
async fn test_client_connection_manager_exists() {
init_crypto();
let client = make_client("test-token".to_string(), "parity-test");
let active = client.connection_manager.list_active().await;
assert_eq!(active.len(), 0);
}
#[tokio::test]
async fn test_device_discovery_returns_devices() -> Result<()> {
if !live_enabled() {
eprintln!("skipping live test: set OPENRTC_LIVE_TESTS=1 to run");
return Ok(());
}
init_crypto();
let (token, uid) = get_test_token().await?;
let client = make_client(token, "parity-test");
client.set_node_id("parity-node-disc".into()).await;
client.init_iroh(None, vec![]).await?;
let ticket = client.endpoint_ticket().await?;
client
.update_presence(&uid, "Parity Test Node", &ticket, None)
.await?;
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
let devices = client.search_devices(&uid).await?;
assert!(!devices.is_empty(), "Should find at least 1 device");
for d in &devices {
assert!(!d.device_id.is_empty());
assert!(!d.device_name.is_empty());
}
let ours = devices.iter().find(|d| d.device_name == "Parity Test Node");
assert!(ours.is_some(), "Should find our own device");
let ours = ours.unwrap();
assert!(ours.online);
Ok(())
}
#[tokio::test]
async fn test_presence_update_and_offline() -> Result<()> {
if !live_enabled() {
eprintln!("skipping live test: set OPENRTC_LIVE_TESTS=1 to run");
return Ok(());
}
init_crypto();
let (token, uid) = get_test_token().await?;
let client = make_client(token, "parity-test");
client.set_node_id("parity-node-presence".into()).await;
client.init_iroh(None, vec![]).await?;
let ticket = client.endpoint_ticket().await?;
client
.update_presence(&uid, "Presence Test", &ticket, Some("{\"test\":true}"))
.await?;
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
let devices = client.search_devices(&uid).await?;
let ours = devices.iter().find(|d| d.device_name == "Presence Test");
assert!(ours.is_some(), "Device should be online");
assert!(ours.unwrap().online);
client.set_offline(&uid).await?;
Ok(())
}
#[tokio::test]
async fn test_presence_with_ttl() -> Result<()> {
if !live_enabled() {
eprintln!("skipping live test: set OPENRTC_LIVE_TESTS=1 to run");
return Ok(());
}
init_crypto();
let (token, uid) = get_test_token().await?;
let client = make_client(token, "parity-test");
client.set_node_id("parity-node-ttl".into()).await;
client.init_iroh(None, vec![]).await?;
let ticket = client.endpoint_ticket().await?;
client
.update_presence_with_ttl(&uid, "TTL Test", &ticket, 120_000, None)
.await?;
Ok(())
}
#[tokio::test]
async fn test_signaling_session_creation() -> Result<()> {
if !live_enabled() {
eprintln!("skipping live test: set OPENRTC_LIVE_TESTS=1 to run");
return Ok(());
}
init_crypto();
let (token, _uid) = get_test_token().await?;
let client = make_client(token, "parity-test");
client.set_node_id("parity-sig-node".into()).await;
let doc_id = client
.send_message("target-node-id", "test-payload", Some("offer"), None)
.await?;
assert!(!doc_id.is_empty(), "Should return a document ID");
Ok(())
}
#[tokio::test]
async fn test_signaling_session_struct_creation() {
let session = SignalingSession {
connection_id: "conn-test-1".into(),
initiator: "node-a".into(),
target: "node-b".into(),
initiator_device_id: "dev-a".into(),
target_device_id: "dev-b".into(),
connection_type: Some("iroh".into()),
offer: None,
offer_e2ee: None,
answer: None,
answer_e2ee: None,
ice_candidates: vec![],
initiator_node_id: Some("node-a".into()),
target_node_id: Some("node-b".into()),
initiator_endpoint_addr: Some("{\"nodeId\":\"node-a\"}".into()),
target_endpoint_addr: None,
intent: Some("file-transfer".into()),
app_tag: Some("plutonium-app".into()),
created_at: Some(1700000000000),
expires_at: Some(1700060000000),
state: "pending".into(),
};
assert_eq!(session.connection_id, "conn-test-1");
assert_eq!(session.state, "pending");
assert_eq!(session.connection_type, Some("iroh".into()));
}
#[tokio::test]
async fn test_device_serialization_matches_ts_shape() {
let device = Device {
app_tag: Some("plutonium-app".into()),
device_id: "dev-rust-01".into(),
user_id: Some("uid-r".into()),
device_name: "Rust Node".into(),
kind: Some("device".into()),
platform_type: Some("desktop".into()),
capabilities: Some(openrtc::signaling::DeviceCapabilities {
can_host: true,
can_sync: true,
read_only: false,
}),
session_id: Some("sess-r".into()),
node_id: Some("node-rust".into()),
tag: Some("plutonium-app".into()),
metadata: None,
online: true,
ticket: Some("ticket-rust".into()),
last_seen_at: None,
expires_at: None,
created_at: None,
updated_at: None,
excluded_peers: vec![],
};
let json = serde_json::to_value(&device).unwrap();
assert_eq!(json["deviceId"], "dev-rust-01");
assert_eq!(json["deviceName"], "Rust Node");
assert_eq!(json["platformType"], "desktop");
assert_eq!(json["online"], true);
assert_eq!(json["nodeId"], "node-rust");
assert_eq!(json["ticket"], "ticket-rust");
assert_eq!(json["tag"], "plutonium-app");
let caps = &json["capabilities"];
assert_eq!(caps["canHost"], true);
assert_eq!(caps["canSync"], true);
assert_eq!(caps["readOnly"], false);
}
#[tokio::test]
async fn test_session_serialization_matches_ts_shape() {
let session = SignalingSession {
connection_id: "conn-rs-1".into(),
initiator: "node-a".into(),
target: "node-b".into(),
initiator_device_id: "dev-a".into(),
target_device_id: "dev-b".into(),
connection_type: Some("iroh".into()),
offer: None,
offer_e2ee: None,
answer: None,
answer_e2ee: None,
ice_candidates: vec![],
initiator_node_id: Some("node-a".into()),
target_node_id: Some("node-b".into()),
initiator_endpoint_addr: Some("{\"nodeId\":\"node-a\"}".into()),
target_endpoint_addr: None,
intent: Some("sync".into()),
app_tag: Some("plutonium-app".into()),
created_at: Some(1700000000000),
expires_at: Some(1700060000000),
state: "pending".into(),
};
let json = serde_json::to_value(&session).unwrap();
assert_eq!(json["connectionId"], "conn-rs-1");
assert_eq!(json["initiator"], "node-a");
assert_eq!(json["target"], "node-b");
assert_eq!(json["initiatorDeviceId"], "dev-a");
assert_eq!(json["targetDeviceId"], "dev-b");
assert_eq!(json["connectionType"], "iroh");
assert_eq!(json["state"], "pending");
assert_eq!(json["initiatorNodeId"], "node-a");
assert_eq!(json["targetNodeId"], "node-b");
assert_eq!(json["initiatorEndpointAddr"], "{\"nodeId\":\"node-a\"}");
assert_eq!(json["intent"], "sync");
assert_eq!(json["appTag"], "plutonium-app");
assert_eq!(json["iceCandidates"], json!([]));
}
#[tokio::test]
async fn test_device_event_serialization() {
let event = DeviceEvent::Added {
device: Device {
app_tag: None,
device_id: "dev-1".into(),
user_id: None,
device_name: "Test".into(),
kind: None,
platform_type: None,
capabilities: None,
session_id: None,
node_id: None,
tag: None,
metadata: None,
online: true,
ticket: None,
last_seen_at: None,
expires_at: None,
created_at: None,
updated_at: None,
excluded_peers: vec![],
},
};
let json = serde_json::to_value(&event).unwrap();
assert_eq!(json["type"], "added");
assert!(json["device"].is_object());
}
#[tokio::test]
async fn test_session_event_serialization() {
let event = SessionEvent::Added {
session: SignalingSession {
connection_id: "conn-evt".into(),
initiator: "a".into(),
target: "b".into(),
initiator_device_id: "da".into(),
target_device_id: "db".into(),
connection_type: None,
offer: None,
offer_e2ee: None,
answer: None,
answer_e2ee: None,
ice_candidates: vec![],
initiator_node_id: None,
target_node_id: None,
initiator_endpoint_addr: None,
target_endpoint_addr: None,
intent: None,
app_tag: None,
created_at: None,
expires_at: None,
state: "pending".into(),
},
};
let json = serde_json::to_value(&event).unwrap();
assert_eq!(json["type"], "added");
assert!(json["session"].is_object());
assert_eq!(json["session"]["connectionId"], "conn-evt");
}
#[tokio::test]
async fn test_ts_device_json_deserializes_into_rust() {
let ts_json = r#"{
"deviceId": "dev-ts-01",
"userId": "uid-ts",
"deviceName": "Web Browser",
"platformType": "web",
"capabilities": { "canHost": false, "canSync": true, "readOnly": true },
"sessionId": "sess-ts",
"nodeId": "node-ts",
"tag": "plutonium-app",
"metadata": "{\"browser\":\"chrome\"}",
"online": true,
"ticket": "ticket-ts",
"lastSeenAt": null,
"expiresAt": null,
"createdAt": null,
"updatedAt": null
}"#;
let device: Device = serde_json::from_str(ts_json).unwrap();
assert_eq!(device.device_id, "dev-ts-01");
assert_eq!(device.device_name, "Web Browser");
assert_eq!(device.platform_type, Some("web".into()));
assert!(device.online);
assert_eq!(device.capabilities.as_ref().unwrap().can_host, false);
assert_eq!(device.capabilities.as_ref().unwrap().can_sync, true);
assert_eq!(device.capabilities.as_ref().unwrap().read_only, true);
}
#[tokio::test]
async fn test_ts_session_json_deserializes_into_rust() {
let ts_json = r#"{
"connectionId": "conn-ts-1",
"initiator": "node-ts-a",
"target": "node-ts-b",
"initiatorDeviceId": "dev-ts-a",
"targetDeviceId": "dev-ts-b",
"connectionType": "iroh",
"offer": null,
"offerE2ee": null,
"answer": null,
"answerE2ee": null,
"iceCandidates": [],
"initiatorNodeId": "node-ts-a",
"targetNodeId": "node-ts-b",
"initiatorEndpointAddr": "{\"nodeId\":\"node-ts-a\"}",
"targetEndpointAddr": null,
"intent": "file-transfer",
"appTag": "plutonium-app",
"createdAt": 1700000000000,
"expiresAt": 1700060000000,
"state": "pending"
}"#;
let session: SignalingSession = serde_json::from_str(ts_json).unwrap();
assert_eq!(session.connection_id, "conn-ts-1");
assert_eq!(session.initiator_device_id, "dev-ts-a");
assert_eq!(session.connection_type, Some("iroh".into()));
assert_eq!(session.state, "pending");
assert_eq!(session.intent, Some("file-transfer".into()));
}