epiphany/crates/epiphany-bundle/tests/manifest_selection.rs

59 lines
2.3 KiB
Rust

//! The manifest-selection acceptance gate (QUICKSTART, Agent D): *"the manifest
//! selection harness handles every corruption scenario (slot A corrupt + slot B
//! valid, vice versa, both valid same generation, both valid generation+1,
//! neither valid)."*
//!
//! The harness itself lives in [`epiphany_bundle::fuzz::run_manifest_selection_harness`]
//! (it needs crate-private image construction); this integration test drives it
//! through the public surface and adds a couple of end-to-end checks against the
//! real commit path.
use epiphany_bundle::fuzz::run_manifest_selection_harness;
use epiphany_bundle::BundleCapabilities;
use epiphany_bundle::{Bundle, DocumentId, FileUuid, Manifest, MemStore, Slot, StagedChunk};
#[test]
fn every_selection_scenario_holds() {
run_manifest_selection_harness();
}
/// After a real commit, both slots are valid and differ by exactly one
/// generation; selection picks the higher (the just-committed state), and
/// corrupting it falls back cleanly to the previous generation.
#[test]
fn commit_then_corrupt_active_slot_falls_back() {
let mut bundle = Bundle::create(
MemStore::new(),
FileUuid([3; 16]),
Manifest::empty(DocumentId([4; 16])),
BundleCapabilities::synthetic_for_fixture(0),
)
.unwrap();
// Commit once: slot A holds gen 0, slot B holds gen 1 (active).
let payload = epiphany_bundle::encode_block(&[b"env".to_vec()]);
bundle
.commit(&[StagedChunk::operation_block(payload)], |ctx| {
let mut m = ctx.previous_manifest.clone();
m.operation_roots.extend(ctx.new_chunks.iter().copied());
m
})
.unwrap();
assert_eq!(bundle.active_slot(), Slot::B);
assert_eq!(bundle.generation(), 1);
let mut image = bundle.into_store().into_bytes();
// Corrupt the active slot B (offset 320) inside its CRC-covered region.
image[320 + 80] ^= 0xFF;
// Recovery falls back to slot A (the previous generation), cleanly.
let recovered = Bundle::open(
MemStore::from_bytes(image),
BundleCapabilities::synthetic_for_fixture(0),
)
.unwrap();
assert_eq!(recovered.active_slot(), Slot::A);
assert_eq!(recovered.generation(), 0);
assert!(recovered.anomalies().is_empty());
recovered.verify_canonical_chunks().unwrap();
}