lib

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

nip22_comment_profile.rs (7405B)


      1 mod support;
      2 
      3 use nostr::{Keys as RadrootsNostrKeys, SecretKey as RadrootsNostrSecretKey};
      4 use radroots_event::{
      5     envelope::kind::{
      6         KIND_CALENDAR_DATE_EVENT, KIND_CALENDAR_TIME_EVENT, KIND_CLASSIFIED_LISTING, KIND_COMMENT,
      7     },
      8     post::comment::{
      9         AuthoredNip22Comment, Nip22AddressRootReference, Nip22CommentParentReference,
     10         Nip22EventRootReference,
     11     },
     12 };
     13 use radroots_event_codec::{
     14     admission::comment::verify_and_admit_nip22_comment_event,
     15     decode::comment::RadrootsInboundNip22CommentPosition,
     16 };
     17 use radroots_nostr::Error;
     18 use radroots_nostr::event::Kind as RadrootsNostrKind;
     19 use radroots_nostr::event::Timestamp as RadrootsNostrTimestamp;
     20 use radroots_nostr::event::from_nostr;
     21 use radroots_nostr::event::{GenericBuilder, build_nip22_comment as build_nip22_comment_event};
     22 use support::{
     23     FIXTURE_ALICE_SECRET_KEY_HEX, FIXTURE_BOB_PUBLIC_KEY_HEX, FIXTURE_CAROL_PUBLIC_KEY_HEX,
     24     RELAY_PRIMARY_WSS, RELAY_SECONDARY_WSS,
     25 };
     26 
     27 const CREATED_AT: u64 = 1_784_347_200;
     28 const ROOT_EVENT_ID: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
     29 const ADDRESS_REVISION_ID: &str =
     30     "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
     31 const NESTED_ROOT_EVENT_ID: &str =
     32     "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc";
     33 const PARENT_EVENT_ID: &str = "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd";
     34 
     35 #[test]
     36 fn typed_nip22_comment_builders_sign_and_admit_all_exact_shapes() {
     37     let keys = fixture_keys();
     38     let created_at = RadrootsNostrTimestamp::from_secs(CREATED_AT);
     39 
     40     let top_event = AuthoredNip22Comment::top_level_event(
     41         "Are these carrots available Saturday?",
     42         Nip22EventRootReference::parse(
     43             ROOT_EVENT_ID,
     44             FIXTURE_BOB_PUBLIC_KEY_HEX,
     45             KIND_CLASSIFIED_LISTING,
     46             Some(RELAY_PRIMARY_WSS),
     47         )
     48         .expect("event root"),
     49     )
     50     .expect("top-level event Comment");
     51     let top_event_tags = vec![
     52         tag(&[
     53             "E",
     54             ROOT_EVENT_ID,
     55             RELAY_PRIMARY_WSS,
     56             FIXTURE_BOB_PUBLIC_KEY_HEX,
     57         ]),
     58         tag(&["K", "30402"]),
     59         tag(&["P", FIXTURE_BOB_PUBLIC_KEY_HEX, RELAY_PRIMARY_WSS]),
     60         tag(&[
     61             "e",
     62             ROOT_EVENT_ID,
     63             RELAY_PRIMARY_WSS,
     64             FIXTURE_BOB_PUBLIC_KEY_HEX,
     65         ]),
     66         tag(&["k", "30402"]),
     67         tag(&["p", FIXTURE_BOB_PUBLIC_KEY_HEX, RELAY_PRIMARY_WSS]),
     68     ];
     69 
     70     let address =
     71         format!("{KIND_CALENDAR_DATE_EVENT}:{FIXTURE_BOB_PUBLIC_KEY_HEX}:victoria-market");
     72     let top_address = AuthoredNip22Comment::parse_top_level_address(
     73         "Looking forward to the Victoria market.",
     74         Nip22AddressRootReference::parse(&address, Some(RELAY_SECONDARY_WSS))
     75             .expect("address root"),
     76         ADDRESS_REVISION_ID,
     77     )
     78     .expect("top-level address Comment");
     79     let top_address_tags = vec![
     80         tag(&["A", &address, RELAY_SECONDARY_WSS]),
     81         tag(&["K", "31922"]),
     82         tag(&["P", FIXTURE_BOB_PUBLIC_KEY_HEX, RELAY_SECONDARY_WSS]),
     83         tag(&["a", &address, RELAY_SECONDARY_WSS]),
     84         tag(&["e", ADDRESS_REVISION_ID, RELAY_SECONDARY_WSS]),
     85         tag(&["k", "31922"]),
     86         tag(&["p", FIXTURE_BOB_PUBLIC_KEY_HEX, RELAY_SECONDARY_WSS]),
     87     ];
     88 
     89     let nested_event = AuthoredNip22Comment::nested(
     90         "Harvest starts Friday morning.",
     91         Nip22EventRootReference::parse(
     92             NESTED_ROOT_EVENT_ID,
     93             FIXTURE_BOB_PUBLIC_KEY_HEX,
     94             KIND_CALENDAR_TIME_EVENT,
     95             None,
     96         )
     97         .expect("nested event root"),
     98         parent(Some(RELAY_SECONDARY_WSS)),
     99     )
    100     .expect("nested event Comment");
    101     let nested_event_tags = vec![
    102         tag(&["E", NESTED_ROOT_EVENT_ID, "", FIXTURE_BOB_PUBLIC_KEY_HEX]),
    103         tag(&["K", "31923"]),
    104         tag(&["P", FIXTURE_BOB_PUBLIC_KEY_HEX]),
    105         tag(&[
    106             "e",
    107             PARENT_EVENT_ID,
    108             RELAY_SECONDARY_WSS,
    109             FIXTURE_CAROL_PUBLIC_KEY_HEX,
    110         ]),
    111         tag(&["k", "1111"]),
    112         tag(&["p", FIXTURE_CAROL_PUBLIC_KEY_HEX, RELAY_SECONDARY_WSS]),
    113     ];
    114 
    115     let nested_address_value =
    116         format!("{KIND_CLASSIFIED_LISTING}:{FIXTURE_BOB_PUBLIC_KEY_HEX}:carrots");
    117     let nested_address = AuthoredNip22Comment::nested(
    118         "I can pick up two bunches.",
    119         Nip22AddressRootReference::parse(&nested_address_value, Some(RELAY_PRIMARY_WSS))
    120             .expect("nested address root"),
    121         parent(None),
    122     )
    123     .expect("nested address Comment");
    124     let nested_address_tags = vec![
    125         tag(&["A", &nested_address_value, RELAY_PRIMARY_WSS]),
    126         tag(&["K", "30402"]),
    127         tag(&["P", FIXTURE_BOB_PUBLIC_KEY_HEX, RELAY_PRIMARY_WSS]),
    128         tag(&["e", PARENT_EVENT_ID, "", FIXTURE_CAROL_PUBLIC_KEY_HEX]),
    129         tag(&["k", "1111"]),
    130         tag(&["p", FIXTURE_CAROL_PUBLIC_KEY_HEX]),
    131     ];
    132 
    133     for (comment, expected_tags, expected_position) in [
    134         (top_event, top_event_tags, "top_event"),
    135         (top_address, top_address_tags, "top_address"),
    136         (nested_event, nested_event_tags, "nested"),
    137         (nested_address, nested_address_tags, "nested"),
    138     ] {
    139         let event = build_nip22_comment_event(&comment)
    140             .expect("typed Comment builder")
    141             .custom_created_at(created_at)
    142             .sign_with_keys(&keys)
    143             .expect("signed Comment");
    144         assert_eq!(event.kind.as_u16(), KIND_COMMENT as u16);
    145         assert_eq!(event.created_at, created_at);
    146         assert_eq!(event_tags(&event), expected_tags);
    147         event.verify().expect("valid Comment signature");
    148 
    149         let admitted = verify_and_admit_nip22_comment_event(
    150             from_nostr(&event).expect("Comment event adapter"),
    151         )
    152         .expect("Comment admission");
    153         assert_eq!(
    154             position_label(admitted.projection().position()),
    155             expected_position
    156         );
    157     }
    158 }
    159 
    160 #[test]
    161 fn generic_kind_1111_builder_cannot_bypass_typed_comment_authoring() {
    162     let kind = RadrootsNostrKind::Custom(KIND_COMMENT as u16);
    163     let error = GenericBuilder::new(kind, "Raw Comment")
    164         .sign_with_keys(&fixture_keys())
    165         .expect_err("generic kind 1111 must be reserved");
    166 
    167     assert!(matches!(
    168         error,
    169         Error::TypedAuthoringRequired { kind: actual }
    170             if actual == KIND_COMMENT as u16
    171     ));
    172 }
    173 
    174 fn fixture_keys() -> RadrootsNostrKeys {
    175     RadrootsNostrKeys::new(
    176         RadrootsNostrSecretKey::from_hex(FIXTURE_ALICE_SECRET_KEY_HEX).expect("fixture secret key"),
    177     )
    178 }
    179 
    180 fn parent(relay: Option<&str>) -> Nip22CommentParentReference {
    181     Nip22CommentParentReference::parse(PARENT_EVENT_ID, FIXTURE_CAROL_PUBLIC_KEY_HEX, relay)
    182         .expect("Comment parent")
    183 }
    184 
    185 fn event_tags(event: &nostr::Event) -> Vec<Vec<String>> {
    186     event
    187         .tags
    188         .iter()
    189         .map(|tag| tag.as_slice().to_vec())
    190         .collect()
    191 }
    192 
    193 fn position_label(position: &RadrootsInboundNip22CommentPosition) -> &'static str {
    194     match position {
    195         RadrootsInboundNip22CommentPosition::TopLevelEvent { .. } => "top_event",
    196         RadrootsInboundNip22CommentPosition::TopLevelAddress { .. } => "top_address",
    197         RadrootsInboundNip22CommentPosition::Nested { .. } => "nested",
    198     }
    199 }
    200 
    201 fn tag(values: &[&str]) -> Vec<String> {
    202     values.iter().map(|value| (*value).to_owned()).collect()
    203 }