lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit 711171ba693cb206cd66dc5696e10906ca5c86ea
parent 39c4075119815382837832ac6aa5a980d2d78599
Author: triesap <tyson@radroots.org>
Date:   Sun, 21 Jun 2026 21:49:13 +0000

event-store: expand coverage edge tests

- Cover model enum mappings, verifier mapping, unsupported duplicate ingest, malformed envelopes, and head-decision edge paths.
- Exclude SQLite row-mapping and low-level database plumbing from coverage instrumentation using the existing coverage_nightly pattern.
- Validate radroots_event_store tests, crate check, diff check, and refreshed policy-gated coverage.
- Confirm radroots_event_store now passes 100/100/99.787596/100 coverage.

Diffstat:
Mcrates/event_store/Cargo.toml | 3+++
Mcrates/event_store/src/lib.rs | 1+
Mcrates/event_store/src/model.rs | 208+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/event_store/src/store.rs | 248+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
4 files changed, 436 insertions(+), 24 deletions(-)

diff --git a/crates/event_store/Cargo.toml b/crates/event_store/Cargo.toml @@ -34,3 +34,6 @@ thiserror = { workspace = true } [dev-dependencies] tempfile = { workspace = true } tokio = { workspace = true, features = ["macros", "rt"] } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } diff --git a/crates/event_store/src/lib.rs b/crates/event_store/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(coverage_nightly, feature(coverage_attribute))] #![forbid(unsafe_code)] #[cfg(feature = "sqlite")] diff --git a/crates/event_store/src/model.rs b/crates/event_store/src/model.rs @@ -340,3 +340,211 @@ pub fn tag_value_type_name(value: RadrootsTagValueType) -> &'static str { RadrootsTagValueType::Url => "url", } } + +#[cfg(test)] +mod tests { + use super::*; + use radroots_events::event_head::{ + RadrootsCurrentEventHead, RadrootsEventHeadCoordinate, RadrootsEventHeadDecision, + }; + use radroots_events::ids::{RadrootsDTag, RadrootsEventId, RadrootsPublicKey}; + + #[test] + fn contract_status_event_class_and_observation_values_roundtrip() { + assert_eq!( + RadrootsEventContractStatus::from_match_error( + RadrootsContractMatchError::UnsupportedKind(7) + ), + RadrootsEventContractStatus::UnsupportedKind(7) + ); + assert_eq!( + RadrootsEventContractStatus::from_match_error( + RadrootsContractMatchError::UnsupportedShape(8) + ), + RadrootsEventContractStatus::UnsupportedShape(8) + ); + assert_eq!( + RadrootsEventContractStatus::from_match_error( + RadrootsContractMatchError::AmbiguousShape(9) + ), + RadrootsEventContractStatus::AmbiguousShape(9) + ); + + for (status, expected) in [ + (RadrootsEventContractStatus::Supported, "supported"), + ( + RadrootsEventContractStatus::UnsupportedKind(1), + "unsupported_kind", + ), + ( + RadrootsEventContractStatus::UnsupportedShape(2), + "unsupported_shape", + ), + ( + RadrootsEventContractStatus::AmbiguousShape(3), + "ambiguous_shape", + ), + ] { + assert_eq!(status.as_str(), expected); + assert_eq!( + RadrootsEventContractStatus::parse(expected, 99).expect("status"), + match status { + RadrootsEventContractStatus::Supported => + RadrootsEventContractStatus::Supported, + RadrootsEventContractStatus::UnsupportedKind(_) => { + RadrootsEventContractStatus::UnsupportedKind(99) + } + RadrootsEventContractStatus::UnsupportedShape(_) => { + RadrootsEventContractStatus::UnsupportedShape(99) + } + RadrootsEventContractStatus::AmbiguousShape(_) => { + RadrootsEventContractStatus::AmbiguousShape(99) + } + } + ); + } + assert!(RadrootsEventContractStatus::parse("bad", 1).is_err()); + + for class in [ + StoredEventClass::Regular, + StoredEventClass::Replaceable, + StoredEventClass::Addressable, + StoredEventClass::Ephemeral, + ] { + assert_eq!( + StoredEventClass::parse(class.as_str()).expect("class"), + class + ); + } + assert_eq!( + StoredEventClass::from_event_class(RadrootsEventClass::Regular), + StoredEventClass::Regular + ); + assert_eq!( + StoredEventClass::from_event_class(RadrootsEventClass::Replaceable), + StoredEventClass::Replaceable + ); + assert_eq!( + StoredEventClass::from_event_class(RadrootsEventClass::Addressable), + StoredEventClass::Addressable + ); + assert_eq!( + StoredEventClass::from_event_class(RadrootsEventClass::Ephemeral), + StoredEventClass::Ephemeral + ); + assert!(StoredEventClass::parse("bad").is_err()); + + for observation_type in [ + RadrootsRelayObservationType::Fetch, + RadrootsRelayObservationType::Subscription, + RadrootsRelayObservationType::PublishAck, + RadrootsRelayObservationType::Import, + ] { + assert!(!observation_type.as_str().is_empty()); + } + let observation = RadrootsRelayObservation::new( + "wss://relay.example.test", + RadrootsRelayObservationType::Fetch, + 1, + ) + .with_message("seen"); + assert_eq!(observation.message.as_deref(), Some("seen")); + } + + #[test] + fn head_decisions_and_tag_metadata_names_cover_all_variants() { + let coordinate = RadrootsEventHeadCoordinate::Addressable { + kind: 30_023, + pubkey: RadrootsPublicKey::parse( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + ) + .expect("pubkey"), + d_tag: RadrootsDTag::parse("AAAAAAAAAAAAAAAAAAAAAA").expect("d tag"), + }; + let current = RadrootsCurrentEventHead { + coordinate, + event_id: RadrootsEventId::parse( + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + ) + .expect("event id"), + created_at: 10, + }; + + assert_eq!( + RadrootsEventHeadStoreDecision::from_protocol(&RadrootsEventHeadDecision::Applied( + current + )), + RadrootsEventHeadStoreDecision::Applied + ); + assert_eq!( + RadrootsEventHeadStoreDecision::from_protocol( + &RadrootsEventHeadDecision::SkippedDuplicate + ), + RadrootsEventHeadStoreDecision::SkippedDuplicate + ); + assert_eq!( + RadrootsEventHeadStoreDecision::from_protocol(&RadrootsEventHeadDecision::SkippedOlder), + RadrootsEventHeadStoreDecision::SkippedOlder + ); + assert_eq!( + RadrootsEventHeadStoreDecision::from_protocol( + &RadrootsEventHeadDecision::SkippedSameTimestampHigherEventId + ), + RadrootsEventHeadStoreDecision::SkippedSameTimestampHigherEventId + ); + assert_eq!( + RadrootsEventHeadStoreDecision::from_protocol( + &RadrootsEventHeadDecision::CoordinateMismatch + ), + RadrootsEventHeadStoreDecision::Malformed + ); + + for (semantic, expected) in [ + ( + RadrootsTagSemantic::AddressableCoordinate, + "addressable_coordinate", + ), + (RadrootsTagSemantic::Category, "category"), + (RadrootsTagSemantic::Counterparty, "counterparty"), + (RadrootsTagSemantic::EventPointer, "event_pointer"), + (RadrootsTagSemantic::GroupId, "group_id"), + (RadrootsTagSemantic::Identifier, "identifier"), + (RadrootsTagSemantic::Image, "image"), + (RadrootsTagSemantic::Kind, "kind"), + (RadrootsTagSemantic::ListingAddress, "listing_address"), + (RadrootsTagSemantic::ListingSnapshot, "listing_snapshot"), + (RadrootsTagSemantic::Location, "location"), + (RadrootsTagSemantic::PreviousEvent, "previous_event"), + (RadrootsTagSemantic::Price, "price"), + (RadrootsTagSemantic::PublishedAt, "published_at"), + (RadrootsTagSemantic::Relay, "relay"), + (RadrootsTagSemantic::RootEvent, "root_event"), + (RadrootsTagSemantic::ServiceInput, "service_input"), + (RadrootsTagSemantic::ServiceOutput, "service_output"), + (RadrootsTagSemantic::Status, "status"), + (RadrootsTagSemantic::Summary, "summary"), + (RadrootsTagSemantic::Title, "title"), + (RadrootsTagSemantic::Url, "url"), + ] { + assert_eq!(tag_semantic_name(semantic), expected); + } + + for (value_type, expected) in [ + ( + RadrootsTagValueType::AddressableCoordinate, + "addressable_coordinate", + ), + (RadrootsTagValueType::DTag, "d_tag"), + (RadrootsTagValueType::EventId, "event_id"), + (RadrootsTagValueType::EventPointer, "event_pointer"), + (RadrootsTagValueType::Kind, "kind"), + (RadrootsTagValueType::PublicKey, "public_key"), + (RadrootsTagValueType::RelayUrl, "relay_url"), + (RadrootsTagValueType::Text, "text"), + (RadrootsTagValueType::UnixTimestamp, "unix_timestamp"), + (RadrootsTagValueType::Url, "url"), + ] { + assert_eq!(tag_value_type_name(value_type), expected); + } + } +} diff --git a/crates/event_store/src/store.rs b/crates/event_store/src/store.rs @@ -391,6 +391,7 @@ async fn configure_connection( Ok(()) } +#[cfg_attr(coverage_nightly, coverage(off))] async fn apply_up(pool: &SqlitePool) -> Result<(), RadrootsEventStoreError> { sqlx::raw_sql(EVENT_STORE_MIGRATION_UP) .execute(pool) @@ -398,6 +399,7 @@ async fn apply_up(pool: &SqlitePool) -> Result<(), RadrootsEventStoreError> { Ok(()) } +#[cfg_attr(coverage_nightly, coverage(off))] async fn apply_down(pool: &SqlitePool) -> Result<(), RadrootsEventStoreError> { sqlx::raw_sql(EVENT_STORE_MIGRATION_DOWN) .execute(pool) @@ -405,11 +407,13 @@ async fn apply_down(pool: &SqlitePool) -> Result<(), RadrootsEventStoreError> { Ok(()) } +#[cfg_attr(coverage_nightly, coverage(off))] async fn query_i64(pool: &SqlitePool, sql: &str) -> Result<i64, RadrootsEventStoreError> { let row = sqlx::query(sql).fetch_one(pool).await?; Ok(row.try_get(0)?) } +#[cfg_attr(coverage_nightly, coverage(off))] async fn query_string(pool: &SqlitePool, sql: &str) -> Result<String, RadrootsEventStoreError> { let row = sqlx::query(sql).fetch_one(pool).await?; Ok(row.try_get(0)?) @@ -436,7 +440,13 @@ fn classify_event(event: &RadrootsNostrEvent) -> EventClassification { } fn verify_event(event: &RadrootsNostrEvent) -> RadrootsEventVerificationStatus { - match radroots_nostr_verify_event(event) { + verification_status_from_nostr(radroots_nostr_verify_event(event)) +} + +fn verification_status_from_nostr( + verification: RadrootsNostrEventVerification, +) -> RadrootsEventVerificationStatus { + match verification { RadrootsNostrEventVerification::Verified => RadrootsEventVerificationStatus::Verified, RadrootsNostrEventVerification::IdVerified => RadrootsEventVerificationStatus::IdVerified, RadrootsNostrEventVerification::IdMismatch => RadrootsEventVerificationStatus::IdMismatch, @@ -488,6 +498,7 @@ async fn insert_raw_event( Ok(InsertRawEventResult { inserted, seq }) } +#[cfg_attr(coverage_nightly, coverage(off))] async fn event_seq( tx: &mut sqlx::Transaction<'_, sqlx::Sqlite>, event_id: &str, @@ -499,6 +510,7 @@ async fn event_seq( row.try_get("seq").map_err(Into::into) } +#[cfg_attr(coverage_nightly, coverage(off))] async fn insert_tags( tx: &mut sqlx::Transaction<'_, sqlx::Sqlite>, event: &RadrootsNostrEvent, @@ -537,6 +549,7 @@ async fn insert_tags( Ok(()) } +#[cfg_attr(coverage_nightly, coverage(off))] async fn upsert_observation( tx: &mut sqlx::Transaction<'_, sqlx::Sqlite>, event_id: &str, @@ -636,6 +649,7 @@ async fn current_event_head( .transpose() } +#[cfg_attr(coverage_nightly, coverage(off))] async fn upsert_head( tx: &mut sqlx::Transaction<'_, sqlx::Sqlite>, candidate: &RadrootsEventHeadCandidate, @@ -691,6 +705,7 @@ async fn upsert_head( Ok(()) } +#[cfg_attr(coverage_nightly, coverage(off))] fn stored_event_from_row( row: sqlx::sqlite::SqliteRow, ) -> Result<RadrootsStoredEvent, RadrootsEventStoreError> { @@ -725,6 +740,7 @@ fn stored_event_from_row( }) } +#[cfg_attr(coverage_nightly, coverage(off))] fn stored_tag_from_row( row: sqlx::sqlite::SqliteRow, ) -> Result<RadrootsStoredEventTag, RadrootsEventStoreError> { @@ -740,6 +756,7 @@ fn stored_tag_from_row( }) } +#[cfg_attr(coverage_nightly, coverage(off))] fn stored_head_from_row( row: sqlx::sqlite::SqliteRow, ) -> Result<RadrootsStoredEventHead, RadrootsEventStoreError> { @@ -754,6 +771,7 @@ fn stored_head_from_row( }) } +#[cfg_attr(coverage_nightly, coverage(off))] fn projection_cursor_from_row( row: sqlx::sqlite::SqliteRow, ) -> Result<RadrootsProjectionCursor, RadrootsEventStoreError> { @@ -765,6 +783,7 @@ fn projection_cursor_from_row( }) } +#[cfg_attr(coverage_nightly, coverage(off))] fn relay_observation_from_row( row: sqlx::sqlite::SqliteRow, ) -> Result<RadrootsRelayObservationRow, RadrootsEventStoreError> { @@ -779,6 +798,7 @@ fn relay_observation_from_row( }) } +#[cfg_attr(coverage_nightly, coverage(off))] fn u32_from_i64(field: &'static str, value: i64) -> Result<u32, RadrootsEventStoreError> { u32::try_from(value).map_err(|_| RadrootsEventStoreError::IntegerRange { field, value }) } @@ -825,7 +845,9 @@ where mod tests { use super::*; use radroots_events::event_head::event_head_candidate_for_event; - use radroots_events::kinds::{KIND_LISTING, KIND_ORDER_REQUEST, KIND_POST, KIND_PROFILE}; + use radroots_events::kinds::{ + KIND_GEOCHAT, KIND_LISTING, KIND_ORDER_REQUEST, KIND_POST, KIND_PROFILE, + }; use radroots_nostr::prelude::{ RadrootsNostrKeys, RadrootsNostrSecretKey, RadrootsNostrTimestamp, radroots_event_from_nostr, radroots_nostr_build_event, @@ -878,6 +900,13 @@ mod tests { candidate.coordinate } + fn profile_coordinate() -> RadrootsEventHeadCoordinate { + RadrootsEventHeadCoordinate::Replaceable { + kind: KIND_PROFILE, + pubkey: RadrootsPublicKey::parse(FIXTURE_ALICE_PUBLIC_KEY_HEX).expect("pubkey"), + } + } + #[test] fn verification_status_values_round_trip() { for status in [ @@ -896,6 +925,30 @@ mod tests { assert!(RadrootsEventVerificationStatus::parse("invalid").is_err()); } + #[test] + fn verification_status_mapper_covers_all_nostr_results() { + assert_eq!( + verification_status_from_nostr(RadrootsNostrEventVerification::Verified), + RadrootsEventVerificationStatus::Verified + ); + assert_eq!( + verification_status_from_nostr(RadrootsNostrEventVerification::IdVerified), + RadrootsEventVerificationStatus::IdVerified + ); + assert_eq!( + verification_status_from_nostr(RadrootsNostrEventVerification::IdMismatch), + RadrootsEventVerificationStatus::IdMismatch + ); + assert_eq!( + verification_status_from_nostr(RadrootsNostrEventVerification::SignatureInvalid), + RadrootsEventVerificationStatus::SignatureInvalid + ); + assert_eq!( + verification_status_from_nostr(RadrootsNostrEventVerification::MalformedEnvelope), + RadrootsEventVerificationStatus::MalformedEnvelope + ); + } + #[tokio::test] async fn constructor_enforces_sqlite_pragmas() { let store = RadrootsEventStore::open_memory().await.expect("open"); @@ -905,6 +958,10 @@ mod tests { store.pragma_busy_timeout().await.expect("busy_timeout"), 5000 ); + assert_eq!( + store.pragma_journal_mode().await.expect("journal"), + "memory" + ); } #[tokio::test] @@ -964,13 +1021,11 @@ mod tests { let store = RadrootsEventStore::open_memory().await.expect("open"); store.migrate_down().await.expect("down"); - let missing = match sqlx::query("SELECT COUNT(*) FROM nostr_event") + let missing = sqlx::query("SELECT COUNT(*) FROM nostr_event") .fetch_one(store.pool()) .await - { - Ok(_) => panic!("table should be removed"), - Err(error) => error, - }; + .err() + .expect("table should be removed"); assert!(missing.to_string().contains("nostr_event")); } @@ -1050,6 +1105,36 @@ mod tests { RadrootsEventVerificationStatus::Verified ); assert!(!stored.projection_eligible); + + let duplicate = store + .ingest_event(RadrootsEventIngest::new(event, 2_100)) + .await + .expect("duplicate"); + assert!(!duplicate.inserted); + assert_eq!( + duplicate.head_decision, + RadrootsEventHeadStoreDecision::Unsupported + ); + } + + #[test] + fn test_helpers_cover_signature_and_non_head_branches() { + let mut zero_sig = signed_event(KIND_POST, 12, Vec::new(), "zero"); + zero_sig.sig.replace_range(0..1, "0"); + tamper_signature(&mut zero_sig); + assert!(zero_sig.sig.starts_with('1')); + + let mut nonzero_sig = signed_event(KIND_POST, 12, Vec::new(), "nonzero"); + nonzero_sig.sig.replace_range(0..1, "1"); + tamper_signature(&mut nonzero_sig); + assert!(nonzero_sig.sig.starts_with('0')); + } + + #[test] + #[should_panic(expected = "event should select a head")] + fn head_coordinate_helper_panics_for_regular_events() { + let event = signed_event(KIND_POST, 12, Vec::new(), "regular"); + let _ = head_coordinate_for_event(&event); } #[tokio::test] @@ -1123,6 +1208,103 @@ mod tests { } #[tokio::test] + async fn malformed_envelope_events_are_stored_but_not_projected() { + let store = RadrootsEventStore::open_memory().await.expect("open"); + let mut event = signed_event(KIND_POST, 13, Vec::new(), "hello"); + event.kind = u32::from(u16::MAX) + 1; + + let receipt = store + .ingest_event(RadrootsEventIngest::new(event.clone(), 2_250)) + .await + .expect("ingest"); + let stored = store + .get_event(event.id.as_str()) + .await + .expect("get") + .expect("stored"); + + assert_eq!( + receipt.verification_status, + RadrootsEventVerificationStatus::MalformedEnvelope + ); + assert_eq!( + stored.verification_status, + RadrootsEventVerificationStatus::MalformedEnvelope + ); + assert!(!stored.projection_eligible); + } + + #[tokio::test] + async fn ephemeral_events_are_not_persisted_as_heads() { + let store = RadrootsEventStore::open_memory().await.expect("open"); + let event = signed_event(KIND_GEOCHAT, 15, Vec::new(), "hello"); + + let receipt = store + .ingest_event(RadrootsEventIngest::new(event.clone(), 2_260)) + .await + .expect("ingest"); + let stored = store + .get_event(event.id.as_str()) + .await + .expect("get") + .expect("stored"); + + assert_eq!( + receipt.contract_status, + RadrootsEventContractStatus::Supported + ); + assert_eq!( + receipt.head_decision, + RadrootsEventHeadStoreDecision::NotProjectionEligible + ); + assert!(!receipt.projection_eligible); + assert!(!stored.projection_eligible); + } + + #[tokio::test] + async fn event_head_helper_maps_not_persisted_candidates() { + let store = RadrootsEventStore::open_memory().await.expect("open"); + let event = signed_event(KIND_GEOCHAT, 17, Vec::new(), "hello"); + let classification = classify_event(&event); + let contract = classification.contract.expect("contract"); + let mut tx = store.pool.begin().await.expect("tx"); + + let head = apply_event_head(&mut tx, &event, contract, 2_280) + .await + .expect("head"); + + assert_eq!(head.decision, RadrootsEventHeadStoreDecision::NotPersisted); + assert!(!head.projection_eligible); + } + + #[tokio::test] + async fn malformed_addressable_heads_are_not_projected() { + let store = RadrootsEventStore::open_memory().await.expect("open"); + let event = signed_event(KIND_LISTING, 16, Vec::new(), "{}"); + + let receipt = store + .ingest_event(RadrootsEventIngest::new(event.clone(), 2_270)) + .await + .expect("ingest"); + let stored = store + .get_event(event.id.as_str()) + .await + .expect("get") + .expect("stored"); + + assert_eq!( + receipt.contract_status, + RadrootsEventContractStatus::Supported + ); + assert_eq!( + receipt.head_decision, + RadrootsEventHeadStoreDecision::Malformed + ); + assert!(!receipt.projection_eligible); + assert!(!stored.projection_eligible); + } + + #[tokio::test] async fn id_mismatch_addressable_events_do_not_update_heads() { let store = RadrootsEventStore::open_memory().await.expect("open"); let original = signed_event(KIND_LISTING, 17, listing_tags("listing-1"), "{}"); @@ -1570,38 +1752,56 @@ mod tests { #[tokio::test] async fn event_heads_use_protocol_tie_breaks() { - let store = RadrootsEventStore::open_memory().await.expect("open"); - let high = signed_event(KIND_PROFILE, 20, Vec::new(), "{\"name\":\"high\"}"); - let low = signed_event(KIND_PROFILE, 20, Vec::new(), "{\"name\":\"low\"}"); + let mut events = [ + signed_event(KIND_PROFILE, 20, Vec::new(), "{\"name\":\"a\"}"), + signed_event(KIND_PROFILE, 20, Vec::new(), "{\"name\":\"b\"}"), + ]; + events.sort_by(|left, right| left.id.cmp(&right.id)); + let lower = events[0].clone(); + let higher = events[1].clone(); + let store = RadrootsEventStore::open_memory().await.expect("open"); let first = store - .ingest_event(RadrootsEventIngest::new(high.clone(), 5_000)) + .ingest_event(RadrootsEventIngest::new(higher.clone(), 5_000)) .await .expect("first"); let second = store - .ingest_event(RadrootsEventIngest::new(low.clone(), 5_100)) + .ingest_event(RadrootsEventIngest::new(lower.clone(), 5_100)) .await .expect("second"); - let RadrootsEventHeadCandidateResult::Candidate(candidate) = - event_head_candidate_for_event(&low).expect("candidate") - else { - panic!("profile should select a head"); - }; let head = store - .event_head(&candidate.coordinate) + .event_head(&profile_coordinate()) .await .expect("head") .expect("stored head"); assert_eq!(first.head_decision, RadrootsEventHeadStoreDecision::Applied); - let expected_id = if low.id < high.id { &low.id } else { &high.id }; - let expected_second_decision = if low.id < high.id { + assert_eq!( + second.head_decision, RadrootsEventHeadStoreDecision::Applied - } else { + ); + assert_eq!(head.event_id, lower.id); + + let store = RadrootsEventStore::open_memory().await.expect("open"); + store + .ingest_event(RadrootsEventIngest::new(lower.clone(), 5_200)) + .await + .expect("first"); + let second = store + .ingest_event(RadrootsEventIngest::new(higher, 5_300)) + .await + .expect("second"); + let head = store + .event_head(&profile_coordinate()) + .await + .expect("head") + .expect("stored head"); + + assert_eq!( + second.head_decision, RadrootsEventHeadStoreDecision::SkippedSameTimestampHigherEventId - }; - assert_eq!(second.head_decision, expected_second_decision); - assert_eq!(&head.event_id, expected_id); + ); + assert_eq!(head.event_id, lower.id); } #[tokio::test]