lib

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

commit 1e0c91b479674149b6069629893d8de8af0b2d23
parent 77040d588430ba2ad3a35f7668e0f6d7b8020def
Author: triesap <tyson@radroots.org>
Date:   Sun, 21 Jun 2026 20:00:41 +0000

events-codec: expand farm file coverage

- Cover farm file parsed-data wrappers and minimal optional metadata shapes.

- Add decode rejection coverage for owner document, size, dimension, source, and caption tags.

- Exercise encoder validation for workspace, caption, dimensions, source, alt, and fallback fields.

- Validate full radroots_events_codec tests, check, diff check, and refreshed coverage run.

Diffstat:
Mcrates/events_codec/src/farm_file/mod.rs | 249++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 248 insertions(+), 1 deletion(-)

diff --git a/crates/events_codec/src/farm_file/mod.rs b/crates/events_codec/src/farm_file/mod.rs @@ -14,7 +14,9 @@ mod tests { }; use crate::error::{EventEncodeError, EventParseError}; - use crate::farm_file::decode::farm_file_metadata_from_event; + use crate::farm_file::decode::{ + data_from_event, farm_file_metadata_from_event, parsed_from_event, + }; use crate::farm_file::encode::{ farm_file_metadata_build_tags, to_wire_parts, to_wire_parts_with_kind, }; @@ -118,6 +120,72 @@ mod tests { } #[test] + fn farm_file_metadata_wrappers_roundtrip_minimal_optional_shape() { + let mut metadata = sample_metadata(); + metadata.caption = None; + metadata.original_sha256 = None; + metadata.size_bytes = None; + metadata.dimensions = None; + metadata.blurhash = None; + metadata.thumb = None; + metadata.image = Some(RadrootsFarmFileSource { + url: "https://media.example.invalid/image/sha256".to_string(), + mime_type: None, + dimensions: Some(RadrootsFarmFileDimensions { w: 640, h: 480 }), + }); + metadata.alt = None; + metadata.fallbacks.clear(); + let parts = to_wire_parts(&metadata).expect("file metadata wire parts"); + + assert!( + !parts + .tags + .iter() + .any(|tag| tag.first().map(String::as_str) == Some("size")) + ); + assert!( + !parts + .tags + .iter() + .any(|tag| tag.first().map(String::as_str) == Some("dim")) + ); + assert!(parts.tags.iter().any(|tag| tag + == &vec![ + "image".to_string(), + "https://media.example.invalid/image/sha256".to_string(), + "640x480".to_string() + ])); + + let data = data_from_event( + "event-id".to_string(), + "author-pubkey".to_string(), + 42, + parts.kind, + parts.content.clone(), + parts.tags.clone(), + ) + .expect("parsed data"); + assert_eq!(data.id, "event-id"); + assert_eq!(data.author, "author-pubkey"); + assert_eq!(data.published_at, 42); + assert_eq!(data.kind, KIND_FARM_FILE_METADATA); + assert_eq!(data.data, metadata); + + let parsed = parsed_from_event( + "event-id".to_string(), + "author-pubkey".to_string(), + 42, + parts.kind, + parts.content, + parts.tags, + "sig".to_string(), + ) + .expect("parsed event"); + assert_eq!(parsed.event.sig, "sig"); + assert_eq!(parsed.data.data, metadata); + } + + #[test] fn farm_file_metadata_preserves_expanded_owner_document_kinds() { for kind in [ RadrootsFarmCrdtDocumentKind::FarmMembership, @@ -143,6 +211,156 @@ mod tests { } } + #[test] + fn farm_file_metadata_rejects_malformed_decode_tags() { + let parts = to_wire_parts(&sample_metadata()).expect("file metadata wire parts"); + + let mut missing_owner = parts.tags.clone(); + missing_owner + .retain(|tag| tag.first().map(String::as_str) != Some("radroots:owner_document")); + let err = + farm_file_metadata_from_event(parts.kind, &missing_owner, &parts.content).unwrap_err(); + assert!(matches!( + err, + EventParseError::MissingTag("radroots:owner_document") + )); + + for replacement in [ + vec![ + "radroots:owner_document".to_string(), + OWNER_DOCUMENT_ID.to_string(), + ], + vec![ + "radroots:owner_document".to_string(), + OWNER_DOCUMENT_ID.to_string(), + " ".to_string(), + ], + vec![ + "radroots:owner_document".to_string(), + "bad d tag".to_string(), + "FarmTask".to_string(), + ], + ] { + let mut tags = replace_tag(&parts.tags, "radroots:owner_document", replacement); + let err = farm_file_metadata_from_event(parts.kind, &tags, &parts.content).unwrap_err(); + assert!(matches!( + err, + EventParseError::InvalidTag("radroots:owner_document") + )); + tags.clear(); + } + + for (key, value, expected) in [ + ("size", "not-a-number", "size"), + ("dim", "bad", "dim"), + ("dim", "0x12", "dim"), + ("thumb", "", "thumb"), + ("thumb", " ", "thumb"), + ] { + let tags = replace_tag(&parts.tags, key, tag(key, value)); + let err = farm_file_metadata_from_event(parts.kind, &tags, &parts.content).unwrap_err(); + assert!( + matches!( + err, + EventParseError::InvalidTag(found) if found == expected + ) || matches!( + err, + EventParseError::InvalidNumber(found, _) if found == expected + ) + ); + } + + for replacement in [ + vec!["thumb".to_string()], + vec![ + "thumb".to_string(), + "https://media.example.invalid/thumb/sha256".to_string(), + "image/jpeg".to_string(), + "320x240".to_string(), + "extra".to_string(), + ], + vec![ + "thumb".to_string(), + "https://media.example.invalid/thumb/sha256".to_string(), + "image/jpeg".to_string(), + " ".to_string(), + ], + ] { + let tags = replace_tag(&parts.tags, "thumb", replacement); + let err = farm_file_metadata_from_event(parts.kind, &tags, &parts.content).unwrap_err(); + assert!(matches!(err, EventParseError::InvalidTag("thumb"))); + } + + let err = farm_file_metadata_from_event(parts.kind, &parts.tags, " ").unwrap_err(); + assert!(matches!(err, EventParseError::InvalidTag("caption"))); + } + + #[test] + fn farm_file_metadata_rejects_encoder_validation_edges() { + for (metadata, expected) in [ + { + let mut metadata = sample_metadata(); + metadata.workspace.d_tag = "bad d tag".to_string(); + (metadata, EventEncodeError::InvalidField("workspace.d_tag")) + }, + { + let mut metadata = sample_metadata(); + metadata.caption = Some("".to_string()); + (metadata, EventEncodeError::EmptyRequiredField("caption")) + }, + { + let mut metadata = sample_metadata(); + metadata.dimensions = Some(RadrootsFarmFileDimensions { w: 0, h: 1200 }); + (metadata, EventEncodeError::InvalidField("dimensions")) + }, + { + let mut metadata = sample_metadata(); + metadata.blurhash = Some("".to_string()); + (metadata, EventEncodeError::EmptyRequiredField("blurhash")) + }, + { + let mut metadata = sample_metadata(); + metadata.thumb = Some(RadrootsFarmFileSource { + url: "".to_string(), + mime_type: None, + dimensions: None, + }); + (metadata, EventEncodeError::EmptyRequiredField("thumb")) + }, + { + let mut metadata = sample_metadata(); + metadata.thumb = Some(RadrootsFarmFileSource { + url: "https://media.example.invalid/thumb/sha256".to_string(), + mime_type: Some("".to_string()), + dimensions: None, + }); + (metadata, EventEncodeError::EmptyRequiredField("thumb")) + }, + { + let mut metadata = sample_metadata(); + metadata.thumb = Some(RadrootsFarmFileSource { + url: "https://media.example.invalid/thumb/sha256".to_string(), + mime_type: None, + dimensions: Some(RadrootsFarmFileDimensions { w: 320, h: 0 }), + }); + (metadata, EventEncodeError::InvalidField("thumb")) + }, + { + let mut metadata = sample_metadata(); + metadata.alt = Some("".to_string()); + (metadata, EventEncodeError::EmptyRequiredField("alt")) + }, + { + let mut metadata = sample_metadata(); + metadata.fallbacks = vec!["".to_string()]; + (metadata, EventEncodeError::EmptyRequiredField("fallbacks")) + }, + ] { + let err = farm_file_metadata_build_tags(&metadata).unwrap_err(); + assert_same_encode_error(err, expected); + } + } + fn sample_metadata() -> RadrootsFarmFileMetadata { RadrootsFarmFileMetadata { d_tag: FILE_D_TAG.to_string(), @@ -177,4 +395,33 @@ mod tests { fn tag(key: &str, value: &str) -> Vec<String> { vec![key.to_string(), value.to_string()] } + + fn replace_tag(tags: &[Vec<String>], key: &str, replacement: Vec<String>) -> Vec<Vec<String>> { + tags.iter() + .map(|tag| { + if tag.first().map(String::as_str) == Some(key) { + replacement.clone() + } else { + tag.clone() + } + }) + .collect() + } + + fn assert_same_encode_error(actual: EventEncodeError, expected: EventEncodeError) { + match (actual, expected) { + ( + EventEncodeError::EmptyRequiredField(actual), + EventEncodeError::EmptyRequiredField(expected), + ) + | (EventEncodeError::InvalidField(actual), EventEncodeError::InvalidField(expected)) => { + assert_eq!(actual, expected); + } + (EventEncodeError::InvalidKind(actual), EventEncodeError::InvalidKind(expected)) => { + assert_eq!(actual, expected); + } + (EventEncodeError::Json, EventEncodeError::Json) => {} + (actual, expected) => panic!("unexpected error {actual:?}, expected {expected:?}"), + } + } }