commit fae944f17be1400a5e0db3d3fd4e4e829d73c2ff
parent 3ffa691253ad594661a34c55e8965829fdf4ed71
Author: triesap <tyson@radroots.org>
Date: Sun, 21 Jun 2026 20:20:27 +0000
events-codec: expand repost coverage
- Cover repost event targets without relay or author tags plus invalid target, id, and author cases.
- Add generic repost event-target coverage, invalid k-tag parsing, target mismatches, malformed refs, and external-target rejection.
- Exercise generic repost address and event branch behavior through public encode/decode APIs.
- Validate repost integration tests, full radroots_events_codec tests, crate check, diff check, and refreshed coverage run.
Diffstat:
1 file changed, 225 insertions(+), 1 deletion(-)
diff --git a/crates/events_codec/tests/repost.rs b/crates/events_codec/tests/repost.rs
@@ -1,7 +1,7 @@
#![cfg(feature = "serde_json")]
use radroots_events::{
- kinds::{KIND_ARTICLE, KIND_GENERIC_REPOST, KIND_POST, KIND_REPOST},
+ kinds::{KIND_ARTICLE, KIND_GENERIC_REPOST, KIND_POST, KIND_REACTION, KIND_REPOST},
repost::{RadrootsGenericRepost, RadrootsRepost},
social::RadrootsSocialTarget,
tags::{TAG_A, TAG_E, TAG_K, TAG_P},
@@ -58,6 +58,14 @@ fn has_tag(tags: &[Vec<String>], key: &str, value: &str) -> bool {
})
}
+fn replace_tag_value(tags: &mut [Vec<String>], key: &str, value: &str) {
+ let tag = tags
+ .iter_mut()
+ .find(|tag| tag.first().map(|entry| entry.as_str()) == Some(key))
+ .expect("tag");
+ tag[1] = value.to_string();
+}
+
#[test]
fn repost_to_wire_parts_roundtrips_kind_one_target() {
let repost = note_repost();
@@ -150,6 +158,222 @@ fn repost_codecs_reject_wrong_kind_and_wrong_target_kind() {
}
#[test]
+fn repost_event_target_codecs_cover_optional_and_error_edges() {
+ let mut no_relay = note_repost();
+ no_relay.content = Some("fresh note".to_string());
+ if let RadrootsSocialTarget::Event { author, relays, .. } = &mut no_relay.target {
+ *author = None;
+ *relays = None;
+ }
+ let parts = repost_to_wire_parts(&no_relay).unwrap();
+ assert_eq!(parts.content, "fresh note");
+ assert!(!parts.tags.iter().any(|tag| {
+ tag.first().map(|entry| entry.as_str()) == Some(TAG_P)
+ || tag
+ .get(2)
+ .map(|entry| !entry.trim().is_empty())
+ .unwrap_or(false)
+ }));
+ let decoded = repost_from_event(parts.kind, &parts.tags, &parts.content).unwrap();
+ assert_eq!(decoded.content.as_deref(), Some("fresh note"));
+ assert!(matches!(
+ decoded.target,
+ RadrootsSocialTarget::Event { relays: None, .. }
+ ));
+
+ let mut invalid_target = note_repost();
+ invalid_target.target = RadrootsSocialTarget::Address {
+ address: format!("{KIND_ARTICLE}:{AUTHOR}:{ARTICLE_D_TAG}"),
+ author: Some(AUTHOR.to_string()),
+ event_kind: Some(KIND_ARTICLE),
+ relays: None,
+ };
+ assert!(matches!(
+ repost_build_tags(&invalid_target),
+ Err(EventEncodeError::InvalidField("target"))
+ ));
+
+ let mut invalid_id = note_repost();
+ if let RadrootsSocialTarget::Event { id, .. } = &mut invalid_id.target {
+ *id = "not-a-lowercase-hex-id".to_string();
+ }
+ assert!(matches!(
+ repost_build_tags(&invalid_id),
+ Err(EventEncodeError::InvalidField("target.id"))
+ ));
+
+ let mut invalid_author = note_repost();
+ if let RadrootsSocialTarget::Event { author, .. } = &mut invalid_author.target {
+ *author = Some(" ".to_string());
+ }
+ assert!(matches!(
+ repost_build_tags(&invalid_author),
+ Err(EventEncodeError::EmptyRequiredField("target.author"))
+ ));
+
+ let mut tags = repost_build_tags(¬e_repost()).unwrap();
+ let event_tag = tags
+ .iter_mut()
+ .find(|tag| tag.first().map(String::as_str) == Some(TAG_E))
+ .expect("event tag");
+ event_tag.truncate(1);
+ assert!(matches!(
+ repost_from_event(KIND_REPOST, &tags, ""),
+ Err(EventParseError::InvalidTag(TAG_E))
+ ));
+
+ let mut tags = repost_build_tags(¬e_repost()).unwrap();
+ replace_tag_value(&mut tags, TAG_E, "not-a-lowercase-hex-id");
+ assert!(matches!(
+ repost_from_event(KIND_REPOST, &tags, ""),
+ Err(EventParseError::InvalidTag(TAG_E))
+ ));
+}
+
+#[test]
+fn generic_repost_codecs_cover_event_targets_and_error_edges() {
+ let generic = RadrootsGenericRepost {
+ target: RadrootsSocialTarget::Event {
+ id: EVENT_ID.to_string(),
+ author: Some(AUTHOR.to_string()),
+ event_kind: Some(KIND_REACTION),
+ relays: Some(vec![
+ " ".to_string(),
+ "wss://relay.example.test".to_string(),
+ ]),
+ },
+ target_kind: KIND_REACTION,
+ content: None,
+ };
+ let parts = generic_repost_to_wire_parts(&generic).unwrap();
+ assert!(has_tag(&parts.tags, TAG_E, EVENT_ID));
+ assert!(has_tag(
+ &parts.tags,
+ TAG_K,
+ KIND_REACTION.to_string().as_str()
+ ));
+ let decoded = generic_repost_from_event(parts.kind, &parts.tags, &parts.content).unwrap();
+ assert!(decoded.content.is_none());
+ assert!(matches!(
+ decoded.target,
+ RadrootsSocialTarget::Event {
+ event_kind: Some(KIND_REACTION),
+ ..
+ }
+ ));
+
+ let wrong_kind = generic_repost_from_event(KIND_REPOST, &parts.tags, "").unwrap_err();
+ assert!(matches!(
+ wrong_kind,
+ EventParseError::InvalidKind {
+ expected: "16",
+ got: KIND_REPOST
+ }
+ ));
+
+ let mut tags = parts.tags.clone();
+ replace_tag_value(&mut tags, TAG_K, KIND_POST.to_string().as_str());
+ assert!(matches!(
+ generic_repost_from_event(KIND_GENERIC_REPOST, &tags, ""),
+ Err(EventParseError::InvalidTag(TAG_K))
+ ));
+
+ let mut tags = parts.tags.clone();
+ replace_tag_value(&mut tags, TAG_K, "not-a-number");
+ assert!(matches!(
+ generic_repost_from_event(KIND_GENERIC_REPOST, &tags, ""),
+ Err(EventParseError::InvalidNumber(TAG_K, _))
+ ));
+
+ let tags = vec![vec![TAG_K.to_string(), KIND_REACTION.to_string()]];
+ assert!(matches!(
+ generic_repost_from_event(KIND_GENERIC_REPOST, &tags, ""),
+ Err(EventParseError::MissingTag(TAG_E))
+ ));
+
+ let mut tags = generic_repost_build_tags(&generic_article_repost()).unwrap();
+ replace_tag_value(
+ &mut tags,
+ TAG_A,
+ format!("{KIND_REACTION}:{AUTHOR}:{ARTICLE_D_TAG}").as_str(),
+ );
+ assert!(matches!(
+ generic_repost_from_event(KIND_GENERIC_REPOST, &tags, ""),
+ Err(EventParseError::InvalidTag(TAG_A))
+ ));
+
+ let mut tags = generic_repost_build_tags(&generic).unwrap();
+ let event_tag = tags
+ .iter_mut()
+ .find(|tag| tag.first().map(String::as_str) == Some(TAG_E))
+ .expect("event tag");
+ event_tag.truncate(1);
+ assert!(matches!(
+ generic_repost_from_event(KIND_GENERIC_REPOST, &tags, ""),
+ Err(EventParseError::InvalidTag(TAG_E))
+ ));
+
+ let mut tags = generic_repost_build_tags(&generic).unwrap();
+ replace_tag_value(&mut tags, TAG_E, "not-a-lowercase-hex-id");
+ assert!(matches!(
+ generic_repost_from_event(KIND_GENERIC_REPOST, &tags, ""),
+ Err(EventParseError::InvalidTag(TAG_E))
+ ));
+
+ let mut generic = generic_article_repost();
+ generic.target_kind = KIND_REACTION;
+ assert!(matches!(
+ generic_repost_build_tags(&generic),
+ Err(EventEncodeError::InvalidField("target_kind"))
+ ));
+
+ let mut generic = generic_article_repost();
+ if let RadrootsSocialTarget::Address { author, relays, .. } = &mut generic.target {
+ *author = Some(" ".to_string());
+ *relays = None;
+ }
+ assert!(matches!(
+ generic_repost_build_tags(&generic),
+ Err(EventEncodeError::EmptyRequiredField("target.author"))
+ ));
+
+ let mut generic = generic_article_repost();
+ generic.target = RadrootsSocialTarget::External {
+ id: "https://example.test/repost-target".to_string(),
+ external_kind: "web".to_string(),
+ hint: None,
+ };
+ assert!(matches!(
+ generic_repost_build_tags(&generic),
+ Err(EventEncodeError::InvalidField("target"))
+ ));
+
+ let mut generic = RadrootsGenericRepost {
+ target: RadrootsSocialTarget::Event {
+ id: EVENT_ID.to_string(),
+ author: None,
+ event_kind: None,
+ relays: None,
+ },
+ target_kind: KIND_REACTION,
+ content: None,
+ };
+ assert!(matches!(
+ generic_repost_build_tags(&generic),
+ Err(EventEncodeError::InvalidField("target_kind"))
+ ));
+
+ if let RadrootsSocialTarget::Event { event_kind, .. } = &mut generic.target {
+ *event_kind = Some(KIND_REACTION);
+ }
+ generic.target_kind = KIND_POST;
+ assert!(matches!(
+ generic_repost_build_tags(&generic),
+ Err(EventEncodeError::InvalidField("target_kind"))
+ ));
+}
+
+#[test]
fn repost_wrappers_preserve_event_metadata() {
let parts = repost_to_wire_parts(¬e_repost()).unwrap();
let data = repost_data_from_event(