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

nordisk/holger

update-bundle/src/tests/targeting.rs

//! Targeting + anti-rollback tests.

use chrono::Utc;

use super::testkit::*;
use crate::core::BundleCore;
use crate::rollback::{AcceptedEntry, AcceptedState, Admissibility, Floor, RollbackStore};
use crate::schema::CORE_JSON;
use crate::verify::{verify_member_map, TargetSpec};

fn target(p: &str, m: &str, r: &str) -> TargetSpec {
    TargetSpec {
        product: Some(p.into()),
        model: Some(m.into()),
        revision: Some(r.into()),
    }
}

fn core_of(b: &BuiltBundle) -> BundleCore {
    BundleCore::from_json_bytes(&b.members[CORE_JSON]).unwrap()
}

#[test]
fn correct_target_passes() {
    let b = valid_bundle();
    assert!(verify_member_map(
        &b.members,
        &b.policy,
        &target("ExampleDrive", "ACS880", "R4"),
        None,
        b.now
    )
    .is_ok());
}

#[test]
fn wrong_product_fails() {
    let b = valid_bundle();
    assert!(verify_member_map(
        &b.members,
        &b.policy,
        &target("OtherDrive", "ACS880", "R4"),
        None,
        b.now
    )
    .is_err());
}

#[test]
fn wrong_model_fails() {
    let b = valid_bundle();
    assert!(verify_member_map(
        &b.members,
        &b.policy,
        &target("ExampleDrive", "ACS999", "R4"),
        None,
        b.now
    )
    .is_err());
}

#[test]
fn wrong_revision_fails() {
    let b = valid_bundle();
    assert!(verify_member_map(
        &b.members,
        &b.policy,
        &target("ExampleDrive", "ACS880", "R9"),
        None,
        b.now
    )
    .is_err());
}

fn entry(seq: u64, id: &str, digest: &str) -> AcceptedEntry {
    AcceptedEntry {
        sequence: seq,
        bundle_id: id.into(),
        core_digest: digest.into(),
        accepted_at: Utc::now(),
    }
}

#[test]
fn newer_sequence_is_admissible() {
    let b = valid_bundle_seq(5, "bundle-0005");
    let core = core_of(&b);
    let state = AcceptedState::from_entries(vec![entry(3, "old", "aa")]);
    assert_eq!(
        state.check_admissible(&core, &b.candidate_digest).unwrap(),
        Admissibility::New
    );
}

#[test]
fn older_sequence_is_rejected() {
    let b = valid_bundle_seq(5, "bundle-0005");
    let core = core_of(&b);
    let state = AcceptedState::from_entries(vec![entry(9, "newer", "bb")]);
    assert!(state.check_admissible(&core, &b.candidate_digest).is_err());
}

#[test]
fn same_sequence_different_bytes_is_replay() {
    let b = valid_bundle_seq(5, "bundle-0005");
    let core = core_of(&b);
    let state = AcceptedState::from_entries(vec![entry(5, "bundle-0005", "different-digest")]);
    assert!(state.check_admissible(&core, &b.candidate_digest).is_err());
}

#[test]
fn same_sequence_same_bytes_is_idempotent() {
    let b = valid_bundle_seq(5, "bundle-0005");
    let core = core_of(&b);
    let state = AcceptedState::from_entries(vec![entry(5, "bundle-0005", &b.candidate_digest)]);
    assert_eq!(
        state.check_admissible(&core, &b.candidate_digest).unwrap(),
        Admissibility::IdempotentReimport
    );
}

#[test]
fn bundle_id_reuse_with_different_bytes_is_rejected() {
    let b = valid_bundle_seq(7, "bundle-shared");
    let core = core_of(&b);
    let state = AcceptedState::from_entries(vec![entry(3, "bundle-shared", "different-digest")]);
    assert!(state.check_admissible(&core, &b.candidate_digest).is_err());
}

#[test]
fn rollback_store_records_and_reloads() {
    let dir = tempfile::tempdir().unwrap();
    let store = RollbackStore::open(dir.path());
    assert!(store
        .load("ExampleCorp", "ExampleDrive")
        .unwrap()
        .highest_accepted_sequence()
        .is_none());
    let e = entry(5, "bundle-0005", "digest-5");
    store.record("ExampleCorp", "ExampleDrive", &e).unwrap();
    // Idempotent re-record of the identical marker.
    store.record("ExampleCorp", "ExampleDrive", &e).unwrap();
    // Conflicting marker at the same sequence is refused.
    let conflict = entry(5, "bundle-0005", "different-digest");
    assert!(store
        .record("ExampleCorp", "ExampleDrive", &conflict)
        .is_err());
    let state = store.load("ExampleCorp", "ExampleDrive").unwrap();
    assert_eq!(state.highest_accepted_sequence(), Some(5));
}

// ── The anti-rollback FLOOR (?26/?27, 2026-09-20) ────────────────────────────
//
// RED-when-broken: each of these fails the moment an absent floor goes back to
// admitting whatever it is handed.

#[test]
fn an_absent_floor_refuses_an_import_that_did_not_ask_to_establish_it() {
    let b = valid_bundle_seq(5, "bundle-0005");
    let core = core_of(&b);
    let floor = Floor::Absent;
    let err = floor
        .check_admissible(&core, &b.candidate_digest, false)
        .expect_err("an absent floor must refuse, not admit");
    assert_eq!(
        err.code().code,
        holger_errcode::UPDATE_ROLLBACK_FLOOR_ABSENT.code,
        "the refusal carries its own frozen code, got: {err}"
    );
}

#[test]
fn an_absent_floor_admits_only_when_first_import_is_asked_for_by_name() {
    let b = valid_bundle_seq(5, "bundle-0005");
    let core = core_of(&b);
    assert_eq!(
        Floor::Absent
            .check_admissible(&core, &b.candidate_digest, true)
            .unwrap(),
        Admissibility::New,
    );
}

#[test]
fn first_import_does_not_weaken_a_floor_that_is_in_force() {
    // A floor at sequence 9; the bundle is older. `--first-import` must NOT be a
    // downgrade bypass — it only answers "there is no floor", never "ignore it".
    let b = valid_bundle_seq(5, "bundle-0005");
    let core = core_of(&b);
    let floor = Floor::InForce(AcceptedState::from_entries(vec![entry(9, "newer", "bb")]));
    assert!(floor
        .check_admissible(&core, &b.candidate_digest, true)
        .is_err());
}

#[test]
fn an_emptied_state_directory_reads_as_absent_not_as_a_cleared_floor() {
    // `rm *.accepted` must not be a quieter erase than `rm -r`: a directory that
    // exists but holds no marker admits exactly what a missing directory admits,
    // so it has to answer the same way.
    let dir = tempfile::tempdir().unwrap();
    let store = RollbackStore::open(dir.path());
    assert!(
        !store
            .floor("ExampleCorp", "ExampleDrive")
            .unwrap()
            .is_in_force(),
        "a store with no directory at all has no floor"
    );

    let e = entry(5, "bundle-0005", "digest-5");
    store.record("ExampleCorp", "ExampleDrive", &e).unwrap();
    assert!(store
        .floor("ExampleCorp", "ExampleDrive")
        .unwrap()
        .is_in_force());

    // Erase the markers but leave the directory standing — the wipe shape.
    let d = dir.path().join("ExampleCorp").join("ExampleDrive");
    for de in std::fs::read_dir(&d).unwrap().flatten() {
        std::fs::remove_file(de.path()).unwrap();
    }
    let floor = store.floor("ExampleCorp", "ExampleDrive").unwrap();
    assert!(
        !floor.is_in_force(),
        "an emptied state directory is Absent, not an in-force floor of nothing"
    );
}