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:
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"));
+ }
+}