lib

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

list_set.rs (8692B)


      1 #[path = "../src/test_fixtures.rs"]
      2 mod test_fixtures;
      3 
      4 use radroots_events::{
      5     kinds::{KIND_LIST_SET_FOLLOW, KIND_POST},
      6     list::RadrootsListEntry,
      7     list_set::RadrootsListSet,
      8 };
      9 use radroots_events_codec::error::{EventEncodeError, EventParseError};
     10 use radroots_events_codec::list_set::decode::{
     11     data_from_event, list_set_from_tags, parsed_from_event,
     12 };
     13 use radroots_events_codec::list_set::encode::{list_set_build_tags, to_wire_parts_with_kind};
     14 use test_fixtures::APP_PRIMARY_HTTPS;
     15 
     16 fn app_url(path: &str) -> String {
     17     format!("{APP_PRIMARY_HTTPS}/{path}")
     18 }
     19 
     20 fn sample_list_set() -> RadrootsListSet {
     21     RadrootsListSet {
     22         d_tag: "members.owners".to_string(),
     23         content: "private".to_string(),
     24         entries: vec![
     25             RadrootsListEntry {
     26                 tag: "p".to_string(),
     27                 values: vec!["owner".to_string()],
     28             },
     29             RadrootsListEntry {
     30                 tag: "t".to_string(),
     31                 values: vec!["orchard".to_string()],
     32             },
     33         ],
     34         title: Some("owners".to_string()),
     35         description: Some("core team".to_string()),
     36         image: Some(app_url("team.png")),
     37     }
     38 }
     39 
     40 #[test]
     41 fn list_set_build_tags_and_decode_roundtrip() {
     42     let list_set = sample_list_set();
     43     let tags = list_set_build_tags(&list_set).unwrap();
     44     let decoded =
     45         list_set_from_tags(KIND_LIST_SET_FOLLOW, list_set.content.clone(), &tags).unwrap();
     46     assert_eq!(decoded.d_tag, list_set.d_tag);
     47     assert_eq!(decoded.title, list_set.title);
     48     assert_eq!(decoded.description, list_set.description);
     49     assert_eq!(decoded.image, list_set.image);
     50     assert_eq!(decoded.entries.len(), list_set.entries.len());
     51     assert_eq!(decoded.entries[0].tag, list_set.entries[0].tag);
     52     assert_eq!(decoded.entries[0].values, list_set.entries[0].values);
     53     assert_eq!(decoded.entries[1].tag, list_set.entries[1].tag);
     54     assert_eq!(decoded.entries[1].values, list_set.entries[1].values);
     55 }
     56 
     57 #[test]
     58 fn list_set_encode_and_decode_reject_invalid_inputs() {
     59     let invalid = RadrootsListSet {
     60         d_tag: " ".to_string(),
     61         content: "".to_string(),
     62         entries: Vec::new(),
     63         title: None,
     64         description: None,
     65         image: None,
     66     };
     67     let err = list_set_build_tags(&invalid).unwrap_err();
     68     assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag")));
     69     let err = to_wire_parts_with_kind(&invalid, KIND_LIST_SET_FOLLOW).unwrap_err();
     70     assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag")));
     71 
     72     let invalid = RadrootsListSet {
     73         d_tag: "farm:invalid:owners".to_string(),
     74         content: "".to_string(),
     75         entries: Vec::new(),
     76         title: None,
     77         description: None,
     78         image: None,
     79     };
     80     let err = list_set_build_tags(&invalid).unwrap_err();
     81     assert!(matches!(err, EventEncodeError::InvalidField("d_tag")));
     82 
     83     let err = to_wire_parts_with_kind(&sample_list_set(), KIND_POST).unwrap_err();
     84     assert!(matches!(err, EventEncodeError::InvalidKind(KIND_POST)));
     85 
     86     let err = list_set_from_tags(KIND_POST, "".to_string(), &[]).unwrap_err();
     87     assert!(matches!(
     88         err,
     89         EventParseError::InvalidKind {
     90             expected: "nip51 list set kind",
     91             got: KIND_POST
     92         }
     93     ));
     94 
     95     let mut invalid_entry_tag = sample_list_set();
     96     invalid_entry_tag.entries[0].tag = " ".to_string();
     97     let err = list_set_build_tags(&invalid_entry_tag).unwrap_err();
     98     assert!(matches!(
     99         err,
    100         EventEncodeError::EmptyRequiredField("entry.tag")
    101     ));
    102 
    103     let mut invalid_entry_values = sample_list_set();
    104     invalid_entry_values.entries[0].values.clear();
    105     let err = list_set_build_tags(&invalid_entry_values).unwrap_err();
    106     assert!(matches!(
    107         err,
    108         EventEncodeError::EmptyRequiredField("entry.values")
    109     ));
    110 
    111     let mut invalid_entry_first = sample_list_set();
    112     invalid_entry_first.entries[0].values = vec![" ".to_string()];
    113     let err = list_set_build_tags(&invalid_entry_first).unwrap_err();
    114     assert!(matches!(
    115         err,
    116         EventEncodeError::EmptyRequiredField("entry.values")
    117     ));
    118 }
    119 
    120 #[test]
    121 fn list_set_decode_rejects_invalid_tag_shapes() {
    122     let err = list_set_from_tags(KIND_LIST_SET_FOLLOW, "".to_string(), &[]).unwrap_err();
    123     assert!(matches!(err, EventParseError::MissingTag("d")));
    124 
    125     let err = list_set_from_tags(
    126         KIND_LIST_SET_FOLLOW,
    127         "".to_string(),
    128         &[vec!["d".to_string(), " ".to_string()]],
    129     )
    130     .unwrap_err();
    131     assert!(matches!(err, EventParseError::InvalidTag("d")));
    132 
    133     let err = list_set_from_tags(
    134         KIND_LIST_SET_FOLLOW,
    135         "".to_string(),
    136         &[vec!["".to_string(), "value".to_string()]],
    137     )
    138     .unwrap_err();
    139     assert!(matches!(err, EventParseError::InvalidTag("tag")));
    140 
    141     let err = list_set_from_tags(
    142         KIND_LIST_SET_FOLLOW,
    143         "".to_string(),
    144         &[
    145             vec!["d".to_string(), "members.owners".to_string()],
    146             vec!["p".to_string(), " ".to_string()],
    147         ],
    148     )
    149     .unwrap_err();
    150     assert!(matches!(err, EventParseError::InvalidTag("tag")));
    151 
    152     let err = list_set_from_tags(
    153         KIND_LIST_SET_FOLLOW,
    154         "".to_string(),
    155         &[
    156             vec!["d".to_string(), "farm:invalid:members".to_string()],
    157             vec!["p".to_string(), "owner".to_string()],
    158         ],
    159     )
    160     .unwrap_err();
    161     assert!(matches!(err, EventParseError::InvalidTag("d")));
    162 }
    163 
    164 #[test]
    165 fn list_set_metadata_and_index_from_event_roundtrip() {
    166     let list_set = sample_list_set();
    167     let parts = to_wire_parts_with_kind(&list_set, KIND_LIST_SET_FOLLOW).unwrap();
    168 
    169     let metadata = data_from_event(
    170         "id".to_string(),
    171         "author".to_string(),
    172         44,
    173         KIND_LIST_SET_FOLLOW,
    174         parts.content.clone(),
    175         parts.tags.clone(),
    176     )
    177     .unwrap();
    178     assert_eq!(metadata.id, "id");
    179     assert_eq!(metadata.author, "author");
    180     assert_eq!(metadata.published_at, 44);
    181     assert_eq!(metadata.kind, KIND_LIST_SET_FOLLOW);
    182     assert_eq!(metadata.data.d_tag, "members.owners");
    183 
    184     let index = parsed_from_event(
    185         "id".to_string(),
    186         "author".to_string(),
    187         44,
    188         KIND_LIST_SET_FOLLOW,
    189         parts.content,
    190         parts.tags,
    191         "sig".to_string(),
    192     )
    193     .unwrap();
    194     assert_eq!(index.event.kind, KIND_LIST_SET_FOLLOW);
    195     assert_eq!(index.event.sig, "sig");
    196     assert_eq!(index.data.data.entries.len(), 2);
    197 }
    198 
    199 #[test]
    200 fn list_set_index_from_event_propagates_parse_errors() {
    201     let err = parsed_from_event(
    202         "id".to_string(),
    203         "author".to_string(),
    204         44,
    205         KIND_POST,
    206         "private".to_string(),
    207         Vec::new(),
    208         "sig".to_string(),
    209     )
    210     .unwrap_err();
    211     assert!(matches!(
    212         err,
    213         EventParseError::InvalidKind {
    214             expected: "nip51 list set kind",
    215             got: KIND_POST
    216         }
    217     ));
    218 }
    219 
    220 #[test]
    221 fn list_set_decode_keeps_first_optional_display_tags() {
    222     let tags = vec![
    223         vec!["d".to_string(), "members.owners".to_string()],
    224         vec!["title".to_string(), "owners".to_string()],
    225         vec!["title".to_string(), "ignored".to_string()],
    226         vec!["description".to_string(), "team".to_string()],
    227         vec!["description".to_string(), "ignored".to_string()],
    228         vec!["image".to_string(), app_url("a.png")],
    229         vec!["image".to_string(), app_url("b.png")],
    230         vec!["p".to_string(), "owner".to_string()],
    231     ];
    232     let decoded = list_set_from_tags(KIND_LIST_SET_FOLLOW, "private".to_string(), &tags).unwrap();
    233     let expected_image = app_url("a.png");
    234     assert_eq!(decoded.title.as_deref(), Some("owners"));
    235     assert_eq!(decoded.description.as_deref(), Some("team"));
    236     assert_eq!(decoded.image.as_deref(), Some(expected_image.as_str()));
    237 }
    238 
    239 #[test]
    240 fn list_set_decode_keeps_first_d_tag() {
    241     let tags = vec![
    242         vec!["d".to_string(), "members.owners".to_string()],
    243         vec!["d".to_string(), "members.ignore".to_string()],
    244         vec!["p".to_string(), "owner".to_string()],
    245     ];
    246     let decoded = list_set_from_tags(KIND_LIST_SET_FOLLOW, "private".to_string(), &tags).unwrap();
    247     assert_eq!(decoded.d_tag, "members.owners");
    248 }
    249 
    250 #[test]
    251 fn list_set_build_tags_omits_blank_optional_metadata() {
    252     let mut list_set = sample_list_set();
    253     list_set.title = Some(" ".to_string());
    254     list_set.description = Some(" ".to_string());
    255     list_set.image = Some(" ".to_string());
    256     let tags = list_set_build_tags(&list_set).unwrap();
    257     assert!(!tags.iter().any(|tag| tag[0] == "title"));
    258     assert!(!tags.iter().any(|tag| tag[0] == "description"));
    259     assert!(!tags.iter().any(|tag| tag[0] == "image"));
    260 }