From d10e3cd88e22b18bdb33729c8d58c6e3aa5ec9d5 Mon Sep 17 00:00:00 2001 From: "K. Isom" Date: Sun, 18 Oct 2026 04:28:38 -0700 Subject: [PATCH] test(core): close deferred coverage gaps --- tines-core/src/exchange.rs | 80 +++++++++++++++++++++++++++++++++++++ tines-core/src/mem_store.rs | 10 +++++ tines-core/tests/pair.rs | 30 ++++++++++++++ 3 files changed, 120 insertions(+) diff --git a/tines-core/src/exchange.rs b/tines-core/src/exchange.rs index fabe271..79c21c2 100644 --- a/tines-core/src/exchange.rs +++ b/tines-core/src/exchange.rs @@ -1036,6 +1036,86 @@ mod tests { ); } + #[test] + fn valid_addressed_obs_is_accepted() { + let mut ex = Exchange::new(ME); + let mut store = MemStore::new(); + ex.on_event(Event::Contact(1), &mut store, 0); + // A well-formed, non-expired addressed obs addressed to us IS stored. + let mut o = obs([2; 16], 7, 0, None, b"t", b"hi"); + o.to = Some(ME); + o.lifetime_ms = Some(1000); + let id = o.id(); + let frame = Frame::Obs { + from: [2; 16], + obs: o.clone(), + }; + assert!( + ex.on_event(Event::Frame(1, frame_bytes(&frame)), &mut store, 0) + .is_some(), + "a valid addressed obs must be accepted" + ); + assert!(store.contains(&id)); + assert_eq!(store.len(), 1); + } + + #[test] + fn addressing_invariant_violation_reverse_is_dropped() { + let mut ex = Exchange::new(ME); + let mut store = MemStore::new(); + ex.on_event(Event::Contact(1), &mut store, 0); + // to = None but lifetime_ms = Some: the invariant is violated in the + // reverse direction, so this frame is malformed and must be dropped. + let mut o = obs([2; 16], 7, 0, None, b"t", b"hi"); + o.to = None; + o.lifetime_ms = Some(100); + let frame = Frame::Obs { + from: [2; 16], + obs: o.clone(), + }; + assert!(ex + .on_event(Event::Frame(1, frame_bytes(&frame)), &mut store, 0) + .is_none()); + assert_eq!( + store.len(), + 0, + "an invariant-violating frame must not be stored" + ); + } + + #[test] + fn expired_addressed_obs_is_not_forwarded() { + let mut ex = Exchange::new(ME); + let mut store = MemStore::new(); + // An addressed obs already past its lifetime, inserted directly so it + // bypasses the receive-side reject (this tests the FORWARD path). + let mut o = obs([2; 16], 7, 0, None, b"t", b"hi"); + o.to = Some([3; 16]); + o.lifetime_ms = Some(100); + o.age_ms = 200; + let id = o.id(); + store.insert(o, 0).unwrap(); + + // A peer whose NodeId we learn (not the recipient, so epidemic applies). + ex.on_event(Event::Contact(1), &mut store, 0); + let bits = heapless::Vec::from_slice(&[0u8; consts::SUMMARY_BYTES]).unwrap(); + let sum = Frame::Summary { + from: [2; 16], + salt: 0, + bits, + }; + ex.on_event(Event::Frame(1, frame_bytes(&sum)), &mut store, 0); + + // The expired obs must never be forwarded to any peer. + assert!( + drain_addressed_obs(&mut ex, &store, Some([3; 16])).is_none(), + "an expired addressed obs must not be forwarded" + ); + // It is still held (expiry is checked at select/forward time, not + // retroactively removed here). + assert!(store.contains(&id)); + } + #[test] fn addressed_obs_not_in_summary() { let mut ex = Exchange::new(ME); diff --git a/tines-core/src/mem_store.rs b/tines-core/src/mem_store.rs index 95405ed..a26bbd8 100644 --- a/tines-core/src/mem_store.rs +++ b/tines-core/src/mem_store.rs @@ -419,5 +419,15 @@ mod tests { // The broadcast survives even though it shares topic/observer with the // dropped addressed observations. assert!(s.contains(&broadcast_id)); + // The three youngest addressed (seq 0..2, ages 10/20/30) survive along + // with the broadcast (seq 100); seq 3..9 are dropped. + let mut seqs = std::collections::BTreeSet::new(); + s.for_each_topic(b"t", 0, &mut |o| { + seqs.insert(o.seq); + }); + assert_eq!( + seqs, + std::collections::BTreeSet::from_iter([0u64, 1, 2, 100]) + ); } } diff --git a/tines-core/tests/pair.rs b/tines-core/tests/pair.rs index fac87e2..b3c6868 100644 --- a/tines-core/tests/pair.rs +++ b/tines-core/tests/pair.rs @@ -275,3 +275,33 @@ fn belief_excludes_addressed_to_others() { ); assert_eq!(belief.observer, [1; 16]); } + +#[test] +fn observe_to_rejects_oversized_payload() { + let (a, _b) = PairTransport::pair(1500); + let mut node = Node::new([1u8; 16], 0, MemStore::new(), a); + + let oversized_payload = vec![0u8; consts::MAX_PAYLOAD + 1]; + assert_eq!( + node.observe_to([2; 16], 5000, b"task", &oversized_payload, None, 0), + Err(ObserveError::TooLarge) + ); + assert_eq!(node.store().len(), 0); + assert_eq!(node.next_seq(), 0); +} + +#[test] +fn belief_includes_addressed_to_me() { + let (a, _b) = PairTransport::pair(1500); + let mut node = Node::new([1u8; 16], 0, MemStore::new(), a); + + // An addressed message *to me* is part of my app-facing view. + node.observe_to([1; 16], 10_000, b"door", b"for-me", None, 10) + .unwrap(); + + let belief = node + .belief(b"door", &LatestWins, 100_000) + .expect("door belief exists"); + assert_eq!(belief.payload, Payload::from_slice(b"for-me").unwrap()); + assert_eq!(belief.observer, [1; 16]); +}