use agent_frame::{
verify_unit, verify_unit_with, Op, RootEvent, RootKind, SourceResolver, Span, Unit, VerifyError,
};
use content_addressable::{ContentId, RawContentId};
const SRC: &[u8] = b"the quick brown fox jumps over the lazy dog";
const OTHER: &[u8] = b"the quick brown fox jumps over the lazy cat";
fn root() -> ContentId {
RootEvent::new(RootKind::UserAction, b"esc", 0)
.id()
.unwrap()
}
fn unit(span: Span) -> Unit {
Unit::seal(Op::Elide, SRC, span, root()).expect("mintable")
}
#[test]
fn a_unit_re_derives_against_its_own_source() {
let u = unit(Span::new(4, 19));
let v = verify_unit(&u, SRC).expect("a unit must verify against the source it was sealed over");
assert_eq!(v.source, RawContentId::from_content(SRC));
assert_eq!(v.span, Span::new(4, 19));
assert_eq!(v.elided, RawContentId::from_content(&SRC[4..19]));
assert_eq!(v.elided_len, 15);
assert_eq!(v.source_len, SRC.len() as u64);
assert_eq!(
&SRC[4..19],
b"quick brown fox",
"the fixture must actually address the bytes the assertion names"
);
}
#[test]
fn a_near_identical_source_is_rejected() {
let u = unit(Span::new(4, 19));
let err = verify_unit(&u, OTHER).expect_err("one byte different is a different source");
match err {
VerifyError::SourceMismatch { at, len } => {
assert_eq!(at.declared, RawContentId::from_content(SRC));
assert_eq!(at.actual, RawContentId::from_content(OTHER));
assert_eq!(len, OTHER.len());
assert_ne!(at.declared, at.actual, "the error must name BOTH sides");
}
other => panic!("expected a source mismatch, got {other:?}"),
}
}
#[test]
fn a_span_past_the_end_of_the_source_is_rejected() {
let over = Span::new(4, SRC.len() as u64 + 1);
assert!(
Unit::seal(Op::Elide, SRC, over, root()).is_err(),
"seal must refuse a span it cannot address"
);
let mut raw = unit(Span::new(4, 19)).to_raw();
raw.span = over;
let smuggled = Unit::try_from(raw).expect("admission cannot see the source length");
let err = verify_unit(&smuggled, SRC).expect_err("the span runs past the end");
assert!(
matches!(
err,
VerifyError::SpanOutOfBounds { source_len, .. } if source_len == SRC.len()
),
"got {err:?}"
);
}
#[test]
fn a_forged_elision_claim_is_caught_by_recomputation() {
let mut raw = unit(Span::new(4, 19)).to_raw();
raw.elided = RawContentId::from_content(b"quick brown cat");
let forged = Unit::try_from(raw).expect("shape is fine; the lie is about the world");
let err = verify_unit(&forged, SRC).expect_err("recomputation must catch the forged claim");
match err {
VerifyError::ElisionMismatch { at, start, end } => {
assert_eq!(at.declared, RawContentId::from_content(b"quick brown cat"));
assert_eq!(at.actual, RawContentId::from_content(b"quick brown fox"));
assert_eq!((start, end), (4, 19));
}
other => panic!("expected an elision mismatch, got {other:?}"),
}
}
#[test]
fn re_derivation_holds_across_every_span_of_a_source() {
let n = SRC.len() as u64;
for start in 0..n {
for end in (start + 1)..=n {
let u = unit(Span::new(start, end));
let v = verify_unit(&u, SRC).expect("every real span re-derives");
assert_eq!(v.elided_len, end - start);
}
}
}
struct Fixed(Vec<u8>);
impl SourceResolver for Fixed {
fn resolve_unverified(&self, _id: &RawContentId) -> Result<Vec<u8>, String> {
Ok(self.0.clone())
}
}
struct Missing;
impl SourceResolver for Missing {
fn resolve_unverified(&self, id: &RawContentId) -> Result<Vec<u8>, String> {
Err(format!("no blob {id}"))
}
}
#[test]
fn the_resolver_wrapper_decides_identically_to_the_direct_call() {
let u = unit(Span::new(4, 19));
assert_eq!(
verify_unit_with(&u, &Fixed(SRC.to_vec())),
verify_unit(&u, SRC),
"one verifier: the wrapper must not reach a different verdict"
);
assert_eq!(
verify_unit_with(&u, &Fixed(OTHER.to_vec())),
verify_unit(&u, OTHER),
"including on the failing verdict — a wrapper that only agrees when \
things pass is two verifiers"
);
}
#[test]
fn an_unresolvable_source_fails_closed() {
let u = unit(Span::new(4, 19));
let err = verify_unit_with(&u, &Missing).expect_err("absence is a finding, not a pass");
match err {
VerifyError::SourceUnavailable { id, reason } => {
assert_eq!(*id, RawContentId::from_content(SRC));
assert!(reason.contains("no blob"), "{reason}");
}
other => panic!("expected source-unavailable, got {other:?}"),
}
}