use crate::error::Result;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AircraftSnapshot {
pub now: Option<f64>,
pub messages: Option<u64>,
pub aircraft: Vec<AircraftEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AircraftEntry {
pub hex: Option<String>,
pub flight: Option<String>,
pub lat: Option<f64>,
pub lon: Option<f64>,
pub alt_baro: Option<i32>,
pub alt_geom: Option<i32>,
pub gs: Option<f64>,
pub rssi: Option<f64>,
pub messages: Option<i64>,
pub seen: Option<f64>,
pub seen_pos: Option<f64>,
pub track: Option<f64>,
pub squawk: Option<String>,
pub category: Option<String>,
}
#[allow(dead_code)] pub async fn fetch_snapshot(url: &str) -> Result<AircraftSnapshot> {
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(10))
.build()?;
let response = client.get(url).send().await?;
if !response.status().is_success() {
return Err(crate::error::Error::Generic(format!(
"HTTP error: {} - {}",
response.status().as_u16(),
response.status().canonical_reason().unwrap_or("Unknown")
)));
}
let snapshot: AircraftSnapshot = response.json().await?;
Ok(snapshot)
}
#[cfg(test)]
mod tests {
use super::*;
use axum::{extract::Path, response::Json, routing::get, Router};
use tokio::net::TcpListener;
#[tokio::test]
async fn test_deserialize_aircraft_snapshot_minimal() {
let json = r#"
{
"aircraft": []
}
"#;
let snapshot: AircraftSnapshot = serde_json::from_str(json).unwrap();
assert_eq!(snapshot.now, None);
assert_eq!(snapshot.messages, None);
assert_eq!(snapshot.aircraft.len(), 0);
}
#[tokio::test]
async fn test_deserialize_aircraft_snapshot_complete() {
let json = include_str!("../../tests/data/aircraft1.json");
let snapshot: AircraftSnapshot = serde_json::from_str(json).unwrap();
assert_eq!(snapshot.now, Some(1641024000.5));
assert_eq!(snapshot.messages, Some(12345));
assert_eq!(snapshot.aircraft.len(), 2);
let aircraft1 = &snapshot.aircraft[0];
assert_eq!(aircraft1.hex, Some("abc123".to_string()));
assert_eq!(aircraft1.flight, Some("UAL456".to_string()));
assert_eq!(aircraft1.lat, Some(40.7128));
assert_eq!(aircraft1.lon, Some(-74.0060));
assert_eq!(aircraft1.alt_baro, Some(35000));
assert_eq!(aircraft1.gs, Some(450.2));
assert_eq!(aircraft1.rssi, Some(-45.5));
assert_eq!(aircraft1.messages, Some(1000));
assert_eq!(aircraft1.seen, Some(0.1));
assert_eq!(aircraft1.track, Some(90.0));
assert_eq!(aircraft1.squawk, Some("1200".to_string()));
let aircraft2 = &snapshot.aircraft[1];
assert_eq!(aircraft2.hex, Some("def456".to_string()));
assert_eq!(aircraft2.flight, None);
assert_eq!(aircraft2.lat, None);
assert_eq!(aircraft2.lon, None);
assert_eq!(aircraft2.alt_geom, Some(28000));
assert_eq!(aircraft2.gs, Some(380.0));
assert_eq!(aircraft2.rssi, Some(-52.1));
}
#[tokio::test]
async fn test_aircraft_entry_missing_fields() {
let json = r#"
{
"hex": "abc123"
}
"#;
let entry: AircraftEntry = serde_json::from_str(json).unwrap();
assert_eq!(entry.hex, Some("abc123".to_string()));
assert_eq!(entry.flight, None);
assert_eq!(entry.lat, None);
assert_eq!(entry.lon, None);
assert_eq!(entry.alt_baro, None);
assert_eq!(entry.gs, None);
assert_eq!(entry.rssi, None);
assert_eq!(entry.messages, None);
}
async fn test_server_handler(Path(file): Path<String>) -> Json<serde_json::Value> {
let content = match file.as_str() {
"aircraft1.json" => include_str!("../../tests/data/aircraft1.json"),
"aircraft2.json" => include_str!("../../tests/data/aircraft2.json"),
_ => r#"{"aircraft": []}"#,
};
let value: serde_json::Value = serde_json::from_str(content).unwrap();
Json(value)
}
#[tokio::test]
async fn test_fetch_snapshot_integration() {
let app = Router::new().route("/data/:file", get(test_server_handler));
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(async move {
axum::serve(listener, app).await.unwrap();
});
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
let url = format!("http://127.0.0.1:{}/data/aircraft1.json", addr.port());
let snapshot = fetch_snapshot(&url).await.unwrap();
assert_eq!(snapshot.aircraft.len(), 2);
assert_eq!(snapshot.messages, Some(12345));
assert_eq!(snapshot.aircraft[0].hex, Some("abc123".to_string()));
}
#[tokio::test]
async fn test_fetch_snapshot_http_error() {
let result = fetch_snapshot("http://127.0.0.1:9999/nonexistent").await;
assert!(result.is_err());
}
}