commit e3aacd796e728b7549c463ad6995cf38cea2d984
parent 3a1a072c888551fa1d3bde8f459d41c57436b365
Author: triesap <tyson@radroots.org>
Date: Sat, 21 Feb 2026 23:43:37 +0000
tests: expand encode and decode coverage for message, reaction, comment, and gift_wrap
Diffstat:
4 files changed, 217 insertions(+), 4 deletions(-)
diff --git a/crates/events-codec/tests/comment.rs b/crates/events-codec/tests/comment.rs
@@ -8,7 +8,9 @@ use radroots_events::{
use radroots_events_codec::comment::decode::comment_from_tags;
use radroots_events_codec::comment::decode::{index_from_event, metadata_from_event};
-use radroots_events_codec::comment::encode::{comment_build_tags, to_wire_parts};
+use radroots_events_codec::comment::encode::{
+ comment_build_tags, to_wire_parts, to_wire_parts_with_kind,
+};
use radroots_events_codec::error::{EventEncodeError, EventParseError};
use radroots_events_codec::event_ref::{build_event_ref_tag, push_nip10_ref_tags};
@@ -69,6 +71,24 @@ fn comment_to_wire_parts_requires_content() {
}
#[test]
+fn comment_to_wire_parts_sets_kind_content_and_tags() {
+ let comment = RadrootsComment {
+ root: common::event_ref("root", "author", KIND_POST),
+ parent: common::event_ref("parent", "author", KIND_POST),
+ content: "hello".to_string(),
+ };
+ let parts = to_wire_parts(&comment).unwrap();
+ assert_eq!(parts.kind, KIND_COMMENT);
+ assert_eq!(parts.content, "hello");
+ assert_eq!(parts.tags.len(), 6);
+
+ let custom_parts = to_wire_parts_with_kind(&comment, KIND_POST).unwrap();
+ assert_eq!(custom_parts.kind, KIND_POST);
+ assert_eq!(custom_parts.content, "hello");
+ assert_eq!(custom_parts.tags.len(), 6);
+}
+
+#[test]
fn comment_roundtrip_from_tags_with_parent() {
let root = common::event_ref_with_d(
"root",
diff --git a/crates/events-codec/tests/gift_wrap.rs b/crates/events-codec/tests/gift_wrap.rs
@@ -184,7 +184,78 @@ fn gift_wrap_build_tags_handles_optional_expiration_and_invalid_relay() {
}
#[test]
+fn gift_wrap_to_wire_parts_requires_content_and_accepts_default_kind() {
+ let mut gift_wrap = sample_gift_wrap();
+ gift_wrap.content = " ".to_string();
+ let err = to_wire_parts(&gift_wrap).unwrap_err();
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("content")
+ ));
+
+ let parts = to_wire_parts_with_kind(&sample_gift_wrap(), KIND_GIFT_WRAP).unwrap();
+ assert_eq!(parts.kind, KIND_GIFT_WRAP);
+ assert_eq!(parts.content, "encrypted");
+}
+
+#[test]
fn gift_wrap_to_wire_parts_with_kind_rejects_wrong_kind() {
let err = to_wire_parts_with_kind(&sample_gift_wrap(), KIND_MESSAGE).unwrap_err();
assert!(matches!(err, EventEncodeError::InvalidKind(KIND_MESSAGE)));
}
+
+#[test]
+fn gift_wrap_from_tags_handles_missing_expiration_and_rejects_empty_fields() {
+ let parsed = gift_wrap_from_tags(
+ KIND_GIFT_WRAP,
+ &[vec!["p".to_string(), "pubkey".to_string()]],
+ "payload",
+ )
+ .unwrap();
+ assert_eq!(parsed.recipient.public_key, "pubkey");
+ assert!(parsed.recipient.relay_url.is_none());
+ assert!(parsed.expiration.is_none());
+
+ let err = gift_wrap_from_tags(
+ KIND_GIFT_WRAP,
+ &[vec!["p".to_string(), " ".to_string()]],
+ "payload",
+ )
+ .unwrap_err();
+ assert!(matches!(err, EventParseError::InvalidTag("p")));
+
+ let err = gift_wrap_from_tags(
+ KIND_GIFT_WRAP,
+ &[vec!["p".to_string(), "pubkey".to_string()]],
+ " ",
+ )
+ .unwrap_err();
+ assert!(matches!(err, EventParseError::InvalidTag("content")));
+}
+
+#[test]
+fn gift_wrap_metadata_and_index_propagate_parse_errors() {
+ let tags = vec![vec!["p".to_string(), "pubkey".to_string()]];
+ let err = metadata_from_event(
+ "id".to_string(),
+ "author".to_string(),
+ 11,
+ KIND_GIFT_WRAP,
+ " ".to_string(),
+ tags.clone(),
+ )
+ .unwrap_err();
+ assert!(matches!(err, EventParseError::InvalidTag("content")));
+
+ let err = index_from_event(
+ "id".to_string(),
+ "author".to_string(),
+ 11,
+ KIND_GIFT_WRAP,
+ " ".to_string(),
+ tags,
+ "sig".to_string(),
+ )
+ .unwrap_err();
+ assert!(matches!(err, EventParseError::InvalidTag("content")));
+}
diff --git a/crates/events-codec/tests/message_file.rs b/crates/events-codec/tests/message_file.rs
@@ -1,13 +1,15 @@
+use radroots_events::RadrootsNostrEventPtr;
use radroots_events::kinds::{KIND_MESSAGE, KIND_MESSAGE_FILE};
use radroots_events::message::RadrootsMessageRecipient;
use radroots_events::message_file::{RadrootsMessageFile, RadrootsMessageFileDimensions};
-use radroots_events::RadrootsNostrEventPtr;
use radroots_events_codec::error::{EventEncodeError, EventParseError};
use radroots_events_codec::message_file::decode::{
index_from_event, message_file_from_tags, metadata_from_event,
};
-use radroots_events_codec::message_file::encode::{message_file_build_tags, to_wire_parts};
+use radroots_events_codec::message_file::encode::{
+ message_file_build_tags, to_wire_parts, to_wire_parts_with_kind,
+};
fn sample_message_file() -> RadrootsMessageFile {
RadrootsMessageFile {
@@ -81,6 +83,43 @@ fn message_file_build_tags_requires_file_type() {
}
#[test]
+fn message_file_build_tags_requires_crypto_fields() {
+ let mut message = sample_message_file();
+ message.encryption_algorithm = " ".to_string();
+ let err = message_file_build_tags(&message).unwrap_err();
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("encryption_algorithm")
+ ));
+
+ let mut message = sample_message_file();
+ message.decryption_key = " ".to_string();
+ let err = message_file_build_tags(&message).unwrap_err();
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("decryption_key")
+ ));
+
+ let mut message = sample_message_file();
+ message.decryption_nonce = " ".to_string();
+ let err = message_file_build_tags(&message).unwrap_err();
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("decryption_nonce")
+ ));
+}
+
+#[test]
+fn message_file_to_wire_parts_with_kind_enforces_kind() {
+ let message = sample_message_file();
+ let parts = to_wire_parts_with_kind(&message, KIND_MESSAGE_FILE).unwrap();
+ assert_eq!(parts.kind, KIND_MESSAGE_FILE);
+
+ let err = to_wire_parts_with_kind(&message, KIND_MESSAGE).unwrap_err();
+ assert!(matches!(err, EventEncodeError::InvalidKind(KIND_MESSAGE)));
+}
+
+#[test]
fn message_file_to_wire_parts_sets_kind_content_and_tags() {
let message = sample_message_file();
let parts = to_wire_parts(&message).unwrap();
diff --git a/crates/events-codec/tests/reaction.rs b/crates/events-codec/tests/reaction.rs
@@ -11,7 +11,9 @@ use radroots_events_codec::event_ref::{build_event_ref_tag, push_nip10_ref_tags}
use radroots_events_codec::reaction::decode::{
index_from_event, metadata_from_event, reaction_from_tags,
};
-use radroots_events_codec::reaction::encode::{reaction_build_tags, to_wire_parts};
+use radroots_events_codec::reaction::encode::{
+ reaction_build_tags, to_wire_parts, to_wire_parts_with_kind,
+};
#[test]
fn reaction_build_tags_requires_root_fields() {
@@ -28,6 +30,20 @@ fn reaction_build_tags_requires_root_fields() {
}
#[test]
+fn reaction_build_tags_requires_root_author() {
+ let reaction = RadrootsReaction {
+ root: common::event_ref("root", "", KIND_POST),
+ content: "like".to_string(),
+ };
+
+ let err = reaction_build_tags(&reaction).unwrap_err();
+ assert!(matches!(
+ err,
+ EventEncodeError::EmptyRequiredField("root.author")
+ ));
+}
+
+#[test]
fn reaction_to_wire_parts_requires_content() {
let reaction = RadrootsReaction {
root: common::event_ref("root", "author", KIND_POST),
@@ -42,6 +58,21 @@ fn reaction_to_wire_parts_requires_content() {
}
#[test]
+fn reaction_to_wire_parts_with_kind_keeps_requested_kind() {
+ let reaction = RadrootsReaction {
+ root: common::event_ref("root", "author", KIND_POST),
+ content: "+".to_string(),
+ };
+ let parts = to_wire_parts_with_kind(&reaction, KIND_POST).unwrap();
+ assert_eq!(parts.kind, KIND_POST);
+ assert_eq!(parts.content, "+");
+ assert_eq!(parts.tags.len(), 3);
+
+ let default_parts = to_wire_parts(&reaction).unwrap();
+ assert_eq!(default_parts.kind, KIND_REACTION);
+}
+
+#[test]
fn reaction_from_tags_requires_root_tag() {
let tags = vec![vec!["p".to_string(), "x".to_string()]];
let err = reaction_from_tags(KIND_REACTION, &tags, "+").unwrap_err();
@@ -49,6 +80,25 @@ fn reaction_from_tags_requires_root_tag() {
}
#[test]
+fn reaction_from_tags_rejects_invalid_kind_and_content() {
+ let root = common::event_ref("root", "author", KIND_POST);
+ let mut tags = Vec::new();
+ push_nip10_ref_tags(&mut tags, &root, "e", "p", "k", "a");
+
+ let err = reaction_from_tags(KIND_POST, &tags, "+").unwrap_err();
+ assert!(matches!(
+ err,
+ EventParseError::InvalidKind {
+ expected: "7",
+ got: KIND_POST
+ }
+ ));
+
+ let err = reaction_from_tags(KIND_REACTION, &tags, " ").unwrap_err();
+ assert!(matches!(err, EventParseError::InvalidTag("content")));
+}
+
+#[test]
fn reaction_roundtrip_from_tags() {
let root = common::event_ref_with_d(
"root",
@@ -127,6 +177,39 @@ fn reaction_metadata_and_index_from_event_roundtrip() {
}
#[test]
+fn reaction_metadata_and_index_propagate_parse_errors() {
+ let tags = vec![vec!["e".to_string(), "root".to_string()]];
+ let err = metadata_from_event(
+ "id".to_string(),
+ "author".to_string(),
+ 99,
+ KIND_POST,
+ "+".to_string(),
+ tags.clone(),
+ )
+ .unwrap_err();
+ assert!(matches!(
+ err,
+ EventParseError::InvalidKind {
+ expected: "7",
+ got: KIND_POST
+ }
+ ));
+
+ let err = index_from_event(
+ "id".to_string(),
+ "author".to_string(),
+ 99,
+ KIND_REACTION,
+ " ".to_string(),
+ tags,
+ "sig".to_string(),
+ )
+ .unwrap_err();
+ assert!(matches!(err, EventParseError::InvalidTag("content")));
+}
+
+#[test]
fn reaction_build_tags_supports_root_without_d_tag() {
let reaction = RadrootsReaction {
root: common::event_ref("root", "author", KIND_POST),