use chrono::NaiveDate;
use super::*;
fn date(year: i32, month: u32, day: u32) -> NaiveDate {
NaiveDate::from_ymd_opt(year, month, day).expect("test date is valid")
}
#[test]
fn faa_nasr_cycle_spans_28_days() {
let cycle = NavDataCycle::faa_nasr(date(2026, 5, 14)).expect("cycle builds");
assert_eq!(cycle.authority, NavDataAuthority::FaaNasr);
assert_eq!(cycle.effective_on, date(2026, 5, 14));
assert_eq!(cycle.next_effective_on, date(2026, 6, 11));
assert!(cycle.contains(date(2026, 5, 14)));
assert!(cycle.contains(date(2026, 6, 10)));
assert!(!cycle.contains(date(2026, 6, 11)));
}
#[test]
fn snapshot_resolves_identifiers_case_insensitively() {
let snapshot = NavDataSnapshot::new(
NavDataCycle::faa_nasr(date(2026, 5, 14)).expect("cycle builds"),
vec![
NavPoint::new(
"KSFO",
NavPointKind::Airport,
GeoPoint {
lat: 37.6188056,
lon: -122.3754167,
},
)
.with_name(Some("San Francisco International".to_owned())),
NavPoint::new(
"DUMBA",
NavPointKind::Waypoint,
GeoPoint {
lat: 37.4201667,
lon: -122.102,
},
),
],
);
let airport = snapshot.resolve(" ksfo ").expect("airport resolves");
assert_eq!(airport.kind, NavPointKind::Airport);
assert_eq!(airport.name.as_deref(), Some("San Francisco International"));
assert!(snapshot.resolve("missing").is_none());
assert!(snapshot.contains(date(2026, 6, 1)));
}
#[test]
fn snapshot_round_trips_through_serde() {
let snapshot = NavDataSnapshot::new(
NavDataCycle::faa_nasr(date(2026, 5, 14)).expect("cycle builds"),
vec![NavPoint::new(
"OSI",
NavPointKind::Navaid,
GeoPoint {
lat: 37.3925,
lon: -122.281,
},
)],
);
let json = serde_json::to_string(&snapshot).expect("snapshot serializes");
let decoded: NavDataSnapshot = serde_json::from_str(&json).expect("snapshot deserializes");
assert_eq!(decoded, snapshot);
}
#[test]
fn region_preference_beats_first_match() {
let cycle = NavDataCycle::faa_nasr(NaiveDate::from_ymd_opt(2026, 6, 11).unwrap()).unwrap();
let snapshot = NavDataSnapshot::new(
cycle,
vec![
NavPoint::new(
"DUPFX",
NavPointKind::Waypoint,
GeoPoint { lat: 1.0, lon: 1.0 },
)
.with_region(Some("K2".to_owned())),
NavPoint::new(
"DUPFX",
NavPointKind::Waypoint,
GeoPoint { lat: 9.0, lon: 9.0 },
)
.with_region(Some("PA".to_owned())),
],
);
let pa = snapshot
.resolve_preferring_region("DUPFX", Some("PA"))
.unwrap();
assert!((pa.position.lat - 9.0).abs() < 1e-9);
let fallback = snapshot
.resolve_preferring_region("DUPFX", Some("ZZ"))
.unwrap();
assert!((fallback.position.lat - 1.0).abs() < 1e-9);
}
#[test]
fn airways_attach_and_select_by_designator() {
let cycle = NavDataCycle::faa_nasr(NaiveDate::from_ymd_opt(2026, 6, 11).unwrap()).unwrap();
let snapshot = NavDataSnapshot::new(cycle, Vec::new()).with_airways(vec![
Airway::new(
"V25",
AirwayLocation::Conus,
vec![AirwayPoint::new("AAA"), AirwayPoint::new("BBB")],
),
Airway::new("V25", AirwayLocation::Alaska, vec![AirwayPoint::new("CCC")]),
Airway::new("J94", AirwayLocation::Conus, vec![AirwayPoint::new("DDD")]),
]);
let matches: Vec<_> = snapshot.airways_named("v25").collect();
assert_eq!(matches.len(), 2, "same ident across locations");
assert_eq!(snapshot.airways_named("Q1").count(), 0);
assert_eq!(AirwayLocation::Alaska.code(), "A");
}
#[test]
fn airspaces_select_by_center_airport_case_insensitively() {
let cycle = NavDataCycle::faa_nasr(NaiveDate::from_ymd_opt(2026, 6, 11).unwrap()).unwrap();
let snapshot = NavDataSnapshot::new(cycle, Vec::new()).with_airspaces(vec![
Airspace::new(AirspaceKind::Controlled(ControlledClass::B), "KSEA")
.with_center_ident(Some("KSEA".to_owned())),
Airspace::new(AirspaceKind::Controlled(ControlledClass::D), "KSEA")
.with_center_ident(Some("KSEA".to_owned())),
Airspace::new(AirspaceKind::Restrictive(RestrictiveKind::Moa), "BOARDMAN"),
]);
assert_eq!(snapshot.airspaces_at(" ksea ").count(), 2);
assert_eq!(
snapshot.airspaces_at("BOARDMAN").count(),
0,
"SUA has no center"
);
assert_eq!(snapshot.airspaces_at("KJFK").count(), 0);
}