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

nordisk/facett

facett-releaseview/tests/derive.rs

//! **RED-when-broken** unit tests for the convergence-status kernel — the pure
//! [`derive_status`] fn across all four version-derived states, plus the fifth
//! ([`ConvergeStatus::Publishing`]) reached through the realtime [`apply`] seam, and
//! the whole-model roll-ups. If a status rule regresses, exactly one of these goes
//! red and names the case.
//!
//! [`apply`]: facett_releaseview::model::ReleaseConvergence::apply

use facett_releaseview::model::{
    ConvergeStatus, CrateConvergence, DepEdge, ReleaseConvergence, ReleaseEvent, derive_status,
};

#[test]
fn all_five_convergence_states_are_reachable() {
    // 1. Converged — the three graphs agree.
    assert_eq!(derive_status("0.5.4", "0.5.4", "0.5.4"), ConvergeStatus::Converged);

    // 2. NeedsRelease — crates.io behind local (must publish).
    assert_eq!(derive_status("0.5.4", "0.5.4", "0.5.3"), ConvergeStatus::NeedsRelease);
    // ...even when sparring also leads (still a release, red dominates).
    assert_eq!(derive_status("0.6.0", "0.6.0", "0.5.9"), ConvergeStatus::NeedsRelease);

    // 3. LockDesync — sparring (resolved) disagrees with cratesio (published), but
    //    crates.io is NOT behind local (so not a plain needs-release).
    assert_eq!(derive_status("0.5.4", "0.5.5", "0.5.4"), ConvergeStatus::LockDesync);

    // 4. BumpDrift — sparring differs from local, while sparring == cratesio.
    assert_eq!(derive_status("0.5.3", "0.5.4", "0.5.4"), ConvergeStatus::BumpDrift);

    // 5. Publishing — NOT version-derived; it is the transient live-event state.
    let mut m = ReleaseConvergence::new("t").with_node(CrateConvergence::new("a", "0.5.4", "0.5.4", "0.5.3"));
    assert_eq!(m.nodes[0].status, ConvergeStatus::NeedsRelease);
    assert!(m.apply(ReleaseEvent::PublishStart { krate: "a".into() }));
    assert_eq!(m.nodes[0].status, ConvergeStatus::Publishing, "PublishStart flips the crate to Publishing");
}

#[test]
fn derive_status_never_returns_publishing() {
    // Publishing is a live-event state, never a function of the versions alone.
    for triple in [
        ("0.5.4", "0.5.4", "0.5.4"),
        ("0.5.4", "0.5.4", "0.5.3"),
        ("0.5.4", "0.5.5", "0.5.4"),
        ("0.5.3", "0.5.4", "0.5.4"),
        ("1.0.0", "0.9.0", "0.8.0"),
    ] {
        assert_ne!(derive_status(triple.0, triple.1, triple.2), ConvergeStatus::Publishing);
    }
}

#[test]
fn published_event_converges_the_node() {
    let mut m = ReleaseConvergence::new("release")
        .with_node(CrateConvergence::new("nornir", "0.5.4", "0.5.4", "0.5.3"));
    assert_eq!(m.nodes[0].status, ConvergeStatus::NeedsRelease);
    assert!(!m.is_converged());

    // Live cascade: start publishing, then it lands at the local/sparring version.
    m.apply(ReleaseEvent::PublishStart { krate: "nornir".into() });
    assert_eq!(m.nodes[0].status, ConvergeStatus::Publishing);
    m.apply(ReleaseEvent::Published { krate: "nornir".into(), version: "0.5.4".into() });
    assert_eq!(m.nodes[0].status, ConvergeStatus::Converged, "a publish that matches local+sparring converges");
    assert!(m.is_converged());
}

#[test]
fn sparring_and_local_events_reconcile_drift() {
    // BumpDrift: sparring hoisted 0.5.4, local still 0.5.3, cratesio 0.5.4.
    let mut m = ReleaseConvergence::new("r")
        .with_node(CrateConvergence::new("skade", "0.5.3", "0.5.4", "0.5.4"));
    assert_eq!(m.nodes[0].status, ConvergeStatus::BumpDrift);
    // Adopting the bump into the working tree converges it.
    m.apply(ReleaseEvent::LocalBumped { krate: "skade".into(), version: "0.5.4".into() });
    assert_eq!(m.nodes[0].status, ConvergeStatus::Converged);
}

#[test]
fn status_counts_and_converged_fraction_roll_up() {
    let m = ReleaseConvergence::new("nordisk")
        .with_node(CrateConvergence::new("a", "0.1.0", "0.1.0", "0.1.0")) // converged
        .with_node(CrateConvergence::new("b", "0.2.0", "0.2.0", "0.1.9")) // needs-release
        .with_node(CrateConvergence::new("c", "0.3.0", "0.3.1", "0.3.0")) // lock-desync
        .with_node(CrateConvergence::new("d", "0.4.0", "0.4.1", "0.4.1")) // bump-drift
        .with_edge(DepEdge::new(1, 0));
    // (converged, needs_release, lock_desync, bump_drift, publishing)
    assert_eq!(m.status_counts(), (1, 1, 1, 1, 0));
    assert!(!m.is_converged());
    assert!((m.converged_fraction() - 0.25).abs() < 1e-6, "1 of 4 converged");
}

#[test]
fn apply_to_unknown_crate_is_a_noop() {
    let mut m = ReleaseConvergence::new("r").with_node(CrateConvergence::new("a", "1.0.0", "1.0.0", "1.0.0"));
    assert!(!m.apply(ReleaseEvent::PublishStart { krate: "ghost".into() }), "unknown crate → no change");
    assert_eq!(m.nodes[0].status, ConvergeStatus::Converged);
}