lib

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

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 }