lib

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

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:
Mcrates/events-codec/tests/comment.rs | 22+++++++++++++++++++++-
Mcrates/events-codec/tests/gift_wrap.rs | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/events-codec/tests/message_file.rs | 43+++++++++++++++++++++++++++++++++++++++++--
Mcrates/events-codec/tests/reaction.rs | 85++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
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),