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 }