lib

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

commit 39c4075119815382837832ac6aa5a980d2d78599
parent 6683d409eedb11cd97f8a11f8f9aa50847b49bd1
Author: triesap <tyson@radroots.org>
Date:   Sun, 21 Jun 2026 21:34:02 +0000

authority: expand coverage edge tests

- Cover authority error display/source variants, signer identity and signer failure variants, actor selector/getter paths, and draft-validation fallback mappings.

- Validate radroots_authority tests, crate check, diff check, refreshed coverage run, and radroots_authority policy gate.

Diffstat:
Mcrates/authority/src/actor.rs | 22++++++++++++++++------
Mcrates/authority/src/authorization.rs | 47+++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/authority/src/error.rs | 158+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Mcrates/authority/src/signer.rs | 48++++++++++++++++++++++++++++++++++++++----------
4 files changed, 231 insertions(+), 44 deletions(-)

diff --git a/crates/authority/src/actor.rs b/crates/authority/src/actor.rs @@ -327,6 +327,11 @@ mod tests { .expect("actor"); assert_eq!(actor.source(), RadrootsActorSource::LocalAccount); + assert_eq!(actor.pubkey().as_str(), hex_64('a')); + assert_eq!( + actor.roles().iter().copied().collect::<Vec<_>>(), + vec![RadrootsActorRole::Farmer] + ); let account_id = actor.account_id().expect("account id"); assert_eq!(account_id.as_str(), "acct-field-01"); assert_eq!(account_id.to_string(), "acct-field-01"); @@ -406,11 +411,14 @@ mod tests { let from_str = "acct-field-01" .parse::<RadrootsActorAccountId>() .expect("from str"); + let from_borrowed = + RadrootsActorAccountId::try_from("acct-field-01").expect("from borrowed"); let from_owned = RadrootsActorAccountId::try_from("acct-field-01".to_owned()).expect("from owned"); assert_eq!(parsed, "acct-field-01"); assert_eq!(from_str.as_ref(), "acct-field-01"); + assert_eq!(from_borrowed.as_str(), "acct-field-01"); assert_eq!(from_owned.into_string(), "acct-field-01"); } @@ -423,20 +431,22 @@ mod tests { Some("radroots.listing.published.v1".to_owned()), ); - assert!(matches!( + assert_eq!( request.selector(), - RadrootsActorSelector::AccountId(account_id) if account_id.as_str() == "acct-field-01" - )); + &RadrootsActorSelector::account_id("acct-field-01").expect("selector") + ); assert_eq!(request.required_role(), RadrootsActorRole::Seller); assert_eq!(request.contract_id(), Some("radroots.listing.published.v1")); } #[test] fn selector_supports_account_and_draft_resolution() { - assert!(matches!( + assert_eq!( RadrootsActorSelector::account_id("acct-field-01").expect("selector"), - RadrootsActorSelector::AccountId(ref account_id) if account_id.as_str() == "acct-field-01" - )); + RadrootsActorSelector::AccountId( + RadrootsActorAccountId::parse("acct-field-01").expect("account id") + ) + ); assert!(matches!( RadrootsActorSelector::SelectedAccount, RadrootsActorSelector::SelectedAccount diff --git a/crates/authority/src/authorization.rs b/crates/authority/src/authorization.rs @@ -497,6 +497,40 @@ mod tests { } #[test] + fn signed_event_pubkey_mismatch_fails() { + let pubkey = hex_64('a'); + let draft = listing_draft(pubkey.as_str()); + let signed = RadrootsSignedNostrEvent::new(RadrootsSignedNostrEventParts { + id: draft.expected_event_id.clone(), + pubkey: hex_64('b'), + created_at: draft.created_at, + kind: draft.kind, + tags: draft.tags.clone(), + content: draft.content.clone(), + sig: hex_128('f'), + raw_json: "{}".to_owned(), + }) + .expect("signed event"); + + assert!(matches!( + validate_signed_event_matches_draft(&signed, &draft), + Err(RadrootsAuthorityError::SignedEventPubkeyMismatch { .. }) + )); + } + + #[test] + fn draft_validation_fallback_errors_map_to_computed_id_invalid() { + let error = authority_error_from_draft_validation(RadrootsDraftError::UnknownContract( + "radroots.unknown.v1".to_owned(), + )); + + assert!(matches!( + error, + RadrootsAuthorityError::SignedEventComputedIdInvalid { .. } + )); + } + + #[test] fn signed_event_computed_id_mismatch_fails() { let pubkey = hex_64('a'); let inconsistent_draft = RadrootsFrozenEventDraft { @@ -540,6 +574,19 @@ mod tests { } #[test] + fn static_signer_maps_invalid_signed_event_parts() { + let pubkey = hex_64('a'); + let mut draft = listing_draft(pubkey.as_str()); + draft.expected_event_id = "bad-id".to_owned(); + let signer = StaticSigner::new(pubkey.as_str()); + + assert!(matches!( + signer.sign_frozen_draft(&draft), + Err(RadrootsSignerError::SigningFailed { .. }) + )); + } + + #[test] fn authorized_actor_and_signer_return_signed_event() { let pubkey = hex_64('a'); let draft = listing_draft(pubkey.as_str()); diff --git a/crates/authority/src/error.rs b/crates/authority/src/error.rs @@ -244,43 +244,145 @@ impl std::error::Error for RadrootsSignerError {} #[cfg(test)] mod tests { use super::*; + use radroots_events::contract::RadrootsActorRole; use std::error::Error as _; #[test] fn authority_error_display_uses_contract_messages() { + let cases = [ + ( + RadrootsAuthorityError::InvalidActorPubkey, + "invalid actor public key", + ), + ( + RadrootsAuthorityError::InvalidActorAccountIdEmpty, + "invalid actor account id: empty", + ), + ( + RadrootsAuthorityError::InvalidActorAccountIdUntrimmed, + "invalid actor account id: contains leading or trailing whitespace", + ), + ( + RadrootsAuthorityError::InvalidActorAccountIdControlCharacter, + "invalid actor account id: contains a control character", + ), + ( + RadrootsAuthorityError::InvalidActorAccountIdTooLong { max_len: 128 }, + "invalid actor account id: longer than 128 characters", + ), + ( + RadrootsAuthorityError::InvalidSignerPubkey, + "invalid signer public key", + ), + ( + RadrootsAuthorityError::UnknownContract { + contract_id: "radroots.unknown.v1".to_owned(), + }, + "unknown event contract `radroots.unknown.v1`", + ), + ( + RadrootsAuthorityError::DraftKindMismatch { + contract_id: "radroots.social.post.v1".to_owned(), + expected_kind: 1, + actual_kind: 2, + }, + "event contract `radroots.social.post.v1` expects kind 1, got 2", + ), + ( + RadrootsAuthorityError::ActorRoleUnsatisfied { + contract_id: "radroots.listing.published.v1".to_owned(), + required_role: RadrootsActorRole::Seller, + }, + "actor does not satisfy role Seller for contract `radroots.listing.published.v1`", + ), + ( + RadrootsAuthorityError::ActorPubkeyMismatch { + expected_pubkey: "expected".to_owned(), + actor_pubkey: "actor".to_owned(), + }, + "actor pubkey mismatch: expected expected, got actor", + ), + ( + RadrootsAuthorityError::SignerPubkeyMismatch { + expected_pubkey: "expected".to_owned(), + signer_pubkey: "signer".to_owned(), + }, + "signer pubkey mismatch: expected expected, got signer", + ), + ( + RadrootsAuthorityError::SignedEventPubkeyMismatch { + expected_pubkey: "expected".to_owned(), + actual_pubkey: "actual".to_owned(), + }, + "signed event pubkey mismatch: expected expected, got actual", + ), + ( + RadrootsAuthorityError::SignedEventIdMismatch { + expected_event_id: "expected".to_owned(), + actual_event_id: "actual".to_owned(), + }, + "signed event id mismatch: expected expected, got actual", + ), + ( + RadrootsAuthorityError::SignedEventCreatedAtMismatch { + expected_created_at: 1, + actual_created_at: 2, + }, + "signed event created_at mismatch: expected 1, got 2", + ), + ( + RadrootsAuthorityError::SignedEventKindMismatch { + expected_kind: 1, + actual_kind: 2, + }, + "signed event kind mismatch: expected 1, got 2", + ), + ( + RadrootsAuthorityError::SignedEventTagsMismatch { + expected_len: 2, + actual_len: 1, + }, + "signed event tags mismatch: expected 2 tags, got 1 tags", + ), + ( + RadrootsAuthorityError::SignedEventContentMismatch { + expected_len: 17, + actual_len: 2, + }, + "signed event content mismatch: expected 17 bytes, got 2 bytes", + ), + ( + RadrootsAuthorityError::SignedEventComputedIdInvalid { + message: "invalid event id".to_owned(), + }, + "signed event computed id could not be derived: invalid event id", + ), + ( + RadrootsAuthorityError::SignedEventComputedIdMismatch { + expected_event_id: "expected".to_owned(), + computed_event_id: "computed".to_owned(), + }, + "signed event computed id mismatch: expected expected, computed computed", + ), + ]; + + for (error, expected) in cases { + assert_eq!(error.to_string(), expected); + assert!(error.source().is_none()); + } + } + + #[test] + fn signer_error_display_and_source_are_stable() { assert_eq!( - RadrootsAuthorityError::InvalidActorPubkey.to_string(), - "invalid actor public key" - ); - assert_eq!( - RadrootsAuthorityError::DraftKindMismatch { - contract_id: "radroots.social.post.v1".to_owned(), - expected_kind: 1, - actual_kind: 2, - } - .to_string(), - "event contract `radroots.social.post.v1` expects kind 1, got 2" - ); - assert_eq!( - RadrootsAuthorityError::SignedEventTagsMismatch { - expected_len: 2, - actual_len: 1, - } - .to_string(), - "signed event tags mismatch: expected 2 tags, got 1 tags" + RadrootsSignerError::Unavailable.to_string(), + "signer unavailable" ); assert_eq!( - RadrootsAuthorityError::SignedEventContentMismatch { - expected_len: 17, - actual_len: 2, - } - .to_string(), - "signed event content mismatch: expected 17 bytes, got 2 bytes" + RadrootsSignerError::Rejected.to_string(), + "signer rejected draft" ); - } - #[test] - fn signer_error_display_and_source_are_stable() { let signer_error = RadrootsSignerError::SigningFailed { message: "deterministic failure".to_owned(), }; diff --git a/crates/authority/src/signer.rs b/crates/authority/src/signer.rs @@ -123,6 +123,18 @@ mod tests { } #[test] + fn signer_identity_validates_public_key() { + let pubkey = hex_64('a'); + let identity = RadrootsSignerIdentity::new(pubkey.as_str()).expect("identity"); + assert_eq!(identity.pubkey().as_str(), pubkey); + + assert!(matches!( + RadrootsSignerIdentity::new("bad-pubkey"), + Err(RadrootsAuthorityError::InvalidSignerPubkey) + )); + } + + #[test] fn mock_signer_returns_signed_frozen_draft() { let pubkey = hex_64('a'); let signer = MockSigner::new(pubkey.as_str()); @@ -138,21 +150,37 @@ mod tests { #[test] fn mock_signer_propagates_signing_errors() { let pubkey = hex_64('a'); - let signer = MockSigner::failing( - pubkey.as_str(), + let draft = draft_for(pubkey.as_str()); + + for failure in [ + RadrootsSignerError::Unavailable, + RadrootsSignerError::Rejected, RadrootsSignerError::SigningFailed { message: "deterministic failure".to_owned(), }, - ); - let draft = draft_for(pubkey.as_str()); + ] { + let signer = MockSigner::failing(pubkey.as_str(), failure); + let err = signer.sign_frozen_draft(&draft).expect_err("failure"); + + match err { + RadrootsSignerError::Unavailable => {} + RadrootsSignerError::Rejected => {} + RadrootsSignerError::SigningFailed { message } => { + assert_eq!(message, "deterministic failure"); + } + } + } + } + + #[test] + fn mock_signer_maps_invalid_signed_event_parts() { + let pubkey = hex_64('a'); + let signer = MockSigner::new(pubkey.as_str()); + let mut draft = draft_for(pubkey.as_str()); + draft.expected_event_id = "bad-id".to_string(); let err = signer.sign_frozen_draft(&draft).expect_err("failure"); - assert_eq!( - err, - RadrootsSignerError::SigningFailed { - message: "deterministic failure".to_owned() - } - ); + assert!(matches!(err, RadrootsSignerError::SigningFailed { .. })); } }