lib

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

commit 130dd1c065d0e4c1b0e6f8f07c19559442c9a875
parent 7a8287d9b8ab4c5fda63f20a849b6b06a868a6b4
Author: triesap <tyson@radroots.org>
Date:   Fri, 10 Apr 2026 06:51:52 +0000

events_codec: cover trade tag parser branches

Diffstat:
Mcrates/events_codec/src/trade/tags.rs | 239++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 226 insertions(+), 13 deletions(-)

diff --git a/crates/events_codec/src/trade/tags.rs b/crates/events_codec/src/trade/tags.rs @@ -240,7 +240,10 @@ mod tests { parse_trade_prev_tag, parse_trade_root_tag, push_trade_chain_tags, trade_envelope_tags, validate_trade_chain, }; - use crate::error::EventEncodeError; + use crate::{ + error::{EventEncodeError, EventParseError}, + job::error::JobParseError, + }; use radroots_events::{ RadrootsNostrEventPtr, kinds::KIND_LISTING, @@ -250,8 +253,15 @@ mod tests { #[test] fn trade_envelope_tags_build_expected_tags() { let listing_addr = format!("{KIND_LISTING}:pubkey:AAAAAAAAAAAAAAAAAAAAAg"); - let tags = trade_envelope_tags("pubkey", &listing_addr, Some("order-1"), None, None, None) - .expect("trade tags"); + let tags = trade_envelope_tags( + "pubkey", + listing_addr.as_str(), + Some("order-1"), + None, + None, + None, + ) + .expect("trade tags"); let expected: Vec<Vec<String>> = vec![ vec![String::from("p"), String::from("pubkey")], vec![String::from("a"), listing_addr], @@ -265,7 +275,7 @@ mod tests { let listing_addr = format!("{KIND_LISTING}:pubkey:AAAAAAAAAAAAAAAAAAAAAg"); let tags = trade_envelope_tags( "buyer", - &listing_addr, + listing_addr.as_str(), Some("order-1"), Some(&RadrootsNostrEventPtr { id: "listing-snapshot".into(), @@ -300,7 +310,7 @@ mod tests { let listing_addr = format!("{KIND_LISTING}:pubkey:AAAAAAAAAAAAAAAAAAAAAg"); let tags = trade_envelope_tags( "buyer", - &listing_addr, + listing_addr.as_str(), None::<&str>, Some(&RadrootsNostrEventPtr { id: "listing-snapshot".into(), @@ -325,10 +335,62 @@ mod tests { } #[test] + fn trade_envelope_tags_accept_str_listing_address() { + let listing_addr = format!("{KIND_LISTING}:pubkey:AAAAAAAAAAAAAAAAAAAAAg"); + let tags = trade_envelope_tags( + "buyer", + listing_addr.as_str(), + Some("order-1"), + None::<&RadrootsNostrEventPtr>, + Some("root-event"), + Some("prev-event"), + ) + .expect("trade tags with str listing address"); + assert_eq!( + tags, + vec![ + vec![String::from("p"), String::from("buyer")], + vec![String::from("a"), listing_addr], + vec![String::from(TAG_D), String::from("order-1")], + vec![String::from(TAG_E_ROOT), String::from("root-event")], + vec![String::from(TAG_E_PREV), String::from("prev-event")], + ] + ); + } + + #[test] + fn trade_envelope_tags_accept_str_listing_address_with_snapshot_only() { + let listing_addr = format!("{KIND_LISTING}:pubkey:AAAAAAAAAAAAAAAAAAAAAg"); + let tags = trade_envelope_tags( + "buyer", + listing_addr.as_str(), + None::<&str>, + Some(&RadrootsNostrEventPtr { + id: "listing-snapshot".into(), + relays: None, + }), + None, + None, + ) + .expect("trade tags with str listing address and snapshot only"); + assert_eq!( + tags, + vec![ + vec![String::from("p"), String::from("buyer")], + vec![String::from("a"), listing_addr], + vec![ + String::from(TAG_LISTING_EVENT), + String::from("listing-snapshot"), + ], + ] + ); + } + + #[test] fn trade_envelope_tags_reject_empty_required_fields() { let listing_addr = format!("{KIND_LISTING}:pubkey:AAAAAAAAAAAAAAAAAAAAAg"); - let err = trade_envelope_tags(" ", &listing_addr, None::<&str>, None, None, None) + let err = trade_envelope_tags(" ", listing_addr.as_str(), None::<&str>, None, None, None) .expect_err("blank recipient"); assert!(matches!( err, @@ -342,7 +404,7 @@ mod tests { EventEncodeError::EmptyRequiredField("listing_addr") )); - let err = trade_envelope_tags("buyer", &listing_addr, Some(" "), None, None, None) + let err = trade_envelope_tags("buyer", listing_addr.as_str(), Some(" "), None, None, None) .expect_err("blank order id"); assert!(matches!( err, @@ -351,7 +413,7 @@ mod tests { let err = trade_envelope_tags( "buyer", - &listing_addr, + listing_addr.as_str(), None::<&str>, Some(&RadrootsNostrEventPtr { id: " ".into(), @@ -368,7 +430,7 @@ mod tests { let err = trade_envelope_tags( "buyer", - &listing_addr, + listing_addr.as_str(), None::<&str>, Some(&RadrootsNostrEventPtr { id: "listing-snapshot".into(), @@ -383,15 +445,29 @@ mod tests { EventEncodeError::EmptyRequiredField("listing_event.relays") )); - let err = trade_envelope_tags("buyer", &listing_addr, None::<&str>, None, Some(" "), None) - .expect_err("blank root event id"); + let err = trade_envelope_tags( + "buyer", + listing_addr.as_str(), + None::<&str>, + None, + Some(" "), + None, + ) + .expect_err("blank root event id"); assert!(matches!( err, EventEncodeError::EmptyRequiredField("root_event_id") )); - let err = trade_envelope_tags("buyer", &listing_addr, None::<&str>, None, None, Some(" ")) - .expect_err("blank prev event id"); + let err = trade_envelope_tags( + "buyer", + listing_addr.as_str(), + None::<&str>, + None, + None, + Some(" "), + ) + .expect_err("blank prev event id"); assert!(matches!( err, EventEncodeError::EmptyRequiredField("prev_event_id") @@ -432,6 +508,77 @@ mod tests { } #[test] + fn trade_envelope_tag_parsers_cover_missing_and_invalid_context() { + assert_eq!( + parse_trade_listing_event_tag(&[]).expect("no snapshot"), + None + ); + assert_eq!(parse_trade_root_tag(&[]).expect("no root"), None); + assert_eq!(parse_trade_prev_tag(&[]).expect("no prev"), None); + + assert!(matches!( + parse_trade_counterparty_tag(&[]), + Err(EventParseError::MissingTag("p")) + )); + assert!(matches!( + parse_trade_counterparty_tag(&[vec![String::from("p")]]), + Err(EventParseError::InvalidTag("p")) + )); + assert!(matches!( + parse_trade_counterparty_tag(&[vec![String::from("p"), String::from(" ")]]), + Err(EventParseError::InvalidTag("p")) + )); + + assert!(matches!( + parse_trade_listing_event_tag(&[vec![String::from(TAG_LISTING_EVENT)]]), + Err(EventParseError::InvalidTag(TAG_LISTING_EVENT)) + )); + assert!(matches!( + parse_trade_listing_event_tag(&[vec![ + String::from(TAG_LISTING_EVENT), + String::from(" "), + ]]), + Err(EventParseError::InvalidTag(TAG_LISTING_EVENT)) + )); + assert!(matches!( + parse_trade_listing_event_tag(&[vec![ + String::from(TAG_LISTING_EVENT), + String::from("snapshot"), + String::from(" "), + ]]), + Err(EventParseError::InvalidTag(TAG_LISTING_EVENT)) + )); + assert_eq!( + parse_trade_listing_event_tag(&[vec![ + String::from(TAG_LISTING_EVENT), + String::from("snapshot"), + ]]) + .expect("snapshot without relay"), + Some(RadrootsNostrEventPtr { + id: "snapshot".into(), + relays: None, + }) + ); + + assert!(matches!( + parse_trade_root_tag(&[vec![String::from(TAG_E_ROOT)]]), + Err(EventParseError::InvalidTag(TAG_E_ROOT)) + )); + assert!(matches!( + parse_trade_root_tag(&[vec![String::from(TAG_E_ROOT), String::from(" ")]]), + Err(EventParseError::InvalidTag(TAG_E_ROOT)) + )); + assert!(matches!( + parse_trade_prev_tag(&[vec![String::from(TAG_E_PREV)]]), + Err(EventParseError::InvalidTag(TAG_E_PREV)) + )); + assert!(matches!( + parse_trade_prev_tag(&[vec![String::from(TAG_E_PREV), String::from(" ")]]), + Err(EventParseError::InvalidTag(TAG_E_PREV)) + )); + } + + #[test] fn push_trade_chain_tags_adds_root_prev_and_trade_id() { let mut tags = Vec::new(); push_trade_chain_tags(&mut tags, "root", Some("prev"), Some("trade")); @@ -446,6 +593,16 @@ mod tests { } #[test] + fn push_trade_chain_tags_supports_root_only() { + let mut tags = Vec::new(); + push_trade_chain_tags(&mut tags, "root", None::<&str>, None::<&str>); + assert_eq!( + tags, + vec![vec![String::from(TAG_E_ROOT), String::from("root")]] + ); + } + + #[test] fn validate_trade_chain_requires_root_and_trade_id() { let ok = vec![ vec![String::from(TAG_E_ROOT), String::from("root")], @@ -455,4 +612,60 @@ mod tests { let missing = vec![vec![String::from(TAG_D), String::from("trade")]]; assert!(validate_trade_chain(&missing).is_err()); } + + #[test] + fn validate_trade_chain_rejects_invalid_tag_shapes_and_missing_trade_id() { + let root_only = vec![vec![String::from(TAG_E_ROOT), String::from("root")]]; + assert!(matches!( + validate_trade_chain(&root_only), + Err(JobParseError::MissingChainTag(TAG_D)) + )); + + let invalid_root_shape = vec![vec![String::from(TAG_E_ROOT)]]; + assert!(matches!( + validate_trade_chain(&invalid_root_shape), + Err(JobParseError::InvalidTag(TAG_E_ROOT)) + )); + + let invalid_root_value = vec![ + vec![String::from(TAG_E_ROOT), String::from(" ")], + vec![String::from(TAG_D), String::from("trade")], + ]; + assert!(matches!( + validate_trade_chain(&invalid_root_value), + Err(JobParseError::InvalidTag(TAG_E_ROOT)) + )); + + let invalid_trade_shape = vec![ + vec![String::from(TAG_E_ROOT), String::from("root")], + vec![String::from(TAG_D)], + ]; + assert!(matches!( + validate_trade_chain(&invalid_trade_shape), + Err(JobParseError::InvalidTag(TAG_D)) + )); + + let invalid_trade_value = vec![ + vec![String::from(TAG_E_ROOT), String::from("root")], + vec![String::from(TAG_D), String::from(" ")], + ]; + assert!(matches!( + validate_trade_chain(&invalid_trade_value), + Err(JobParseError::InvalidTag(TAG_D)) + )); + + let with_unrelated_tag = vec![ + vec![String::from("x"), String::from("ignored")], + vec![String::from(TAG_E_ROOT), String::from("root")], + vec![String::from(TAG_D), String::from("trade")], + ]; + assert!(validate_trade_chain(&with_unrelated_tag).is_ok()); + + let with_singleton_unrelated_tag = vec![ + vec![String::from("x")], + vec![String::from(TAG_E_ROOT), String::from("root")], + vec![String::from(TAG_D), String::from("trade")], + ]; + assert!(validate_trade_chain(&with_singleton_unrelated_tag).is_ok()); + } }