lib

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

commit 0b9113e8edeeca1bbfe89a7e4d2eb94792c2dd02
parent 74946e23190d31c20da9348504aa5ec76c3ba7b8
Author: triesap <tyson@radroots.org>
Date:   Sun, 21 Jun 2026 20:09:37 +0000

events-codec: expand farm workspace coverage

- Cover farm workspace parsed wrappers and optional farm manifests.

- Add decode rejection coverage for tag mismatches, marker tags, farm addresses, and invalid content.

- Exercise manifest validation edges for required fields, relays, media servers, supported kinds, and farm refs.

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

Diffstat:
Mcrates/events_codec/src/farm_workspace/mod.rs | 292++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 290 insertions(+), 2 deletions(-)

diff --git a/crates/events_codec/src/farm_workspace/mod.rs b/crates/events_codec/src/farm_workspace/mod.rs @@ -14,11 +14,13 @@ mod tests { RadrootsFarmWorkspaceManifest, RadrootsFarmWorkspaceMediaServer, RadrootsFarmWorkspaceRelay, RadrootsFarmWorkspaceRelayMode, }, - kinds::{KIND_FARM_FILE_METADATA, KIND_POST}, + kinds::{KIND_FARM, KIND_FARM_FILE_METADATA, KIND_POST}, }; use crate::error::{EventEncodeError, EventParseError}; - use crate::farm_workspace::decode::farm_workspace_from_event; + use crate::farm_workspace::decode::{ + data_from_event, farm_workspace_from_event, parsed_from_event, + }; use crate::farm_workspace::encode::{ farm_workspace_build_tags, to_wire_parts, to_wire_parts_with_kind, }; @@ -158,6 +160,222 @@ mod tests { )); } + #[test] + fn farm_workspace_manifest_wrappers_roundtrip_optional_farm() { + let mut manifest = sample_manifest(); + manifest.farm = None; + manifest.media_servers.clear(); + let parts = to_wire_parts(&manifest).expect("workspace wire parts"); + assert!( + !parts + .tags + .iter() + .any(|tag| tag.first().map(String::as_str) == Some("a")) + ); + + let data = data_from_event( + "event-id".to_string(), + "author-pubkey".to_string(), + 99, + 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, 99); + assert_eq!(data.kind, KIND_FARM_WORKSPACE_MANIFEST); + assert_same_manifest(&data.data, &manifest); + + let parsed = parsed_from_event( + "event-id".to_string(), + "author-pubkey".to_string(), + 99, + parts.kind, + parts.content, + parts.tags, + "sig".to_string(), + ) + .expect("parsed event"); + assert_eq!(parsed.event.sig, "sig"); + assert_same_manifest(&parsed.data.data, &manifest); + } + + #[test] + fn farm_workspace_manifest_rejects_decode_tag_and_content_edges() { + let parts = to_wire_parts(&sample_manifest()).expect("workspace wire parts"); + + let wrong_kind = + farm_workspace_from_event(KIND_POST, &parts.tags, &parts.content).unwrap_err(); + assert!(matches!( + wrong_kind, + EventParseError::InvalidKind { + expected: "30078", + got: KIND_POST + } + )); + + let empty_content = farm_workspace_from_event(parts.kind, &parts.tags, " ").unwrap_err(); + assert!(matches!( + empty_content, + EventParseError::InvalidJson("content") + )); + + let bad_json = farm_workspace_from_event(parts.kind, &parts.tags, "{").unwrap_err(); + assert!(matches!(bad_json, EventParseError::InvalidJson("content"))); + + let mut owner_mismatch = parts.tags.clone(); + replace_first_tag(&mut owner_mismatch, "p", tag("p", "other_owner")); + let err = + farm_workspace_from_event(parts.kind, &owner_mismatch, &parts.content).unwrap_err(); + assert!(matches!(err, EventParseError::InvalidTag("p"))); + + let mut missing_marker = parts.tags.clone(); + remove_tags(&mut missing_marker, "t"); + let err = + farm_workspace_from_event(parts.kind, &missing_marker, &parts.content).unwrap_err(); + assert!(matches!(err, EventParseError::MissingTag("t"))); + + for replacement in [ + tag( + "a", + &format!("{KIND_FARM}:farm_pubkey:AAAAAAAAAAAAAAAAAAAAAg"), + ), + tag("a", "30023:farm_pubkey:AAAAAAAAAAAAAAAAAAAAAQ"), + tag("a", &format!("{KIND_FARM}:farm_pubkey:bad d")), + ] { + let mut tags = parts.tags.clone(); + replace_first_tag(&mut tags, "a", replacement); + let err = farm_workspace_from_event(parts.kind, &tags, &parts.content).unwrap_err(); + assert!(matches!(err, EventParseError::InvalidTag("a"))); + } + } + + #[test] + fn farm_workspace_manifest_rejects_content_validation_edges() { + let parts = to_wire_parts(&sample_manifest()).expect("workspace wire parts"); + + for (manifest, expected) in [ + { + let mut manifest = sample_manifest(); + manifest.farm_group_id.clear(); + (manifest, EventParseError::InvalidTag("h")) + }, + { + let mut manifest = sample_manifest(); + manifest.owner_pubkey.clear(); + (manifest, EventParseError::InvalidTag("p")) + }, + { + let mut manifest = sample_manifest(); + manifest.relays.clear(); + (manifest, EventParseError::InvalidJson("relays")) + }, + { + let mut manifest = sample_manifest(); + manifest.supported_kinds = vec![KIND_FARM_WORKSPACE_MANIFEST]; + (manifest, EventParseError::InvalidJson("supported_kinds")) + }, + { + let mut manifest = sample_manifest(); + manifest.name.clear(); + (manifest, EventParseError::InvalidJson("name")) + }, + { + let mut manifest = sample_manifest(); + manifest.protocol_version.clear(); + (manifest, EventParseError::InvalidJson("protocol_version")) + }, + { + let mut manifest = sample_manifest(); + manifest.relays[0].url.clear(); + (manifest, EventParseError::InvalidJson("relays.url")) + }, + { + let mut manifest = sample_manifest(); + manifest.media_servers[0].service.clear(); + ( + manifest, + EventParseError::InvalidJson("media_servers.service"), + ) + }, + { + let mut manifest = sample_manifest(); + manifest.farm.as_mut().unwrap().d_tag = "bad d".to_string(); + (manifest, EventParseError::InvalidTag("d")) + }, + ] { + let content = serde_json::to_string(&manifest).expect("workspace content"); + let err = farm_workspace_from_event(parts.kind, &parts.tags, &content).unwrap_err(); + assert_same_parse_error(err, expected); + } + } + + #[test] + fn farm_workspace_manifest_rejects_encoder_validation_edges() { + for (manifest, expected) in [ + { + let mut manifest = sample_manifest(); + manifest.name.clear(); + (manifest, EventEncodeError::EmptyRequiredField("name")) + }, + { + let mut manifest = sample_manifest(); + manifest.owner_pubkey.clear(); + ( + manifest, + EventEncodeError::EmptyRequiredField("owner_pubkey"), + ) + }, + { + let mut manifest = sample_manifest(); + manifest.protocol_version.clear(); + ( + manifest, + EventEncodeError::EmptyRequiredField("protocol_version"), + ) + }, + { + let mut manifest = sample_manifest(); + manifest.relays[0].url.clear(); + (manifest, EventEncodeError::EmptyRequiredField("relays.url")) + }, + { + let mut manifest = sample_manifest(); + manifest.media_servers[0].url.clear(); + ( + manifest, + EventEncodeError::EmptyRequiredField("media_servers.url"), + ) + }, + { + let mut manifest = sample_manifest(); + manifest.media_servers[0].service.clear(); + ( + manifest, + EventEncodeError::EmptyRequiredField("media_servers.service"), + ) + }, + { + let mut manifest = sample_manifest(); + manifest.farm.as_mut().unwrap().pubkey.clear(); + ( + manifest, + EventEncodeError::EmptyRequiredField("farm.pubkey"), + ) + }, + { + let mut manifest = sample_manifest(); + manifest.farm.as_mut().unwrap().d_tag = "bad d".to_string(); + (manifest, EventEncodeError::InvalidField("farm.d_tag")) + }, + ] { + let err = farm_workspace_build_tags(&manifest).unwrap_err(); + assert_same_encode_error(err, expected); + } + } + fn sample_manifest() -> RadrootsFarmWorkspaceManifest { RadrootsFarmWorkspaceManifest { d_tag: D_TAG.to_string(), @@ -191,4 +409,74 @@ mod tests { fn tag(key: &str, value: &str) -> Vec<String> { vec![key.to_string(), value.to_string()] } + + fn remove_tags(tags: &mut Vec<Vec<String>>, name: &str) { + tags.retain(|tag| tag.first().map(String::as_str) != Some(name)); + } + + fn replace_first_tag(tags: &mut [Vec<String>], name: &str, replacement: Vec<String>) { + let tag = tags + .iter_mut() + .find(|tag| tag.first().map(String::as_str) == Some(name)) + .expect("tag"); + *tag = replacement; + } + + fn assert_same_manifest( + actual: &RadrootsFarmWorkspaceManifest, + expected: &RadrootsFarmWorkspaceManifest, + ) { + assert_eq!( + serde_json::to_value(actual).expect("actual manifest value"), + serde_json::to_value(expected).expect("expected manifest value") + ); + } + + fn assert_same_parse_error(actual: EventParseError, expected: EventParseError) { + match (actual, expected) { + (EventParseError::MissingTag(actual), EventParseError::MissingTag(expected)) + | (EventParseError::InvalidTag(actual), EventParseError::InvalidTag(expected)) + | (EventParseError::InvalidJson(actual), EventParseError::InvalidJson(expected)) => { + assert_eq!(actual, expected); + } + ( + EventParseError::InvalidKind { + expected: actual_expected, + got: actual_got, + }, + EventParseError::InvalidKind { expected, got }, + ) => { + assert_eq!(actual_expected, expected); + assert_eq!(actual_got, got); + } + ( + EventParseError::InvalidNumber(actual, _), + EventParseError::InvalidNumber(expected, _), + ) => { + assert_eq!(actual, expected); + } + (actual, expected) => { + panic!("unexpected parse error {actual:?}, expected {expected:?}") + } + } + } + + 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 encode error {actual:?}, expected {expected:?}") + } + } + } }