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 }