#![allow(clippy::expect_used, clippy::panic)]
use super::*;
fn sfo_28r() -> RunwayEnd {
RunwayEnd::new("28R")
.with_true_alignment_deg(Some(298.0))
.with_elevation_ft(Some(13.0))
}
#[test]
fn a_direct_headwind_has_no_crosswind() {
let (headwind, crosswind) = sfo_28r().wind_components_kt(298.0, 20.0).expect("aligned");
assert!((headwind - 20.0).abs() < 1e-6);
assert!(crosswind.abs() < 1e-6);
}
#[test]
fn a_direct_crosswind_has_no_headwind() {
let (headwind, crosswind) = sfo_28r().wind_components_kt(208.0, 20.0).expect("aligned");
assert!(headwind.abs() < 1e-6);
assert!((crosswind - 20.0).abs() < 1e-6);
}
#[test]
fn a_tailwind_is_a_negative_headwind() {
let (headwind, crosswind) = sfo_28r().wind_components_kt(118.0, 15.0).expect("aligned");
assert!((headwind + 15.0).abs() < 1e-6, "tailwind = -15");
assert!(crosswind.abs() < 1e-6);
}
#[test]
fn forty_five_degree_wind_splits_evenly() {
let (headwind, crosswind) = sfo_28r()
.wind_components_kt(298.0 - 45.0, 14.142)
.expect("aligned");
assert!((headwind - 10.0).abs() < 0.01);
assert!((crosswind - 10.0).abs() < 0.01);
}
#[test]
fn reciprocal_ends_have_equal_crosswind_opposite_headwind() {
let rwy = Runway::new("SFO", "10R/28L").with_ends(vec![
RunwayEnd::new("10R").with_true_alignment_deg(Some(118.0)),
RunwayEnd::new("28L").with_true_alignment_deg(Some(298.0)),
]);
let (_, xw_10r) = rwy.ends[0].wind_components_kt(130.0, 20.0).expect("10r");
let (hw_10r, _) = rwy.ends[0].wind_components_kt(130.0, 20.0).expect("10r");
let (_, xw_28l) = rwy.ends[1].wind_components_kt(130.0, 20.0).expect("28l");
let (hw_28l, _) = rwy.ends[1].wind_components_kt(130.0, 20.0).expect("28l");
assert!(
(xw_10r - xw_28l).abs() < 1e-6,
"reciprocal crosswinds equal"
);
assert!(hw_10r > 0.0 && hw_28l < 0.0, "one into-wind, one tailwind");
assert!((rwy.crosswind_kt(130.0, 20.0).expect("xw") - xw_10r).abs() < 1e-6);
}
#[test]
fn best_runway_is_a_min_across_different_runways() {
let near = Runway::new("X", "12/30").with_ends(vec![
RunwayEnd::new("12").with_true_alignment_deg(Some(120.0)),
]);
let cross = Runway::new("X", "04/22").with_ends(vec![
RunwayEnd::new("04").with_true_alignment_deg(Some(40.0)),
]);
let best = [&near, &cross]
.iter()
.filter_map(|r| r.crosswind_kt(130.0, 20.0))
.min_by(|a, b| a.partial_cmp(b).unwrap())
.expect("some runway");
assert!((best - 20.0 * (130.0_f64 - 120.0).to_radians().sin().abs()).abs() < 1e-6);
}
#[test]
fn no_alignment_yields_no_components() {
assert!(
RunwayEnd::new("01L")
.wind_components_kt(280.0, 10.0)
.is_none()
);
assert!(
Runway::new("SFO", "01L/19R")
.with_ends(vec![RunwayEnd::new("01L")])
.crosswind_kt(280.0, 10.0)
.is_none()
);
}
#[test]
fn non_finite_wind_is_rejected() {
assert!(sfo_28r().wind_components_kt(f64::NAN, 10.0).is_none());
assert!(sfo_28r().wind_components_kt(298.0, f64::INFINITY).is_none());
}