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:
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:?}"),
+ }
+ }
}