use std::fs;
use std::net::UdpSocket;
use std::time::Duration;
use ssq::Client;
const SINGLE_PACKET_HEADER: [u8; 4] = [0xFF, 0xFF, 0xFF, 0xFF];
fn fixture(path: &str) -> Vec<u8> {
let full = format!("tests/fixtures/{path}");
fs::read(&full).unwrap_or_else(|e| panic!("failed to read fixture {full}: {e}"))
}
fn snap_name(prefix: &str, fixture_path: &str) -> String {
let base = fixture_path
.strip_suffix(".bin")
.unwrap_or(fixture_path)
.replace('/', "__");
format!("{prefix}__{base}")
}
fn single_packet(payload: &[u8]) -> Vec<u8> {
let mut buf = Vec::with_capacity(4 + payload.len());
buf.extend_from_slice(&SINGLE_PACKET_HEADER);
buf.extend_from_slice(payload);
buf
}
fn challenge_response(challenge: i32) -> Vec<u8> {
let mut buf = Vec::with_capacity(9);
buf.extend_from_slice(&SINGLE_PACKET_HEADER);
buf.push(b'A');
buf.extend_from_slice(&challenge.to_le_bytes());
buf
}
fn mock_server(responses: Vec<Vec<u8>>) -> std::net::SocketAddr {
let socket = UdpSocket::bind("127.0.0.1:0").unwrap();
let addr = socket.local_addr().unwrap();
socket
.set_read_timeout(Some(Duration::from_secs(5)))
.unwrap();
std::thread::spawn(move || {
let mut buf = [0u8; 2048];
for response in responses {
let (_, src) = socket.recv_from(&mut buf).unwrap();
socket.send_to(&response, src).unwrap();
}
});
addr
}
#[test]
fn sync_info() {
let path = "320/74_91_118_209_27015_info.bin";
let payload = fixture(path);
let addr = mock_server(vec![single_packet(&payload)]);
let client = Client::new(Duration::from_secs(5)).unwrap();
let info = client.info(addr).unwrap();
insta::assert_debug_snapshot!(snap_name("sync", path), info);
}
#[test]
fn sync_info_with_challenge() {
let path = "0/51_38_89_140_2302_info.bin";
let payload = fixture(path);
let addr = mock_server(vec![
challenge_response(0x12345678),
single_packet(&payload),
]);
let client = Client::new(Duration::from_secs(5)).unwrap();
let info = client.info(addr).unwrap();
insta::assert_debug_snapshot!(snap_name("sync", path), info);
}
#[test]
fn sync_players() {
let path = "320/74_91_118_209_27015_players.bin";
let payload = fixture(path);
let addr = mock_server(vec![
challenge_response(0xAABBCCDD_u32 as i32),
single_packet(&payload),
]);
let client = Client::new(Duration::from_secs(5)).unwrap();
let players = client.players(addr).unwrap();
insta::assert_debug_snapshot!(snap_name("sync", path), players);
}
#[test]
fn sync_rules() {
let path = "320/74_91_118_209_27015_rules.bin";
let payload = fixture(path);
let addr = mock_server(vec![
challenge_response(0x11223344),
single_packet(&payload),
]);
let client = Client::new(Duration::from_secs(5)).unwrap();
let rules = client.rules(addr).unwrap();
insta::assert_debug_snapshot!(snap_name("sync", path), rules);
}
#[test]
fn sync_players_no_challenge() {
let path = "0/172_111_51_218_2402_players.bin";
let payload = fixture(path);
let addr = mock_server(vec![single_packet(&payload)]);
let client = Client::new(Duration::from_secs(5)).unwrap();
let players = client.players(addr).unwrap();
insta::assert_debug_snapshot!(snap_name("sync", path), players);
}
#[test]
fn sync_rules_no_challenge() {
let path = "0/172_111_51_218_2402_rules.bin";
let payload = fixture(path);
let addr = mock_server(vec![single_packet(&payload)]);
let client = Client::new(Duration::from_secs(5)).unwrap();
let rules = client.rules(addr).unwrap();
insta::assert_debug_snapshot!(snap_name("sync", path), rules);
}
#[cfg(feature = "async")]
mod async_tests {
use super::*;
use tokio::net::UdpSocket as TokioUdpSocket;
async fn mock_server_async(responses: Vec<Vec<u8>>) -> std::net::SocketAddr {
let socket = TokioUdpSocket::bind("127.0.0.1:0").await.unwrap();
let addr = socket.local_addr().unwrap();
tokio::spawn(async move {
let mut buf = [0u8; 2048];
for response in responses {
let (_, src) = socket.recv_from(&mut buf).await.unwrap();
socket.send_to(&response, src).await.unwrap();
}
});
addr
}
#[tokio::test]
async fn async_info() {
let path = "320/74_91_118_209_27015_info.bin";
let payload = fixture(path);
let addr = mock_server_async(vec![single_packet(&payload)]).await;
let client = ssq::nonblocking::Client::new().await.unwrap();
let info = client.info(addr).await.unwrap();
insta::assert_debug_snapshot!(snap_name("async", path), info);
}
#[tokio::test]
async fn async_info_with_challenge() {
let path = "0/51_38_89_140_2302_info.bin";
let payload = fixture(path);
let addr = mock_server_async(vec![
challenge_response(0x12345678),
single_packet(&payload),
])
.await;
let client = ssq::nonblocking::Client::new().await.unwrap();
let info = client.info(addr).await.unwrap();
insta::assert_debug_snapshot!(snap_name("async", path), info);
}
#[tokio::test]
async fn async_players() {
let path = "320/74_91_118_209_27015_players.bin";
let payload = fixture(path);
let addr = mock_server_async(vec![
challenge_response(0xAABBCCDD_u32 as i32),
single_packet(&payload),
])
.await;
let client = ssq::nonblocking::Client::new().await.unwrap();
let players = client.players(addr).await.unwrap();
insta::assert_debug_snapshot!(snap_name("async", path), players);
}
#[tokio::test]
async fn async_rules() {
let path = "320/74_91_118_209_27015_rules.bin";
let payload = fixture(path);
let addr = mock_server_async(vec![
challenge_response(0x11223344),
single_packet(&payload),
])
.await;
let client = ssq::nonblocking::Client::new().await.unwrap();
let rules = client.rules(addr).await.unwrap();
insta::assert_debug_snapshot!(snap_name("async", path), rules);
}
#[tokio::test]
async fn async_players_no_challenge() {
let path = "0/172_111_51_218_2402_players.bin";
let payload = fixture(path);
let addr = mock_server_async(vec![single_packet(&payload)]).await;
let client = ssq::nonblocking::Client::new().await.unwrap();
let players = client.players(addr).await.unwrap();
insta::assert_debug_snapshot!(snap_name("async", path), players);
}
#[tokio::test]
async fn async_rules_no_challenge() {
let path = "0/172_111_51_218_2402_rules.bin";
let payload = fixture(path);
let addr = mock_server_async(vec![single_packet(&payload)]).await;
let client = ssq::nonblocking::Client::new().await.unwrap();
let rules = client.rules(addr).await.unwrap();
insta::assert_debug_snapshot!(snap_name("async", path), rules);
}
}