Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/edda

crates/nornir-feature-graph/tests/warehouse_roundtrip.rs

//! **The warehouse round-trip, and the deleted-edge red proof.**
//!
//! Build Thing does not re-walk the disk: it reads `feature_dep_edges` out of
//! Mímir and reconstitutes the world. So the rows must carry enough to rebuild
//! the same answers — and a MISSING row must change the answer, loudly, rather
//! than quietly degrading into a smaller graph that still looks plausible.
//!
//! The second test is the one the brief asked for: delete one edge from the
//! derived table and watch the check go red.

use nornir_feature_graph::{
    facts::EnableKind, universe_from_rows, FeatureDepEdgeRow, FeatureGalois, ResolveConfig,
    Universe,
};
use std::collections::BTreeSet;

const POLICY: &str = r#"
[package]
name = "auth-policy"
version = "0.1.0"
[features]
online = ["oidc?/online"]
auth-oidc = ["dep:oidc"]
[dependencies]
serde = "1"
oidc = { version = "0.1", optional = true }
"#;

const OIDC: &str = r#"
[package]
name = "oidc"
version = "0.1.0"
[features]
online = ["dep:reqwest"]
[dependencies]
reqwest = { version = "0.12", optional = true }
"#;

fn world() -> Universe {
    let mut u = Universe::default();
    u.add_manifest_str("edda", "auth-policy", POLICY).unwrap();
    u.add_manifest_str("edda", "oidc", OIDC).unwrap();
    for n in ["serde", "reqwest"] {
        u.add_manifest_str("r", n, &format!("[package]\nname = \"{n}\"\nversion = \"1.0.0\"\n"))
            .unwrap();
    }
    u
}

fn rows(u: &Universe) -> Vec<FeatureDepEdgeRow> {
    let mut r: Vec<_> = u.packages().flat_map(|p| p.edges()).collect();
    r.sort();
    r.dedup();
    r
}

fn answer(u: &Universe) -> (Vec<BTreeSet<String>>, bool) {
    let g = FeatureGalois::new(u, "auth-policy", ResolveConfig::default());
    g.features_pulling("reqwest")
}

/// The rows must reconstitute the world. Asserted on the ANSWER — the actual
/// cause sets — not on a row count, because equal row counts prove nothing
/// about whether the semantics survived the trip.
#[test]
fn rows_reconstitute_the_same_answers() {
    let disk = world();
    let table = rows(&disk);
    assert!(!table.is_empty(), "the fixture produced no rows at all");

    // The weak edge MUST be in the table — it is the only thing that makes the
    // AND-group representable, and a table without it is a table that cannot
    // answer the question this crate exists for.
    assert!(
        table.iter().any(|r| r.kind == EnableKind::WeakDepFeature && r.enables == "oidc/online"),
        "no weak-dep-feature row emitted; table = {table:#?}"
    );
    // And the unconditional ⊥ sentinel.
    assert!(
        table.iter().any(|r| r.feature.is_empty() && r.enables == "serde"),
        "no `feature = \"\"` unconditional row for serde — the ⊥ case is unrepresentable"
    );

    let from_wh = universe_from_rows(&table).expect("non-empty table must build a universe");

    let (disk_causes, disk_complete) = answer(&disk);
    let (wh_causes, wh_complete) = answer(&from_wh);

    assert_eq!(
        disk_causes,
        vec![["auth-oidc".to_string(), "online".to_string()].into_iter().collect::<BTreeSet<_>>()],
        "the on-disk answer is the conjunction"
    );
    assert_eq!(wh_causes, disk_causes, "the warehouse round-trip changed the answer");
    assert_eq!(wh_complete, disk_complete);
}

/// **The red proof.** Delete ONE edge from the derived table — the weak
/// `oidc?/online` row — and the answer must change. If it does not, the table
/// is carrying redundant information and the check is watching nothing.
#[test]
fn deleting_one_edge_changes_the_answer() {
    let disk = world();
    let full = rows(&disk);

    let pruned: Vec<FeatureDepEdgeRow> = full
        .iter()
        .filter(|r| !(r.kind == EnableKind::WeakDepFeature && r.enables == "oidc/online"))
        .cloned()
        .collect();
    assert_eq!(pruned.len(), full.len() - 1, "exactly one row must have been removed");

    let (before, _) = answer(&universe_from_rows(&full).unwrap());
    let (after, _) = answer(&universe_from_rows(&pruned).unwrap());

    assert!(!before.is_empty(), "precondition: the intact table finds the cause");
    assert!(
        after.is_empty(),
        "DELETING THE WEAK EDGE DID NOT CHANGE THE ANSWER — the check is blind. \
         before={before:?} after={after:?}"
    );
    assert_ne!(before, after, "one deleted row must move the answer");
}

/// A universe built from ZERO rows must be an ERROR, never an empty universe
/// that confidently answers "nothing depends on this".
///
/// This exact confusion — querying the wrong warehouse and reading 0 rows as a
/// fact — cost an hour on 2026-07-31.
#[test]
fn an_empty_table_is_loud_not_empty() {
    let err = universe_from_rows(&[]).expect_err("zero rows MUST be an error");
    let msg = err.to_string();
    assert!(
        msg.contains("ZERO rows") && msg.contains("never populated"),
        "the error must say empty-vs-absent in words: {msg}"
    );
    // The negative control: one row is enough to succeed, so the guard is
    // reacting to emptiness and not simply always failing.
    let one = vec![FeatureDepEdgeRow {
        repo: "r".into(),
        crate_name: "a".into(),
        feature: "f".into(),
        enables: "b".into(),
        kind: EnableKind::Dep,
        optional: true,
        target_cfg: None,
        dep_kind: nornir_feature_graph::DepKind::Normal,
    }];
    assert!(universe_from_rows(&one).is_ok(), "one row must succeed — else the guard is not about emptiness");
}