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"
);
}