rhi

Coordinated trade for connected markets
git clone https://radroots.dev/git/rhi.git
Log | Files | Refs | README | LICENSE

commit 7af45e4af4bf0036738e64a40850ac6e026e1faa
parent 88a183dd6bbecf946c6c8593714cff7567e8fc5d
Author: triesap <tyson@radroots.org>
Date:   Tue,  3 Mar 2026 19:09:28 +0000

tests: expand adapter and runtime branch coverage

Diffstat:
Msrc/adapters/nostr/event.rs | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/features/trade_listing/state.rs | 25++++++++++++++++++++++++-
Msrc/main.rs | 53++++++++++++++++++++++++++++++++++++++++++++++++++++-
3 files changed, 139 insertions(+), 2 deletions(-)

diff --git a/src/adapters/nostr/event.rs b/src/adapters/nostr/event.rs @@ -76,3 +76,66 @@ impl JobEventLike for NostrEventAdapter<'_> { self.evt.sig.to_string() } } + +#[cfg(test)] +mod tests { + use super::NostrEventAdapter; + use radroots_events_codec::job::traits::{JobEventBorrow, JobEventLike}; + use radroots_nostr::prelude::{ + RadrootsNostrEvent, RadrootsNostrEventBuilder, RadrootsNostrKind, RadrootsNostrKeys, + RadrootsNostrTag, RadrootsNostrTagKind, + }; + + fn build_event( + keys: &RadrootsNostrKeys, + kind: RadrootsNostrKind, + tags: Vec<RadrootsNostrTag>, + ) -> RadrootsNostrEvent { + RadrootsNostrEventBuilder::new(kind, "content") + .tags(tags) + .sign_with_keys(keys) + .expect("event must sign") + } + + #[test] + fn adapter_exposes_borrow_and_owned_fields_for_custom_kind() { + let keys = RadrootsNostrKeys::generate(); + let recipient = RadrootsNostrKeys::generate(); + let recipient_hex = recipient.public_key().to_hex(); + let tags = vec![RadrootsNostrTag::custom( + RadrootsNostrTagKind::p(), + vec![recipient_hex.clone()], + )]; + let event = build_event(&keys, RadrootsNostrKind::Custom(5322), tags); + let adapter = NostrEventAdapter::new(&event); + + assert_eq!(JobEventBorrow::raw_id(&adapter), event.id.to_hex()); + assert_eq!(JobEventBorrow::raw_author(&adapter), event.pubkey.to_string()); + assert_eq!(JobEventBorrow::raw_content(&adapter), "content"); + assert_eq!(JobEventBorrow::raw_kind(&adapter), 5322); + + assert_eq!(JobEventLike::raw_id(&adapter), event.id.to_hex()); + assert_eq!(JobEventLike::raw_author(&adapter), event.pubkey.to_string()); + assert_eq!(JobEventLike::raw_content(&adapter), "content".to_string()); + assert_eq!(JobEventLike::raw_kind(&adapter), 5322); + assert_eq!( + JobEventLike::raw_tags(&adapter), + vec![vec!["p".to_string(), recipient_hex]] + ); + assert_eq!(JobEventLike::raw_sig(&adapter), event.sig.to_string()); + } + + #[test] + fn adapter_maps_non_custom_kind_to_zero() { + let keys = RadrootsNostrKeys::generate(); + let event = build_event(&keys, RadrootsNostrKind::TextNote, Vec::new()); + let adapter = NostrEventAdapter::new(&event); + + assert_eq!(JobEventBorrow::raw_kind(&adapter), 0); + assert_eq!(JobEventLike::raw_kind(&adapter), 0); + assert_eq!( + JobEventLike::raw_published_at(&adapter), + event.created_at.as_secs() as u32 + ); + } +} diff --git a/src/features/trade_listing/state.rs b/src/features/trade_listing/state.rs @@ -78,7 +78,7 @@ impl std::error::Error for TradeListingStateError {} #[cfg(test)] mod tests { - use super::{TradeListingState, TradeOrderState}; + use super::{TradeListingState, TradeListingStateError, TradeOrderState}; use radroots_trade::listing::order::TradeOrderStatus; #[test] @@ -101,4 +101,27 @@ mod tests { assert!(state.mark_event_seen("order-1", "evt")); assert!(state.is_event_seen("order-1", "evt")); } + + #[test] + fn state_covers_missing_order_paths_and_error_display() { + let mut state = TradeListingState::default(); + assert!(!state.order_exists("missing")); + assert!(state.get_order_mut("missing").is_none()); + assert!(!state.mark_event_seen("missing", "evt-1")); + assert!(!state.is_event_seen("missing", "evt-1")); + + assert_eq!( + TradeListingStateError::MissingOrder.to_string(), + "missing order state" + ); + + let invalid = TradeListingStateError::InvalidTransition { + from: TradeOrderStatus::Requested, + to: TradeOrderStatus::Completed, + }; + assert_eq!( + invalid.to_string(), + "invalid order transition: Requested -> Completed" + ); + } } diff --git a/src/main.rs b/src/main.rs @@ -5,7 +5,11 @@ use tracing::info; #[tokio::main] async fn main() -> ExitCode { - match run().await { + exit_code_from_run(run().await) +} + +fn exit_code_from_run(result: Result<()>) -> ExitCode { + match result { Ok(()) => ExitCode::SUCCESS, Err(err) => { tracing::error!(error = ?err, "Fatal error"); @@ -28,3 +32,50 @@ async fn run() -> Result<()> { run_rhi(&settings, &args).await } + +#[cfg(test)] +mod tests { + use super::{exit_code_from_run, run_rhi}; + use rhi::{cli_args, config}; + use std::path::PathBuf; + use std::process::ExitCode; + + fn minimal_settings() -> config::Settings { + config::Settings { + metadata: serde_json::from_str(r#"{"name":"rhi-test"}"#).expect("metadata"), + config: config::Configuration { + service: radroots_runtime::RadrootsNostrServiceConfig { + logs_dir: "logs".to_string(), + relays: Vec::new(), + nip89_identifier: Some("rhi".to_string()), + nip89_extra_tags: Vec::new(), + }, + subscriber: config::SubscriberConfig::default(), + }, + } + } + + #[test] + fn exit_code_from_run_maps_success_and_error() { + assert_eq!(exit_code_from_run(Ok(())), ExitCode::SUCCESS); + assert_eq!( + exit_code_from_run(Err(anyhow::anyhow!("boom"))), + ExitCode::FAILURE + ); + } + + #[tokio::test] + async fn run_rhi_returns_error_when_identity_is_missing() { + let args = cli_args { + service: radroots_runtime::RadrootsServiceCliArgs { + config: PathBuf::from("config.toml"), + identity: Some(PathBuf::from("/tmp/rhi-missing-identity.json")), + allow_generate_identity: false, + }, + }; + let settings = minimal_settings(); + let err = run_rhi(&settings, &args).await.expect_err("identity should fail"); + let msg = format!("{err:#}"); + assert!(msg.contains("identity") || msg.contains("not found")); + } +}