lib

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

commit 30cb226d21a1709252b2cfd1914ce3c42640dcd0
parent 032280fc6a1e8b054c719f8fc3a54fa7a7c0418e
Author: triesap <tyson@radroots.org>
Date:   Sun, 21 Jun 2026 20:26:58 +0000

events-codec: expand report coverage

- Cover report address targets, report-type variants, and optional target type markers.

- Add file target validation for empty file payloads, invalid URLs, blank magnets, and URL- or magnet-only reports.

- Exercise malformed event/address target decoding and invalid event target encoding.

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

Diffstat:
Mcrates/events_codec/tests/report.rs | 178++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 177 insertions(+), 1 deletion(-)

diff --git a/crates/events_codec/tests/report.rs b/crates/events_codec/tests/report.rs @@ -1,7 +1,7 @@ #![cfg(feature = "serde_json")] use radroots_events::{ - kinds::{KIND_POST, KIND_REPORT}, + kinds::{KIND_ARTICLE, KIND_POST, KIND_REPORT}, report::RadrootsReport, social::{RadrootsReportFileTarget, RadrootsReportType, RadrootsSocialTarget}, tags::{TAG_A, TAG_E, TAG_MAGNET, TAG_P, TAG_SERVER, TAG_SHA256}, @@ -17,6 +17,7 @@ use radroots_events_codec::{ const EVENT_ID: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; const REPORTED: &str = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; const FILE_HASH: &str = "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"; +const ARTICLE_D_TAG: &str = "DDDDDDDDDDDDDDDDDDDDDA"; fn profile_report() -> RadrootsReport { RadrootsReport { @@ -57,6 +58,21 @@ fn file_report() -> RadrootsReport { } } +fn address_report() -> RadrootsReport { + RadrootsReport { + reported_pubkey: REPORTED.to_string(), + report_type: RadrootsReportType::Nudity, + event: Some(RadrootsSocialTarget::Address { + address: format!("{KIND_ARTICLE}:{REPORTED}:{ARTICLE_D_TAG}"), + author: Some(REPORTED.to_string()), + event_kind: Some(KIND_ARTICLE), + relays: None, + }), + file: None, + content: None, + } +} + fn has_tag(tags: &[Vec<String>], key: &str, value: &str) -> bool { tags.iter().any(|tag| { tag.first().map(|entry| entry.as_str()) == Some(key) @@ -64,6 +80,14 @@ fn has_tag(tags: &[Vec<String>], key: &str, value: &str) -> bool { }) } +fn replace_tag_value(tags: &mut [Vec<String>], key: &str, value: &str) { + let tag = tags + .iter_mut() + .find(|tag| tag.first().map(String::as_str) == Some(key)) + .expect("tag"); + tag[1] = value.to_string(); +} + #[test] fn report_to_wire_parts_roundtrips_pubkey_event_and_file_reports() { let profile = to_wire_parts(&profile_report()).unwrap(); @@ -105,6 +129,19 @@ fn report_to_wire_parts_roundtrips_pubkey_event_and_file_reports() { decoded.file.and_then(|target| target.sha256).as_deref(), Some(FILE_HASH) ); + + let address = to_wire_parts(&address_report()).unwrap(); + assert!(has_tag( + &address.tags, + TAG_A, + format!("{KIND_ARTICLE}:{REPORTED}:{ARTICLE_D_TAG}").as_str() + )); + let decoded = report_from_event(address.kind, &address.tags, &address.content).unwrap(); + assert_eq!(decoded.report_type, RadrootsReportType::Nudity); + assert!(matches!( + decoded.event, + Some(RadrootsSocialTarget::Address { relays: None, .. }) + )); } #[test] @@ -135,6 +172,31 @@ fn report_codec_rejects_missing_pubkey_unknown_type_bad_hash_and_wrong_kind() { Err(EventEncodeError::InvalidField("file.sha256")) )); + let mut report = file_report(); + report.file = Some(RadrootsReportFileTarget { + sha256: None, + url: None, + magnet: None, + }); + assert!(matches!( + to_wire_parts(&report), + Err(EventEncodeError::EmptyRequiredField("file")) + )); + + let mut report = file_report(); + report.file.as_mut().unwrap().url = Some("ftp://media.example.test/blob".to_string()); + assert!(matches!( + to_wire_parts(&report), + Err(EventEncodeError::InvalidField("file.url")) + )); + + let mut report = file_report(); + report.file.as_mut().unwrap().magnet = Some(" ".to_string()); + assert!(matches!( + to_wire_parts(&report), + Err(EventEncodeError::EmptyRequiredField("file.magnet")) + )); + let err = report_from_event(KIND_REPORT, &[], "").unwrap_err(); assert!(matches!(err, EventParseError::MissingTag(TAG_P))); @@ -192,6 +254,31 @@ fn report_codec_rejects_bad_event_targets_and_report_type_mismatches() { Err(EventEncodeError::InvalidField("event")) )); + let mut report = event_report(); + if let Some(RadrootsSocialTarget::Event { id, .. }) = &mut report.event { + *id = "not-a-lowercase-hex-id".to_string(); + } + assert!(matches!( + report_build_tags(&report), + Err(EventEncodeError::InvalidField("event.id")) + )); + + let mut report = address_report(); + if let Some(RadrootsSocialTarget::Address { + address, relays, .. + }) = &mut report.event + { + *address = "not-an-address".to_string(); + *relays = Some(vec![ + " ".to_string(), + "wss://relay.example.test".to_string(), + ]); + } + assert!(matches!( + report_build_tags(&report), + Err(EventEncodeError::InvalidField("event.address")) + )); + let tags = vec![ vec![TAG_P.to_string(), REPORTED.to_string(), "spam".to_string()], vec![ @@ -213,6 +300,95 @@ fn report_codec_rejects_bad_event_targets_and_report_type_mismatches() { ]; let err = report_from_event(KIND_REPORT, &tags, "").unwrap_err(); assert!(matches!(err, EventParseError::InvalidNumber(TAG_A, _))); + + let mut tags = report_build_tags(&address_report()).unwrap(); + let address = tags + .iter_mut() + .find(|tag| tag.first().map(String::as_str) == Some(TAG_A)) + .expect("address tag"); + address.truncate(1); + let err = report_from_event(KIND_REPORT, &tags, "").unwrap_err(); + assert!(matches!(err, EventParseError::InvalidTag(TAG_A))); + + let mut tags = report_build_tags(&event_report()).unwrap(); + let event = tags + .iter_mut() + .find(|tag| tag.first().map(String::as_str) == Some(TAG_E)) + .expect("event tag"); + event.truncate(1); + let err = report_from_event(KIND_REPORT, &tags, "").unwrap_err(); + assert!(matches!(err, EventParseError::InvalidTag(TAG_E))); + + let mut tags = report_build_tags(&address_report()).unwrap(); + let address = tags + .iter_mut() + .find(|tag| tag.first().map(String::as_str) == Some(TAG_A)) + .expect("address tag"); + address.truncate(2); + let decoded = report_from_event(KIND_REPORT, &tags, "").expect("address report"); + assert!(matches!( + decoded.event, + Some(RadrootsSocialTarget::Address { .. }) + )); +} + +#[test] +fn report_codec_covers_report_type_and_file_variants() { + for (report_type, expected) in [ + (RadrootsReportType::Profanity, "profanity"), + (RadrootsReportType::Impersonation, "impersonation"), + (RadrootsReportType::Other, "other"), + ] { + let mut report = profile_report(); + report.report_type = report_type; + let parts = to_wire_parts(&report).unwrap(); + assert_eq!(parts.tags[0].get(2).map(String::as_str), Some(expected)); + let decoded = report_from_event(parts.kind, &parts.tags, "").unwrap(); + assert_eq!(decoded.report_type, report.report_type); + } + + let mut report = file_report(); + report.file = Some(RadrootsReportFileTarget { + sha256: None, + url: Some("https://media.example.test/blob".to_string()), + magnet: None, + }); + let parts = to_wire_parts(&report).unwrap(); + let decoded = report_from_event(parts.kind, &parts.tags, "").unwrap(); + assert_eq!( + decoded.file.and_then(|file| file.url).as_deref(), + Some("https://media.example.test/blob") + ); + + let mut report = file_report(); + report.file = Some(RadrootsReportFileTarget { + sha256: None, + url: None, + magnet: Some("magnet:?xt=urn:btih:example".to_string()), + }); + let parts = to_wire_parts(&report).unwrap(); + let decoded = report_from_event(parts.kind, &parts.tags, "").unwrap(); + assert_eq!( + decoded.file.and_then(|file| file.magnet).as_deref(), + Some("magnet:?xt=urn:btih:example") + ); + + let mut tags = report_build_tags(&file_report()).unwrap(); + let sha = tags + .iter_mut() + .find(|tag| tag.first().map(String::as_str) == Some(TAG_SHA256)) + .expect("sha tag"); + sha.truncate(2); + let decoded = report_from_event(KIND_REPORT, &tags, "").unwrap(); + assert_eq!( + decoded.file.and_then(|file| file.sha256).as_deref(), + Some(FILE_HASH) + ); + + let mut tags = report_build_tags(&file_report()).unwrap(); + replace_tag_value(&mut tags, TAG_SHA256, "bad"); + let err = report_from_event(KIND_REPORT, &tags, "").unwrap_err(); + assert!(matches!(err, EventParseError::InvalidTag(TAG_SHA256))); } #[test]