#![allow(clippy::expect_used, clippy::panic)]
use aerocontext_core::{AircraftProfile, GeoPoint};
use super::*;
use crate::flightplan::{FlightPlan, PlanWaypoint, WaypointType};
fn profile() -> AircraftProfile {
serde_json::from_str(
r#"{
"cruise_tas_kt": 450.0,
"climb_rate_fpm": 2500.0,
"climb_tas_kt": 280.0,
"descent_rate_fpm": 1800.0,
"descent_tas_kt": 300.0,
"cruise_burn_per_hour": 200.0,
"taxi_fuel": 30.0,
"fuel_capacity": 2000.0,
"reserve_minutes": 45.0
}"#,
)
.expect("profile parses")
}
fn wp(ident: &str, lat: f64, lon: f64) -> PlanWaypoint {
PlanWaypoint {
identifier: ident.to_owned(),
kind: WaypointType::Int,
position: GeoPoint { lat, lon },
country_code: None,
}
}
fn plan(cruise_ft: Option<i32>) -> FlightPlan {
FlightPlan {
cruise_altitude_ft: cruise_ft,
route: vec![
wp("KEWR", 40.6925, -74.1687),
wp("PSB", 40.9163, -77.9927),
wp("KAAO", 37.7461, -97.2211),
],
..FlightPlan::default()
}
}
#[test]
fn profile_climbs_cruises_and_descends() {
let xs = CrossSection::build(&plan(Some(40000)), &profile()).expect("builds");
assert_eq!(xs.samples.len(), 3);
assert_eq!(xs.samples[0].phase, FlightPhase::Climb);
assert!(xs.samples[0].altitude_ft < 1000.0, "departure starts low");
assert_eq!(xs.samples[0].eta_from_etd.as_secs(), 0);
let last = xs.samples.last().expect("dest");
assert_eq!(last.phase, FlightPhase::Descent);
assert!(last.altitude_ft < 1000.0, "arrives low");
assert!(
(2.0..4.0).contains(&(xs.time_en_route.as_secs_f64() / 3600.0)),
"ete hours = {}",
xs.time_en_route.as_secs_f64() / 3600.0
);
assert!(xs.fuel_within_capacity);
assert!(xs.fuel_required > profile().taxi_fuel);
}
#[test]
fn eta_increases_monotonically_along_the_route() {
let xs = CrossSection::build(&plan(Some(40000)), &profile()).expect("builds");
for pair in xs.samples.windows(2) {
assert!(
pair[1].eta_from_etd >= pair[0].eta_from_etd,
"ETA must not go backwards"
);
assert!(pair[1].along_track_nm > pair[0].along_track_nm);
}
}
#[test]
fn missing_cruise_altitude_is_an_error() {
assert!(matches!(
CrossSection::build(&plan(None), &profile()),
Err(CrossSectionError::NoCruiseAltitude)
));
}
#[test]
fn a_single_point_plan_cannot_be_profiled() {
let mut p = plan(Some(40000));
p.route.truncate(1);
assert!(matches!(
CrossSection::build(&p, &profile()),
Err(CrossSectionError::TooFewPoints)
));
}