use super::*;
#[test]
fn metar_decodes_field_names_from_spec() {
let body = r#"[{
"icaoId": "KORD",
"rawOb": "KORD 041951Z 27012KT 10SM FEW250 28/14 A3002",
"obsTime": 1717530660,
"temp": 28.0,
"altim": 1016.6
}]"#;
let products = metars(body).unwrap();
assert_eq!(products.len(), 1);
assert_eq!(products[0].kind, ProductKind::Metar);
assert_eq!(products[0].location.as_deref(), Some("KORD"));
assert!(products[0].raw_text.starts_with("KORD 041951Z"));
assert_eq!(
products[0].issued_at.map(|t| t.timestamp()),
Some(1717530660)
);
}
#[test]
fn metar_without_raw_text_is_dropped_not_fatal() {
let body = r#"[{"icaoId": "KORD"}, {"icaoId": "KMDW", "rawOb": "KMDW ..."}]"#;
let products = metars(body).unwrap();
assert_eq!(products.len(), 1);
assert_eq!(products[0].location.as_deref(), Some("KMDW"));
}
#[test]
fn taf_uses_raw_taf_not_raw_ob() {
let body = r#"[{
"icaoId": "KORD",
"rawTAF": "KORD 041730Z 0418/0524 27010KT P6SM SCT250",
"issueTime": "2026-06-04 17:30:00"
}]"#;
let products = tafs(body).unwrap();
assert_eq!(products.len(), 1);
assert_eq!(products[0].kind, ProductKind::Taf);
assert!(products[0].raw_text.starts_with("KORD 041730Z"));
assert!(products[0].issued_at.is_some());
}
#[test]
fn pirep_decodes_raw_ob() {
let body = r#"[{
"rawOb": "ORD UA /OV ORD/TM 1955/FL080/TP B738/TB LGT",
"obsTime": 1717531000
}]"#;
let products = pireps(body).unwrap();
assert_eq!(products.len(), 1);
assert_eq!(products[0].kind, ProductKind::Pirep);
assert!(products[0].location.is_none());
}
#[test]
fn pirep_fractional_obs_time_does_not_fail_the_batch() {
let body = r#"[
{"rawOb": "FLOAT .0", "obsTime": 1699379880.0},
{"rawOb": "FLOAT .5", "obsTime": 1699379880.5},
{"rawOb": "INTEGER", "obsTime": 1699379880}
]"#;
let products = pireps(body).unwrap();
assert_eq!(products.len(), 3);
for product in &products {
assert_eq!(
product.issued_at.map(|t| t.timestamp()),
Some(1699379880),
"{} keeps its (truncated) timestamp",
product.raw_text
);
}
}
#[test]
fn airsigmet_carries_polygon_for_clipping() {
let body = r#"[{
"icaoId": "KKCI",
"rawAirSigmet": "WSUS31 KKCI 042000 ...",
"creationTime": "2026-06-04T19:48:24.974Z",
"coords": [{"lat": 40.0, "lon": -90.0}, {"lat": 42.0, "lon": -88.0}]
}]"#;
let entries = air_sigmets(body).unwrap();
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].0.kind, ProductKind::Sigmet);
assert_eq!(entries[0].1.len(), 2);
assert!(entries[0].0.issued_at.is_some());
}
#[test]
fn clip_keeps_intersecting_and_geometryless_drops_outside() {
let inside = (
Product::new(ProductKind::Sigmet, "INSIDE"),
vec![
GeoPoint {
lat: 40.0,
lon: -90.0,
},
GeoPoint {
lat: 42.0,
lon: -88.0,
},
],
);
let outside = (
Product::new(ProductKind::Sigmet, "OUTSIDE"),
vec![
GeoPoint {
lat: 10.0,
lon: 50.0,
},
GeoPoint {
lat: 12.0,
lon: 52.0,
},
],
);
let no_geometry = (Product::new(ProductKind::Sigmet, "NO-GEOMETRY"), vec![]);
let lat_overlap_only = (
Product::new(ProductKind::Sigmet, "LAT-ONLY"),
vec![
GeoPoint {
lat: 40.0,
lon: 50.0,
},
GeoPoint {
lat: 41.0,
lon: 52.0,
},
],
);
let lon_overlap_only = (
Product::new(ProductKind::Sigmet, "LON-ONLY"),
vec![
GeoPoint {
lat: 10.0,
lon: -90.0,
},
GeoPoint {
lat: 12.0,
lon: -88.0,
},
],
);
let kept = clip_to_bbox(
vec![
inside,
outside,
no_geometry,
lat_overlap_only,
lon_overlap_only,
],
(
GeoPoint {
lat: 39.0,
lon: -91.0,
},
GeoPoint {
lat: 41.0,
lon: -87.0,
},
),
);
let texts: Vec<&str> = kept.iter().map(|p| p.raw_text.as_str()).collect();
assert_eq!(texts, ["INSIDE", "NO-GEOMETRY"]);
}
#[test]
fn awc_time_formats_parse_best_effort() {
assert!(parse_awc_time("2026-06-04T19:48:24.974Z").is_some());
assert!(parse_awc_time("2026-06-04 19:48:24.974Z").is_some());
assert!(parse_awc_time("2026-06-04 19:48:24").is_some());
assert!(parse_awc_time("not a time").is_none());
}
#[test]
fn malformed_body_is_an_error_not_a_panic() {
assert!(metars("{\"not\": \"an array\"}").is_err());
assert!(metars("").is_err());
}
#[test]
fn metar_decodes_structured_observation_and_keeps_raw() {
let body = r#"[{
"icaoId": "KSFO",
"rawOb": "KSFO 130956Z VRB03KT 2SM BR BKN008 OVC015 14/13 A2998",
"obsTime": 1717530660,
"temp": 14.0,
"dewp": 13.0,
"wdir": "VRB",
"wspd": 3,
"visib": "2",
"altim": 1015.2,
"clouds": [
{"cover": "BKN", "base": 800},
{"cover": "OVC", "base": 1500}
],
"fltCat": "IFR"
}]"#;
let products = metars(body).unwrap();
let obs = products[0]
.observation
.as_ref()
.expect("METAR carries a decoded observation");
assert!(products[0].raw_text.starts_with("KSFO 130956Z"));
assert_eq!(obs.ceiling_ft(), Some(800.0), "lowest BKN/OVC base");
assert_eq!(obs.visibility_sm, Some(2.0));
assert_eq!(obs.temperature_c, Some(14.0));
assert_eq!(obs.dewpoint_c, Some(13.0));
assert!(obs.wind_variable && obs.wind_dir_deg.is_none());
assert_eq!(obs.wind_speed_kt, Some(3));
assert_eq!(
obs.flight_category(),
Some(aerocontext_core::FlightCategory::Ifr)
);
assert_eq!(obs.flight_category(), obs.reported_category);
}
#[test]
fn metar_with_plus_visibility_and_no_ceiling_is_vfr() {
let body = r#"[{
"icaoId": "KDEN",
"rawOb": "KDEN 130953Z 28008KT 10SM FEW250 25/05 A3001",
"wdir": 280,
"wspd": 8,
"visib": "10+",
"clouds": [{"cover": "FEW", "base": 25000}],
"fltCat": "VFR"
}]"#;
let obs = metars(body).unwrap()[0]
.observation
.clone()
.expect("observation");
assert_eq!(obs.visibility_sm, Some(10.0), "10+ decodes to 10");
assert_eq!(obs.ceiling_ft(), None, "FEW is not a ceiling");
assert_eq!(obs.wind_dir_deg, Some(280));
assert_eq!(
obs.flight_category(),
Some(aerocontext_core::FlightCategory::Vfr)
);
}
#[test]
fn obscured_sky_uses_vert_vis_as_the_ceiling() {
let body = r#"[{
"icaoId": "KSEA",
"rawOb": "KSEA 131053Z 00000KT 1/4SM FG VV002 09/09 A2990",
"visib": "1/4",
"clouds": [{"cover": "OVX", "base": null}],
"vertVis": 200,
"fltCat": "LIFR"
}]"#;
let obs = metars(body).unwrap()[0]
.observation
.clone()
.expect("observation");
assert_eq!(
obs.ceiling_ft(),
Some(200.0),
"vertVis is the obscured ceiling"
);
assert!(!obs.has_unknown_height_ceiling());
assert_eq!(
obs.flight_category(),
Some(aerocontext_core::FlightCategory::Lifr)
);
assert_eq!(obs.flight_category(), obs.reported_category);
}