lib

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

commit 56391a96b622bc5d948a10678bc0c95f7e522a7b
parent 13f6e8a926429c9adcfc4057380e1e2de1ae9fe5
Author: triesap <tyson@radroots.org>
Date:   Fri,  6 Mar 2026 19:20:31 +0000

replica-sync: add sync status error-path coverage tests

- add sync_state tests for farm query, emit, content hash, and state query failure branches
- expose a test-only event_content_hash_fail_next helper for deterministic branch targeting
- make event_state failpoint thread-local to avoid cross-test interference under parallel execution
- run cargo check and cargo test for radroots-replica-sync after formatting updated files

Diffstat:
Mcrates/replica-sync/src/event_state.rs | 19+++++++++++++++----
Mcrates/replica-sync/src/sync_state.rs | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 98 insertions(+), 6 deletions(-)

diff --git a/crates/replica-sync/src/event_state.rs b/crates/replica-sync/src/event_state.rs @@ -15,16 +15,22 @@ use crate::error::RadrootsReplicaEventsError; #[cfg(test)] mod failpoints { - use core::sync::atomic::{AtomicBool, Ordering}; + use std::cell::Cell; - static FORCE_ERROR: AtomicBool = AtomicBool::new(false); + thread_local! { + static FORCE_ERROR: Cell<bool> = const { Cell::new(false) }; + } pub(crate) fn set_error() { - FORCE_ERROR.store(true, Ordering::SeqCst); + FORCE_ERROR.with(|flag| flag.set(true)); } pub(crate) fn take_error() -> bool { - FORCE_ERROR.swap(false, Ordering::SeqCst) + FORCE_ERROR.with(|flag| { + let value = flag.get(); + flag.set(false); + value + }) } } @@ -54,6 +60,11 @@ pub fn event_content_hash( Ok(hex::encode(hasher.finalize())) } +#[cfg(test)] +pub(crate) fn event_content_hash_fail_next() { + failpoints::set_error(); +} + pub fn tag_value<'a>(tags: &'a [Vec<String>], key: &str) -> Option<&'a str> { tags.iter() .find(|tag| tag.get(0).map(|v| v.as_str()) == Some(key)) diff --git a/crates/replica-sync/src/sync_state.rs b/crates/replica-sync/src/sync_state.rs @@ -72,12 +72,15 @@ pub fn radroots_replica_sync_status<E: SqlExecutor>( mod tests { use super::radroots_replica_sync_status; use crate::emit::radroots_replica_sync_all_with_options; - use crate::event_state::{event_content_hash, event_state_key, tag_value}; + use crate::error::RadrootsReplicaEventsError; + use crate::event_state::{ + event_content_hash, event_content_hash_fail_next, event_state_key, tag_value, + }; use crate::types::RadrootsReplicaFarmSelector; use radroots_replica_db::{farm, migrations, nostr_event_state}; use radroots_replica_db_schema::farm::IFarmFields; use radroots_replica_db_schema::nostr_event_state::INostrEventStateFields; - use radroots_sql_core::SqliteExecutor; + use radroots_sql_core::{SqlExecutor, SqliteExecutor}; #[test] fn sync_status_empty_db_is_zero() { @@ -139,4 +142,82 @@ mod tests { assert_eq!(status.expected_count, expected_count); assert_eq!(status.pending_count, expected_count.saturating_sub(1)); } + + #[test] + fn sync_status_reports_farm_query_errors() { + let exec = SqliteExecutor::open_memory().expect("db"); + let err = radroots_replica_sync_status(&exec).expect_err("farm query error"); + assert!(err.to_string().contains("invalid query")); + } + + #[test] + fn sync_status_reports_emit_errors() { + let exec = SqliteExecutor::open_memory().expect("db"); + migrations::run_all_up(&exec).expect("migrations"); + let _ = farm::create( + &exec, + &IFarmFields { + d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), + pubkey: "b".repeat(64), + name: "farm".to_string(), + about: None, + website: None, + picture: None, + banner: None, + location_primary: None, + location_city: None, + location_region: None, + location_country: None, + }, + ) + .expect("farm"); + let _ = exec + .exec("DROP TABLE farm_tag;", "[]") + .expect("drop farm_tag"); + let err = radroots_replica_sync_status(&exec).expect_err("emit error"); + assert!(err.to_string().contains("invalid query")); + } + + #[test] + fn sync_status_reports_content_hash_errors() { + let exec = SqliteExecutor::open_memory().expect("db"); + migrations::run_all_up(&exec).expect("migrations"); + let _ = farm::create( + &exec, + &IFarmFields { + d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), + pubkey: "c".repeat(64), + name: "farm".to_string(), + about: None, + website: None, + picture: None, + banner: None, + location_primary: None, + location_city: None, + location_region: None, + location_country: None, + }, + ) + .expect("farm"); + event_content_hash_fail_next(); + let err = radroots_replica_sync_status(&exec).expect_err("content hash error"); + assert!( + matches!( + err, + RadrootsReplicaEventsError::InvalidData(ref field) if field == "content_hash" + ), + "{err:?}" + ); + } + + #[test] + fn sync_status_reports_state_query_errors() { + let exec = SqliteExecutor::open_memory().expect("db"); + migrations::run_all_up(&exec).expect("migrations"); + let _ = exec + .exec("DROP TABLE nostr_event_state;", "[]") + .expect("drop nostr_event_state"); + let err = radroots_replica_sync_status(&exec).expect_err("state query error"); + assert!(err.to_string().contains("invalid query")); + } }