group.rs (14372B)
1 use radroots_events::group::{ 2 KIND_GROUP_ADMINS, KIND_GROUP_CREATE_GROUP, KIND_GROUP_CREATE_INVITE, KIND_GROUP_DELETE_EVENT, 3 KIND_GROUP_DELETE_GROUP, KIND_GROUP_EDIT_METADATA, KIND_GROUP_JOIN_REQUEST, 4 KIND_GROUP_LEAVE_REQUEST, KIND_GROUP_MEMBERS, KIND_GROUP_METADATA, KIND_GROUP_PUT_USER, 5 KIND_GROUP_REMOVE_USER, KIND_GROUP_ROLES, RadrootsGroupAdmins, RadrootsGroupCreateGroup, 6 RadrootsGroupCreateInvite, RadrootsGroupDeleteEvent, RadrootsGroupDeleteGroup, 7 RadrootsGroupEditMetadata, RadrootsGroupEditableMetadata, RadrootsGroupJoinRequest, 8 RadrootsGroupLeaveRequest, RadrootsGroupMembers, RadrootsGroupMetadata, RadrootsGroupPutUser, 9 RadrootsGroupRemoveUser, RadrootsGroupRole, RadrootsGroupRoles, RadrootsGroupUserRef, 10 }; 11 use radroots_events_codec::error::{EventEncodeError, EventParseError}; 12 use radroots_events_codec::group::decode::{ 13 group_admins_from_event, group_create_group_from_event, group_create_invite_from_event, 14 group_delete_event_from_event, group_delete_group_from_event, group_edit_metadata_from_event, 15 group_join_request_from_event, group_leave_request_from_event, group_members_from_event, 16 group_metadata_from_event, group_put_user_from_event, group_remove_user_from_event, 17 group_roles_from_event, 18 }; 19 use radroots_events_codec::group::encode::{ 20 group_admins_to_wire_parts, group_create_group_to_wire_parts, 21 group_create_invite_to_wire_parts, group_delete_event_to_wire_parts, 22 group_delete_group_to_wire_parts, group_edit_metadata_to_wire_parts, 23 group_join_request_to_wire_parts, group_leave_request_to_wire_parts, 24 group_members_to_wire_parts, group_metadata_to_wire_parts, group_put_user_to_wire_parts, 25 group_remove_user_to_wire_parts, group_roles_to_wire_parts, 26 }; 27 28 const GROUP_ID: &str = "field-group"; 29 const PUBKEY: &str = "member_pubkey"; 30 31 #[test] 32 fn group_public_codecs_roundtrip_all_group_wire_shapes() { 33 let put = RadrootsGroupPutUser { 34 group_id: GROUP_ID.to_string(), 35 message: None, 36 pubkey: PUBKEY.to_string(), 37 roles: Vec::new(), 38 }; 39 let remove = RadrootsGroupRemoveUser { 40 group_id: GROUP_ID.to_string(), 41 message: Some("remove member".to_string()), 42 pubkey: PUBKEY.to_string(), 43 }; 44 let create = RadrootsGroupCreateGroup { 45 group_id: GROUP_ID.to_string(), 46 message: Some("create group".to_string()), 47 metadata: full_metadata(), 48 }; 49 let edit = RadrootsGroupEditMetadata { 50 group_id: GROUP_ID.to_string(), 51 message: None, 52 metadata: minimal_metadata(), 53 }; 54 let delete_group = RadrootsGroupDeleteGroup { 55 group_id: GROUP_ID.to_string(), 56 message: None, 57 }; 58 let delete_event = RadrootsGroupDeleteEvent { 59 group_id: GROUP_ID.to_string(), 60 message: Some("delete event".to_string()), 61 event_id: "event_id".to_string(), 62 }; 63 let invite = RadrootsGroupCreateInvite { 64 group_id: GROUP_ID.to_string(), 65 message: None, 66 code: "invite-code".to_string(), 67 }; 68 let join = RadrootsGroupJoinRequest { 69 group_id: GROUP_ID.to_string(), 70 message: None, 71 code: None, 72 }; 73 let leave = RadrootsGroupLeaveRequest { 74 group_id: GROUP_ID.to_string(), 75 message: None, 76 }; 77 let metadata = RadrootsGroupMetadata { 78 d_tag: GROUP_ID.to_string(), 79 metadata: minimal_metadata(), 80 }; 81 let admins = RadrootsGroupAdmins { 82 d_tag: GROUP_ID.to_string(), 83 description: None, 84 admins: vec![ 85 RadrootsGroupUserRef { 86 pubkey: "admin_pubkey".to_string(), 87 roles: vec!["admin".to_string()], 88 }, 89 RadrootsGroupUserRef { 90 pubkey: "observer_pubkey".to_string(), 91 roles: Vec::new(), 92 }, 93 ], 94 }; 95 let members = RadrootsGroupMembers { 96 d_tag: GROUP_ID.to_string(), 97 description: Some("group members".to_string()), 98 members: vec![RadrootsGroupUserRef { 99 pubkey: PUBKEY.to_string(), 100 roles: vec!["member".to_string()], 101 }], 102 }; 103 let roles = RadrootsGroupRoles { 104 d_tag: GROUP_ID.to_string(), 105 description: None, 106 roles: vec![ 107 RadrootsGroupRole { 108 name: "admin".to_string(), 109 description: Some("full access".to_string()), 110 permissions: vec!["read".to_string(), "write".to_string()], 111 }, 112 RadrootsGroupRole { 113 name: "viewer".to_string(), 114 description: None, 115 permissions: Vec::new(), 116 }, 117 ], 118 }; 119 120 let put_parts = group_put_user_to_wire_parts(&put).unwrap(); 121 let remove_parts = group_remove_user_to_wire_parts(&remove).unwrap(); 122 let create_parts = group_create_group_to_wire_parts(&create).unwrap(); 123 let edit_parts = group_edit_metadata_to_wire_parts(&edit).unwrap(); 124 let delete_group_parts = group_delete_group_to_wire_parts(&delete_group).unwrap(); 125 let delete_event_parts = group_delete_event_to_wire_parts(&delete_event).unwrap(); 126 let invite_parts = group_create_invite_to_wire_parts(&invite).unwrap(); 127 let join_parts = group_join_request_to_wire_parts(&join).unwrap(); 128 let leave_parts = group_leave_request_to_wire_parts(&leave).unwrap(); 129 let metadata_parts = group_metadata_to_wire_parts(&metadata).unwrap(); 130 let admins_parts = group_admins_to_wire_parts(&admins).unwrap(); 131 let members_parts = group_members_to_wire_parts(&members).unwrap(); 132 let roles_parts = group_roles_to_wire_parts(&roles).unwrap(); 133 134 assert_eq!(put_parts.kind, KIND_GROUP_PUT_USER); 135 assert_eq!(remove_parts.kind, KIND_GROUP_REMOVE_USER); 136 assert_eq!(create_parts.kind, KIND_GROUP_CREATE_GROUP); 137 assert_eq!(edit_parts.kind, KIND_GROUP_EDIT_METADATA); 138 assert_eq!(delete_group_parts.kind, KIND_GROUP_DELETE_GROUP); 139 assert_eq!(delete_event_parts.kind, KIND_GROUP_DELETE_EVENT); 140 assert_eq!(invite_parts.kind, KIND_GROUP_CREATE_INVITE); 141 assert_eq!(join_parts.kind, KIND_GROUP_JOIN_REQUEST); 142 assert_eq!(leave_parts.kind, KIND_GROUP_LEAVE_REQUEST); 143 assert_eq!(metadata_parts.kind, KIND_GROUP_METADATA); 144 assert_eq!(admins_parts.kind, KIND_GROUP_ADMINS); 145 assert_eq!(members_parts.kind, KIND_GROUP_MEMBERS); 146 assert_eq!(roles_parts.kind, KIND_GROUP_ROLES); 147 148 assert_eq!( 149 group_put_user_from_event(put_parts.kind, &put_parts.tags, &put_parts.content).unwrap(), 150 put 151 ); 152 assert_eq!( 153 group_remove_user_from_event(remove_parts.kind, &remove_parts.tags, &remove_parts.content) 154 .unwrap(), 155 remove 156 ); 157 assert_eq!( 158 group_create_group_from_event(create_parts.kind, &create_parts.tags, &create_parts.content) 159 .unwrap(), 160 create 161 ); 162 assert_eq!( 163 group_edit_metadata_from_event(edit_parts.kind, &edit_parts.tags, &edit_parts.content) 164 .unwrap(), 165 edit 166 ); 167 assert_eq!( 168 group_delete_group_from_event( 169 delete_group_parts.kind, 170 &delete_group_parts.tags, 171 &delete_group_parts.content 172 ) 173 .unwrap(), 174 delete_group 175 ); 176 assert_eq!( 177 group_delete_event_from_event( 178 delete_event_parts.kind, 179 &delete_event_parts.tags, 180 &delete_event_parts.content 181 ) 182 .unwrap(), 183 delete_event 184 ); 185 assert_eq!( 186 group_create_invite_from_event( 187 invite_parts.kind, 188 &invite_parts.tags, 189 &invite_parts.content 190 ) 191 .unwrap(), 192 invite 193 ); 194 assert_eq!( 195 group_join_request_from_event(join_parts.kind, &join_parts.tags, &join_parts.content) 196 .unwrap(), 197 join 198 ); 199 assert_eq!( 200 group_leave_request_from_event(leave_parts.kind, &leave_parts.tags, &leave_parts.content) 201 .unwrap(), 202 leave 203 ); 204 assert_eq!( 205 group_metadata_from_event( 206 metadata_parts.kind, 207 &metadata_parts.tags, 208 &metadata_parts.content 209 ) 210 .unwrap(), 211 metadata 212 ); 213 assert_eq!( 214 group_admins_from_event(admins_parts.kind, &admins_parts.tags, &admins_parts.content) 215 .unwrap(), 216 admins 217 ); 218 assert_eq!( 219 group_members_from_event( 220 members_parts.kind, 221 &members_parts.tags, 222 &members_parts.content 223 ) 224 .unwrap(), 225 members 226 ); 227 assert_eq!( 228 group_roles_from_event(roles_parts.kind, &roles_parts.tags, &roles_parts.content).unwrap(), 229 roles 230 ); 231 } 232 233 #[test] 234 fn group_public_codecs_reject_invalid_decode_shapes() { 235 assert!(matches!( 236 group_put_user_from_event(KIND_GROUP_REMOVE_USER, &[], "").unwrap_err(), 237 EventParseError::InvalidKind { 238 expected: "9000", 239 got: KIND_GROUP_REMOVE_USER 240 } 241 )); 242 assert!(matches!( 243 group_put_user_from_event(KIND_GROUP_PUT_USER, &[tag("h", GROUP_ID)], "").unwrap_err(), 244 EventParseError::MissingTag("p") 245 )); 246 assert!(matches!( 247 group_metadata_from_event(KIND_GROUP_METADATA, &[tag("d", GROUP_ID)], "content") 248 .unwrap_err(), 249 EventParseError::InvalidJson("content") 250 )); 251 assert!(matches!( 252 group_metadata_from_event( 253 KIND_GROUP_METADATA, 254 &[tag("d", GROUP_ID), tag("private", "true")], 255 "" 256 ) 257 .unwrap_err(), 258 EventParseError::InvalidTag("private") 259 )); 260 assert!(matches!( 261 group_metadata_from_event( 262 KIND_GROUP_METADATA, 263 &[ 264 tag("d", GROUP_ID), 265 tag("supported_kinds", "78"), 266 tag("supported_kinds", "30078") 267 ], 268 "" 269 ) 270 .unwrap_err(), 271 EventParseError::InvalidTag("supported_kinds") 272 )); 273 assert!(matches!( 274 group_metadata_from_event(KIND_GROUP_METADATA, &[tag("d", GROUP_ID)], "").unwrap(), 275 RadrootsGroupMetadata { .. } 276 )); 277 assert!(matches!( 278 group_metadata_from_event( 279 KIND_GROUP_METADATA, 280 &[tag("d", GROUP_ID), tag("supported_kinds", "bad")], 281 "" 282 ) 283 .unwrap_err(), 284 EventParseError::InvalidNumber("supported_kinds", _) 285 )); 286 assert!(matches!( 287 group_put_user_from_event( 288 KIND_GROUP_PUT_USER, 289 &[ 290 tag("h", GROUP_ID), 291 vec!["p".to_string(), PUBKEY.to_string(), "".to_string()] 292 ], 293 "" 294 ) 295 .unwrap_err(), 296 EventParseError::InvalidTag("p") 297 )); 298 assert!(matches!( 299 group_roles_from_event( 300 KIND_GROUP_ROLES, 301 &[ 302 tag("d", GROUP_ID), 303 vec!["role".to_string(), "member".to_string(), "".to_string()] 304 ], 305 "" 306 ) 307 .unwrap_err(), 308 EventParseError::InvalidTag("role") 309 )); 310 assert!(matches!( 311 group_roles_from_event( 312 KIND_GROUP_ROLES, 313 &[ 314 tag("d", GROUP_ID), 315 vec![ 316 "role".to_string(), 317 "member".to_string(), 318 "can read".to_string(), 319 "".to_string() 320 ] 321 ], 322 "" 323 ) 324 .unwrap_err(), 325 EventParseError::InvalidTag("role") 326 )); 327 } 328 329 #[test] 330 fn group_public_encoders_reject_empty_required_fields() { 331 assert_empty_required( 332 group_create_group_to_wire_parts(&RadrootsGroupCreateGroup { 333 group_id: GROUP_ID.to_string(), 334 message: Some(String::new()), 335 metadata: minimal_metadata(), 336 }), 337 "message", 338 ); 339 assert_empty_required( 340 group_edit_metadata_to_wire_parts(&RadrootsGroupEditMetadata { 341 group_id: GROUP_ID.to_string(), 342 message: None, 343 metadata: RadrootsGroupEditableMetadata { 344 about: Some(String::new()), 345 ..minimal_metadata() 346 }, 347 }), 348 "about", 349 ); 350 assert_empty_required( 351 group_join_request_to_wire_parts(&RadrootsGroupJoinRequest { 352 group_id: GROUP_ID.to_string(), 353 message: None, 354 code: Some(String::new()), 355 }), 356 "code", 357 ); 358 assert_empty_required( 359 group_roles_to_wire_parts(&RadrootsGroupRoles { 360 d_tag: GROUP_ID.to_string(), 361 description: None, 362 roles: vec![RadrootsGroupRole { 363 name: "member".to_string(), 364 description: Some(String::new()), 365 permissions: Vec::new(), 366 }], 367 }), 368 "role.description", 369 ); 370 assert_empty_required( 371 group_roles_to_wire_parts(&RadrootsGroupRoles { 372 d_tag: GROUP_ID.to_string(), 373 description: None, 374 roles: vec![RadrootsGroupRole { 375 name: "member".to_string(), 376 description: None, 377 permissions: vec![String::new()], 378 }], 379 }), 380 "role.permissions", 381 ); 382 } 383 384 fn full_metadata() -> RadrootsGroupEditableMetadata { 385 RadrootsGroupEditableMetadata { 386 name: Some("Small Regen Farm".to_string()), 387 about: Some("Field app group".to_string()), 388 picture: Some("https://media.example.invalid/group.png".to_string()), 389 is_private: true, 390 is_restricted: true, 391 is_closed: true, 392 is_hidden: true, 393 supported_kinds: Some(vec![78, 30078]), 394 } 395 } 396 397 fn minimal_metadata() -> RadrootsGroupEditableMetadata { 398 RadrootsGroupEditableMetadata { 399 name: None, 400 about: None, 401 picture: None, 402 is_private: false, 403 is_restricted: false, 404 is_closed: false, 405 is_hidden: false, 406 supported_kinds: None, 407 } 408 } 409 410 fn assert_empty_required<T>(result: Result<T, EventEncodeError>, field: &'static str) { 411 let err = match result { 412 Ok(_) => panic!("expected empty required field error"), 413 Err(err) => err, 414 }; 415 assert!(matches!( 416 err, 417 EventEncodeError::EmptyRequiredField(found) if found == field 418 )); 419 } 420 421 fn tag(key: &str, value: &str) -> Vec<String> { 422 vec![key.to_string(), value.to_string()] 423 }