aerocontext-core 0.4.0

Provider-neutral aeronautical-context model and the pluggable ContextProvider contract
Documentation
use super::*;
use crate::model::{Briefing, Product, ProductKind};

fn briefing(source: &str, products: Vec<Product>) -> Briefing {
    Briefing::new(source).with_products(products)
}

fn product(kind: ProductKind, location: &str, raw_text: &str) -> Product {
    Product::new(kind, raw_text).with_location(Some(location.to_owned()))
}

fn find<'a>(
    report: &'a ComparisonReport,
    location: &str,
    kind: &ProductKind,
) -> &'a ProductComparison {
    report
        .comparisons
        .iter()
        .find(|c| c.key.location.as_deref() == Some(location) && &c.key.kind == kind)
        .unwrap_or_else(|| panic!("no comparison for {location} {kind:?}"))
}

#[test]
fn matching_text_across_sources_is_agreement_even_with_keyword_and_equals() {
    // AWC stores the bare report; Leidos labels it `METAR ...=`. Same
    // weather, so they must reconcile as AGREE.
    let awc = briefing(
        "awc",
        vec![product(
            ProductKind::Metar,
            "KIAD",
            "KIAD 041952Z 28008KT 10SM FEW250 29/13 A3003",
        )],
    );
    let leidos = briefing(
        "leidos",
        vec![product(
            ProductKind::Metar,
            "KIAD",
            "METAR KIAD 041952Z 28008KT 10SM FEW250 29/13 A3003=",
        )],
    );
    let report = compare(&[awc, leidos]);
    let kiad = find(&report, "KIAD", &ProductKind::Metar);
    assert_eq!(kiad.agreement, Agreement::Agree);
    assert_eq!(kiad.values.len(), 2);
    // Display keeps each source's verbatim text, including the label.
    assert!(
        kiad.values
            .iter()
            .any(|v| v.source == "awc" && !v.raw_text.starts_with("METAR"))
    );
    assert!(
        kiad.values
            .iter()
            .any(|v| v.source == "leidos" && v.raw_text.ends_with('='))
    );
}

#[test]
fn differing_text_is_a_disagreement_attributed_to_each_source() {
    let awc = briefing(
        "awc",
        vec![product(
            ProductKind::Taf,
            "KIAD",
            "KIAD 041730Z 0418/0524 27010KT P6SM SCT250",
        )],
    );
    let leidos = briefing(
        "leidos",
        vec![product(
            ProductKind::Taf,
            "KIAD",
            "TAF KIAD 041730Z 0418/0524 30015KT P6SM BKN040=",
        )],
    );
    let report = compare(&[awc, leidos]);
    let taf = find(&report, "KIAD", &ProductKind::Taf);
    assert_eq!(taf.agreement, Agreement::Disagree);

    let awc_value = taf
        .values
        .iter()
        .find(|v| v.source == "awc")
        .expect("awc value");
    let leidos_value = taf
        .values
        .iter()
        .find(|v| v.source == "leidos")
        .expect("leidos value");
    assert!(
        awc_value.raw_text.contains("SCT250"),
        "awc text is preserved"
    );
    assert!(
        leidos_value.raw_text.contains("BKN040"),
        "leidos text is preserved"
    );

    // The rendered report tells the user which source said which.
    let rendered = report.to_string();
    assert!(rendered.contains("DISAGREE"));
    assert!(rendered.contains("awc: KIAD 041730Z"));
    assert!(rendered.contains("leidos: TAF KIAD 041730Z"));
}

#[test]
fn product_in_only_one_source_is_attributed_to_it() {
    let awc = briefing(
        "awc",
        vec![product(
            ProductKind::Metar,
            "KDCA",
            "KDCA 041952Z 30010KT 10SM CLR 30/12 A3001",
        )],
    );
    let leidos = briefing(
        "leidos",
        vec![product(
            ProductKind::Metar,
            "KJYO",
            "METAR KJYO 041952Z 27005KT 10SM CLR 28/14 A3003=",
        )],
    );
    let report = compare(&[awc, leidos]);

    let kdca = find(&report, "KDCA", &ProductKind::Metar);
    assert_eq!(kdca.agreement, Agreement::Single);
    assert_eq!(kdca.values.len(), 1);
    assert_eq!(kdca.values[0].source, "awc");

    let kjyo = find(&report, "KJYO", &ProductKind::Metar);
    assert_eq!(kjyo.agreement, Agreement::Single);
    assert_eq!(kjyo.values[0].source, "leidos");

    let rendered = report.to_string();
    assert!(rendered.contains("KDCA Metar [only awc]"));
    assert!(rendered.contains("KJYO Metar [only leidos]"));
}

#[test]
fn disagreements_helper_filters_to_conflicts_only() {
    let awc = briefing(
        "awc",
        vec![
            product(ProductKind::Metar, "KIAD", "KIAD 041952Z 28008KT A3003"),
            product(ProductKind::Taf, "KIAD", "KIAD 041730Z 27010KT SCT250"),
        ],
    );
    let leidos = briefing(
        "leidos",
        vec![
            product(
                ProductKind::Metar,
                "KIAD",
                "METAR KIAD 041952Z 28008KT A3003=",
            ),
            product(ProductKind::Taf, "KIAD", "TAF KIAD 041730Z 30015KT BKN040="),
        ],
    );
    let report = compare(&[awc, leidos]);
    let conflicts: Vec<&ProductComparison> = report.disagreements().collect();
    assert_eq!(conflicts.len(), 1);
    assert_eq!(conflicts[0].key.kind, ProductKind::Taf);
}

#[test]
fn output_order_is_deterministic_by_location_then_kind() {
    let a = briefing(
        "awc",
        vec![
            product(ProductKind::Taf, "KSEA", "..."),
            product(ProductKind::Metar, "KSEA", "..."),
            product(ProductKind::Metar, "KBOS", "..."),
        ],
    );
    let report = compare(&[a]);
    let order: Vec<(&str, &ProductKind)> = report
        .comparisons
        .iter()
        .map(|c| (c.key.location.as_deref().unwrap_or(""), &c.key.kind))
        .collect();
    assert_eq!(
        order,
        vec![
            ("KBOS", &ProductKind::Metar),
            ("KSEA", &ProductKind::Metar),
            ("KSEA", &ProductKind::Taf),
        ]
    );
}