use faf_radio_rust::{ClientAction, ConnectionState, RadioClient, RadioConfig, RadioError, ServerMessage};
use serde_json;
#[tokio::test]
async fn test_initial_state_disconnected() {
let client = RadioClient::with_url("wss://example.com");
assert_eq!(client.state().await, ConnectionState::Disconnected);
}
#[test]
fn test_with_url_constructor() {
let client = RadioClient::with_url("wss://example.com/radio");
drop(client);
}
#[test]
fn test_config_default_values() {
let config = RadioConfig::default();
assert_eq!(config.heartbeat_interval_ms, 30000);
assert_eq!(config.max_reconnect_attempts, 5);
assert_eq!(config.reconnect_delay_ms, 1000);
assert!(config.auto_reconnect);
assert_eq!(config.max_reconnect_delay_ms, 16000);
assert!(config.url.is_empty());
}
#[test]
fn test_config_custom_values() {
let mut config = RadioConfig::new("wss://custom.server");
config.heartbeat_interval_ms = 60000;
config.max_reconnect_attempts = 10;
config.reconnect_delay_ms = 2000;
config.auto_reconnect = false;
config.max_reconnect_delay_ms = 32000;
assert_eq!(config.url, "wss://custom.server");
assert_eq!(config.heartbeat_interval_ms, 60000);
assert_eq!(config.max_reconnect_attempts, 10);
assert_eq!(config.reconnect_delay_ms, 2000);
assert!(!config.auto_reconnect);
assert_eq!(config.max_reconnect_delay_ms, 32000);
}
#[tokio::test]
async fn test_disconnect_when_disconnected() {
let client = RadioClient::with_url("wss://example.com");
let result = client.disconnect().await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_disconnect_sets_state() {
let client = RadioClient::with_url("wss://example.com");
client.disconnect().await.unwrap();
assert_eq!(client.state().await, ConnectionState::Disconnected);
}
#[tokio::test]
async fn test_connect_already_connected() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let ws_url = format!("ws://127.0.0.1:{}", addr.port());
tokio::spawn(async move {
while let Ok((stream, _)) = listener.accept().await {
tokio::spawn(async move {
let _ws = tokio_tungstenite::accept_async(stream).await;
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
});
}
});
let mut client = RadioClient::with_url(&ws_url);
let result1 = client.connect().await;
assert!(result1.is_ok(), "First connect should succeed");
let result2 = client.connect().await;
assert!(result2.is_err());
assert!(matches!(result2.unwrap_err(), RadioError::AlreadyConnected));
}
#[tokio::test]
async fn test_tune_when_disconnected() {
let client = RadioClient::with_url("wss://example.com");
let result = client.tune(vec!["91.0".to_string()]).await;
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), RadioError::NotConnected));
}
#[tokio::test]
async fn test_untune_when_disconnected() {
let client = RadioClient::with_url("wss://example.com");
let result = client.untune(vec!["91.0".to_string()]).await;
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), RadioError::NotConnected));
}
#[tokio::test]
async fn test_tune_invalid_freq_before_send() {
let client = RadioClient::with_url("wss://example.com");
let result = client.tune(vec!["999.0".to_string()]).await;
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RadioError::InvalidFrequency(_)
));
}
#[tokio::test]
async fn test_untune_invalid_freq_before_send() {
let client = RadioClient::with_url("wss://example.com");
let result = client.untune(vec!["0.0".to_string()]).await;
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RadioError::InvalidFrequency(_)
));
}
#[tokio::test]
async fn test_tune_empty_vec() {
let client = RadioClient::with_url("wss://example.com");
let result = client.tune(vec![]).await;
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), RadioError::NotConnected));
}
#[test]
fn test_tune_action_serialization() {
let action = ClientAction::Tune {
frequencies: vec!["91.0".to_string(), "92.5".to_string()],
};
let json = serde_json::to_string(&action).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["action"], "tune");
assert_eq!(parsed["frequencies"][0], "91.0");
assert_eq!(parsed["frequencies"][1], "92.5");
}
#[test]
fn test_untune_action_serialization() {
let action = ClientAction::Untune {
frequencies: vec!["94.7".to_string()],
};
let json = serde_json::to_string(&action).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["action"], "untune");
assert_eq!(parsed["frequencies"][0], "94.7");
}
#[test]
fn test_ping_action_serialization() {
let action = ClientAction::Ping;
let json = serde_json::to_string(&action).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["action"], "ping");
}
#[test]
fn test_broadcast_action_serialization() {
let action = ClientAction::Broadcast {
frequency: "91.0".to_string(),
event: serde_json::json!({"type": "fafb", "size": 220}),
};
let json = serde_json::to_string(&action).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["action"], "broadcast");
assert_eq!(parsed["frequency"], "91.0");
assert_eq!(parsed["event"]["type"], "fafb");
assert_eq!(parsed["event"]["size"], 220);
}
#[test]
fn test_server_messages_deserialization() {
let connected_json =
r#"{"type":"connected","clientId":"abc-123","message":"Welcome","frequencies":{}}"#;
let msg: ServerMessage = serde_json::from_str(connected_json).unwrap();
assert!(matches!(msg, ServerMessage::Connected { .. }));
let tuned_json = r#"{"type":"tuned","frequencies":["91.0"],"message":"Tuned in"}"#;
let msg: ServerMessage = serde_json::from_str(tuned_json).unwrap();
assert!(matches!(msg, ServerMessage::Tuned { .. }));
let broadcast_json = r#"{"type":"broadcast","frequency":"91.0","event":{"data":"hello"},"timestamp":"2026-03-04T00:00:00Z"}"#;
let msg: ServerMessage = serde_json::from_str(broadcast_json).unwrap();
assert!(matches!(msg, ServerMessage::Broadcast { .. }));
let pong_json = r#"{"type":"pong"}"#;
let msg: ServerMessage = serde_json::from_str(pong_json).unwrap();
assert!(matches!(msg, ServerMessage::Pong));
let error_json = r#"{"type":"error","message":"Something went wrong"}"#;
let msg: ServerMessage = serde_json::from_str(error_json).unwrap();
assert!(matches!(msg, ServerMessage::Error { .. }));
}
#[tokio::test]
async fn test_connect_to_local_server() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let ws_url = format!("ws://127.0.0.1:{}", addr.port());
tokio::spawn(async move {
if let Ok((stream, _)) = listener.accept().await {
let _ws = tokio_tungstenite::accept_async(stream).await;
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
}
});
let mut client = RadioClient::with_url(&ws_url);
assert_eq!(client.state().await, ConnectionState::Disconnected);
let result = client.connect().await;
assert!(result.is_ok(), "Should connect to local WS server");
assert_eq!(client.state().await, ConnectionState::Connected);
}
#[tokio::test]
async fn test_full_lifecycle() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let ws_url = format!("ws://127.0.0.1:{}", addr.port());
tokio::spawn(async move {
if let Ok((stream, _)) = listener.accept().await {
let _ws = tokio_tungstenite::accept_async(stream).await;
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
}
});
let mut client = RadioClient::with_url(&ws_url);
assert_eq!(client.state().await, ConnectionState::Disconnected);
client.connect().await.unwrap();
assert_eq!(client.state().await, ConnectionState::Connected);
client.disconnect().await.unwrap();
assert_eq!(client.state().await, ConnectionState::Disconnected);
}