aerocontext-core 0.1.0

Provider-neutral aeronautical-context model and the pluggable ContextProvider contract
Documentation
use super::*;

fn approx(a: f64, b: f64) -> bool {
    (a - b).abs() < 1e-9
}

#[test]
fn bbox_area_encloses_itself() {
    let area = Area::BoundingBox {
        south_west: GeoPoint {
            lat: 30.0,
            lon: -100.0,
        },
        north_east: GeoPoint {
            lat: 40.0,
            lon: -90.0,
        },
    };
    let (sw, ne) = area.enclosing_bbox().unwrap();
    assert!(approx(sw.lat, 30.0) && approx(sw.lon, -100.0));
    assert!(approx(ne.lat, 40.0) && approx(ne.lon, -90.0));
}

#[test]
fn point_radius_bbox_scales_longitude_by_latitude() {
    // 60 NM = 1 degree of latitude everywhere.
    let area = Area::PointRadius {
        center: GeoPoint {
            lat: 60.0,
            lon: 10.0,
        },
        radius_nm: 60.0,
    };
    let (sw, ne) = area.enclosing_bbox().unwrap();
    assert!(approx(sw.lat, 59.0) && approx(ne.lat, 61.0));
    // cos(60 deg) = 0.5, so the longitude half-width doubles.
    assert!(approx(sw.lon, 8.0) && approx(ne.lon, 12.0));
}

#[test]
fn point_radius_bbox_clamps_latitude_at_pole() {
    let area = Area::PointRadius {
        center: GeoPoint {
            lat: 89.5,
            lon: 0.0,
        },
        radius_nm: 120.0,
    };
    let (sw, ne) = area.enclosing_bbox().unwrap();
    assert!(approx(ne.lat, 90.0));
    assert!(sw.lat < 89.5);
    // Near-pole circles degrade to the full longitude band, not a panic.
    assert!(approx(ne.lon - sw.lon, 360.0));
}

#[test]
fn location_radius_needs_provider_resolution() {
    let area = Area::LocationRadius {
        ident: "KSFO".to_owned(),
        radius_nm: 50.0,
    };
    assert!(area.enclosing_bbox().is_none());
}

#[test]
fn request_round_trips_through_serde() {
    let request = AreaBriefingRequest {
        area: Area::PointRadius {
            center: GeoPoint {
                lat: 37.6,
                lon: -122.4,
            },
            radius_nm: 100.0,
        },
        products: vec![
            ProductKind::Metar,
            ProductKind::Other("synopsis".to_owned()),
        ],
        lookback_hours: Some(2),
        departure_at: "2026-06-05T14:30:00Z".parse().ok(),
    };
    let json = serde_json::to_string(&request).unwrap();
    let back: AreaBriefingRequest = serde_json::from_str(&json).unwrap();
    assert_eq!(back, request);
}