commit 8b9c6547686f77d88e62dce121c0c89af8d9b6b3
parent e3aacd796e728b7549c463ad6995cf38cea2d984
Author: triesap <tyson@radroots.org>
Date: Sat, 21 Feb 2026 23:56:27 +0000
tests: add list_set and listing edge coverage and tighten discount cfg usage
Diffstat:
3 files changed, 180 insertions(+), 9 deletions(-)
diff --git a/crates/events-codec/src/coop/list_sets.rs b/crates/events-codec/src/coop/list_sets.rs
@@ -181,3 +181,53 @@ where
image: None,
})
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn coop_list_set_id_validates_suffix_and_coop_id() {
+ let err = coop_list_set_id("AAAAAAAAAAAAAAAAAAAAAQ", " ")
+ .expect_err("expected suffix validation error");
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("list_set_suffix")
+ ));
+
+ let err = coop_list_set_id(" ", "members").expect_err("expected coop_id validation error");
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("coop_id")
+ ));
+ }
+
+ #[test]
+ fn list_entries_rejects_blank_values() {
+ let err = list_entries("p", [" "]).expect_err("expected blank entry error");
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("entry.values")
+ ));
+ }
+
+ #[test]
+ fn farm_address_rejects_empty_and_invalid_d_tag() {
+ let err = farm_address(&RadrootsFarmRef {
+ pubkey: "58e318557257f2ab58a415d21bb57082b4824cf667a1d64e72bcbc5acc018c62".to_string(),
+ d_tag: " ".to_string(),
+ })
+ .expect_err("expected empty d_tag error");
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("farm.d_tag")
+ ));
+
+ let err = farm_address(&RadrootsFarmRef {
+ pubkey: "58e318557257f2ab58a415d21bb57082b4824cf667a1d64e72bcbc5acc018c62".to_string(),
+ d_tag: "invalid".to_string(),
+ })
+ .expect_err("expected invalid d_tag error");
+ assert!(matches!(err, EventEncodeError::InvalidField("farm.d_tag")));
+ }
+}
diff --git a/crates/events-codec/src/list_set/mod.rs b/crates/events-codec/src/list_set/mod.rs
@@ -81,4 +81,48 @@ mod tests {
.expect_err("expected invalid d_tag");
assert!(matches!(err, EventParseError::InvalidTag("d")));
}
+
+ #[test]
+ fn list_set_accepts_resource_base64_d_tag() {
+ let list = RadrootsListSet {
+ d_tag: "resource:AAAAAAAAAAAAAAAAAAAAAA:members".to_string(),
+ content: "".to_string(),
+ entries: vec![RadrootsListEntry {
+ tag: "p".to_string(),
+ values: vec!["pubkey".to_string()],
+ }],
+ title: None,
+ description: None,
+ image: None,
+ };
+ let tags = list_set_build_tags(&list).expect("build tags");
+ let parsed = list_set_from_tags(KIND_LIST_SET_FOLLOW, list.content.clone(), &tags)
+ .expect("parse list set");
+ assert_eq!(parsed.d_tag, list.d_tag);
+ }
+
+ #[test]
+ fn list_set_rejects_empty_prefixed_id_or_suffix() {
+ let list = RadrootsListSet {
+ d_tag: "farm::members".to_string(),
+ content: "".to_string(),
+ entries: vec![RadrootsListEntry {
+ tag: "p".to_string(),
+ values: vec!["pubkey".to_string()],
+ }],
+ title: None,
+ description: None,
+ image: None,
+ };
+ let err = list_set_build_tags(&list).expect_err("expected invalid d_tag");
+ assert!(matches!(err, EventEncodeError::InvalidField("d_tag")));
+
+ let tags = vec![
+ vec!["d".to_string(), "coop:AAAAAAAAAAAAAAAAAAAAAA:".to_string()],
+ vec!["p".to_string(), "pubkey".to_string()],
+ ];
+ let err = list_set_from_tags(KIND_LIST_SET_FOLLOW, "".to_string(), &tags)
+ .expect_err("expected invalid d_tag");
+ assert!(matches!(err, EventParseError::InvalidTag("d")));
+ }
}
diff --git a/crates/events-codec/src/listing/tags.rs b/crates/events-codec/src/listing/tags.rs
@@ -10,7 +10,9 @@ use alloc::{
use core::cmp;
-use radroots_core::{RadrootsCoreDiscount, RadrootsCoreMoney};
+#[cfg(any(feature = "serde_json", test))]
+use radroots_core::RadrootsCoreDiscount;
+use radroots_core::RadrootsCoreMoney;
use radroots_events::kinds::{KIND_FARM, KIND_PLOT, KIND_RESOURCE_AREA};
use radroots_events::listing::{
RadrootsListing, RadrootsListingAvailability, RadrootsListingBin,
@@ -27,6 +29,7 @@ use crate::error::EventEncodeError;
const TAG_PRICE: &str = "price";
const TAG_RADROOTS_BIN: &str = "radroots:bin";
const TAG_RADROOTS_PRICE: &str = "radroots:price";
+#[cfg(feature = "serde_json")]
const TAG_RADROOTS_DISCOUNT: &str = "radroots:discount";
const TAG_RADROOTS_PRIMARY_BIN: &str = "radroots:primary_bin";
const TAG_RADROOTS_RESOURCE_AREA: &str = "radroots:resource_area";
@@ -601,6 +604,7 @@ fn clean_value(value: &str) -> Option<String> {
}
}
+#[cfg(any(feature = "serde_json", test))]
fn discount_tag_payload(discount: &RadrootsCoreDiscount) -> Result<String, EventEncodeError> {
#[cfg(feature = "serde_json")]
{
@@ -888,11 +892,30 @@ mod tests {
},
);
assert!(find_tag(&invalid_with_geohash_enabled, "g").is_some());
- assert!(
- !invalid_with_geohash_enabled
- .iter()
- .any(|tag| tag.first().map(|v| v.as_str()) == Some("l"))
+ assert!(!invalid_with_geohash_enabled
+ .iter()
+ .any(|tag| tag.first().map(|v| v.as_str()) == Some("l")));
+
+ let mut no_coordinate_tags = Vec::new();
+ let no_coordinate_location = RadrootsListingLocation {
+ primary: "Test".to_string(),
+ city: None,
+ region: None,
+ country: None,
+ lat: None,
+ lng: None,
+ geohash: None,
+ };
+ push_location_geotags(
+ &mut no_coordinate_tags,
+ &no_coordinate_location,
+ ListingTagOptions {
+ include_geohash: false,
+ include_gps: true,
+ ..ListingTagOptions::default()
+ },
);
+ assert!(find_tag(&no_coordinate_tags, "l").is_none());
}
#[test]
@@ -1132,14 +1155,16 @@ mod tests {
}]);
#[cfg(feature = "serde_json")]
{
- let tags = listing_tags_with_options(&listing_with_discount, ListingTagOptions::default())
- .expect("discount serialization works");
+ let tags =
+ listing_tags_with_options(&listing_with_discount, ListingTagOptions::default())
+ .expect("discount serialization works");
assert!(find_tag(&tags, "radroots:discount").is_some());
}
#[cfg(not(feature = "serde_json"))]
{
- let err = listing_tags_with_options(&listing_with_discount, ListingTagOptions::default())
- .expect_err("discount serialization requires serde_json");
+ let err =
+ listing_tags_with_options(&listing_with_discount, ListingTagOptions::default())
+ .expect_err("discount serialization requires serde_json");
assert!(matches!(err, EventEncodeError::Json));
}
@@ -1204,6 +1229,23 @@ mod tests {
assert!(find_tag(&no_availability_tags, "published_at").is_none());
assert!(find_tag(&no_availability_tags, "expires_at").is_none());
+ let mut empty_window_availability = base_listing();
+ empty_window_availability.discounts = None;
+ empty_window_availability.availability = Some(RadrootsListingAvailability::Window {
+ start: None,
+ end: None,
+ });
+ let empty_window_tags = listing_tags_with_options(
+ &empty_window_availability,
+ ListingTagOptions {
+ include_availability: true,
+ ..ListingTagOptions::default()
+ },
+ )
+ .expect("availability window without bounds");
+ assert!(find_tag(&empty_window_tags, "published_at").is_none());
+ assert!(find_tag(&empty_window_tags, "expires_at").is_none());
+
let mut no_delivery = base_listing();
no_delivery.discounts = None;
no_delivery.delivery_method = None;
@@ -1403,6 +1445,25 @@ mod tests {
.expect("first bin tag");
assert_eq!(first_bin.get(1).map(|v| v.as_str()), Some("bin-1"));
assert_eq!(first_bin.get(6).map(|v| v.as_str()), Some("fallback-label"));
+
+ let mut listing_without_primary_match = base_listing();
+ listing_without_primary_match.discounts = None;
+ let mut first = base_bin();
+ first.bin_id = "bin-2".to_string();
+ first.display_label = None;
+ first.quantity = RadrootsCoreQuantity::new(decimal("1000"), RadrootsCoreUnit::MassG);
+ let mut second = base_bin();
+ second.bin_id = "bin-1".to_string();
+ listing_without_primary_match.primary_bin_id = "bin-missing".to_string();
+ listing_without_primary_match.bins = vec![first, second];
+
+ let tags = listing_tags(&listing_without_primary_match).expect("listing tags");
+ let first_bin = tags
+ .iter()
+ .find(|tag| tag.first().map(|v| v.as_str()) == Some("radroots:bin"))
+ .expect("first bin tag");
+ assert_eq!(first_bin.get(1).map(|v| v.as_str()), Some("bin-2"));
+ assert_eq!(first_bin.len(), 6);
}
#[test]
@@ -1425,6 +1486,22 @@ mod tests {
assert_eq!(location.get(1).map(|v| v.as_str()), Some("Moyobamba"));
assert_eq!(location.get(2).map(|v| v.as_str()), Some("San Martin"));
assert_eq!(location.len(), 3);
+
+ listing.location = Some(RadrootsListingLocation {
+ primary: "Moyobamba".to_string(),
+ city: Some("Moyobamba".to_string()),
+ region: Some(" ".to_string()),
+ country: Some("PE".to_string()),
+ lat: Some(-6.03),
+ lng: Some(-76.97),
+ geohash: None,
+ });
+ let tags = listing_tags(&listing).expect("listing tags");
+ let location = find_tag(&tags, "location").expect("location tag");
+ assert_eq!(location.get(1).map(|v| v.as_str()), Some("Moyobamba"));
+ assert_eq!(location.get(2).map(|v| v.as_str()), Some("Moyobamba"));
+ assert_eq!(location.get(3).map(|v| v.as_str()), Some("PE"));
+ assert_eq!(location.len(), 4);
}
#[test]