relay_delivery.rs (11879B)
1 use radroots_local_events::{ 2 RelayDeliveryEvidence, RelayDeliveryFailure, RelayDeliveryState, 3 canonical_relay_set_fingerprint, 4 }; 5 use serde_json::json; 6 7 #[test] 8 fn pending_delivery_evidence_uses_canonical_json_shape() { 9 let evidence = RelayDeliveryEvidence::pending([ 10 " wss://relay-b.example ", 11 "wss://relay-a.example", 12 "wss://relay-b.example", 13 ]) 14 .expect("pending evidence"); 15 16 assert_eq!(evidence.state, RelayDeliveryState::Pending); 17 assert_eq!(evidence.state.as_str(), "pending"); 18 assert_eq!( 19 evidence.target_relays, 20 vec![ 21 "wss://relay-b.example".to_owned(), 22 "wss://relay-a.example".to_owned() 23 ] 24 ); 25 assert_eq!( 26 evidence.to_json_value().expect("json"), 27 json!({ 28 "state": "pending", 29 "target_relays": ["wss://relay-b.example", "wss://relay-a.example"], 30 "connected_relays": [], 31 "acknowledged_relays": [], 32 "failed_relays": [] 33 }) 34 ); 35 } 36 37 #[test] 38 fn acknowledged_delivery_evidence_uses_canonical_failure_fields() { 39 let evidence = RelayDeliveryEvidence::acknowledged( 40 ["wss://relay-a.example", "wss://relay-b.example"], 41 [" wss://relay-a.example "], 42 ["wss://relay-a.example"], 43 vec![RelayDeliveryFailure::new(" wss://relay-b.example ", " timeout ").expect("failure")], 44 ) 45 .expect("acknowledged evidence"); 46 47 assert_eq!(evidence.state.as_str(), "acknowledged"); 48 assert_eq!( 49 evidence.to_json_value().expect("json"), 50 json!({ 51 "state": "acknowledged", 52 "target_relays": ["wss://relay-a.example", "wss://relay-b.example"], 53 "connected_relays": ["wss://relay-a.example"], 54 "acknowledged_relays": ["wss://relay-a.example"], 55 "failed_relays": [ 56 {"relay_url": "wss://relay-b.example", "error": "timeout"} 57 ] 58 }) 59 ); 60 } 61 62 #[test] 63 fn observed_delivery_evidence_tracks_observed_relays_without_acknowledgement() { 64 let evidence = RelayDeliveryEvidence::observed( 65 ["wss://relay-a.example", "wss://relay-b.example"], 66 [" wss://relay-a.example ", "wss://relay-b.example"], 67 ["wss://relay-b.example"], 68 Vec::new(), 69 ) 70 .expect("observed evidence"); 71 72 assert_eq!(evidence.state, RelayDeliveryState::Observed); 73 assert_eq!(evidence.state.as_str(), "observed"); 74 assert!(evidence.acknowledged_relays.is_empty()); 75 assert_eq!( 76 evidence.to_json_value().expect("json"), 77 json!({ 78 "state": "observed", 79 "target_relays": ["wss://relay-a.example", "wss://relay-b.example"], 80 "connected_relays": ["wss://relay-a.example", "wss://relay-b.example"], 81 "acknowledged_relays": [], 82 "observed_relays": ["wss://relay-b.example"], 83 "failed_relays": [] 84 }) 85 ); 86 } 87 88 #[test] 89 fn observed_delivery_evidence_allows_unknown_exact_relay_when_connected() { 90 let evidence = RelayDeliveryEvidence::observed( 91 ["wss://relay-a.example", "wss://relay-b.example"], 92 ["wss://relay-a.example", "wss://relay-b.example"], 93 Vec::<String>::new(), 94 Vec::new(), 95 ) 96 .expect("observed evidence"); 97 98 assert_eq!(evidence.state, RelayDeliveryState::Observed); 99 assert!(evidence.observed_relays.is_empty()); 100 assert_eq!( 101 evidence.to_json_value().expect("json"), 102 json!({ 103 "state": "observed", 104 "target_relays": ["wss://relay-a.example", "wss://relay-b.example"], 105 "connected_relays": ["wss://relay-a.example", "wss://relay-b.example"], 106 "acknowledged_relays": [], 107 "failed_relays": [] 108 }) 109 ); 110 } 111 112 #[test] 113 fn failed_delivery_evidence_requires_failures_without_acknowledgements() { 114 let evidence = RelayDeliveryEvidence::failed( 115 ["wss://relay-a.example"], 116 ["wss://relay-a.example"], 117 vec![RelayDeliveryFailure::new("wss://relay-a.example", "closed").expect("failure")], 118 ) 119 .expect("failed evidence"); 120 121 assert_eq!(evidence.state, RelayDeliveryState::Failed); 122 assert_eq!(evidence.state.as_str(), "failed"); 123 assert!(evidence.acknowledged_relays.is_empty()); 124 assert_eq!(evidence.failed_relays.len(), 1); 125 } 126 127 #[test] 128 fn acknowledged_delivery_evidence_rejects_observed_relays() { 129 let err = RelayDeliveryEvidence::from_json_value(&json!({ 130 "state": "acknowledged", 131 "target_relays": ["wss://relay-a.example"], 132 "connected_relays": ["wss://relay-a.example"], 133 "acknowledged_relays": ["wss://relay-a.example"], 134 "observed_relays": ["wss://relay-a.example"], 135 "failed_relays": [] 136 })) 137 .expect_err("invalid evidence"); 138 139 assert!(err.to_string().contains("observed_relays")); 140 } 141 142 #[test] 143 fn delivery_evidence_fingerprint_uses_target_relays() { 144 let evidence = RelayDeliveryEvidence::acknowledged( 145 ["wss://relay-b.example", "wss://relay-a.example"], 146 ["wss://relay-a.example"], 147 ["wss://relay-a.example"], 148 Vec::new(), 149 ) 150 .expect("evidence"); 151 152 assert_eq!( 153 evidence.relay_set_fingerprint(), 154 canonical_relay_set_fingerprint(["wss://relay-a.example", "wss://relay-b.example"]) 155 ); 156 } 157 158 #[test] 159 fn delivery_evidence_rejects_invalid_json_shape() { 160 let err = RelayDeliveryEvidence::from_json_value(&json!({ 161 "state": "acknowledged", 162 "target_relays": ["wss://relay-a.example"], 163 "connected_relays": [], 164 "acknowledged_relays": [], 165 "failed_relays": [] 166 })) 167 .expect_err("invalid evidence"); 168 169 assert!(err.to_string().contains("acknowledged_relays")); 170 } 171 172 #[test] 173 fn delivery_evidence_rejects_pending_and_failed_cross_state_fields() { 174 let pending_err = RelayDeliveryEvidence::from_json_value(&json!({ 175 "state": "pending", 176 "target_relays": ["wss://relay-a.example"], 177 "connected_relays": [], 178 "acknowledged_relays": [], 179 "observed_relays": ["wss://relay-a.example"], 180 "failed_relays": [ 181 {"relay_url": "wss://relay-a.example", "error": "timeout"} 182 ] 183 })) 184 .expect_err("pending evidence with terminal fields"); 185 186 assert!( 187 pending_err 188 .to_string() 189 .contains("pending delivery evidence") 190 ); 191 192 let failed_err = RelayDeliveryEvidence::from_json_value(&json!({ 193 "state": "failed", 194 "target_relays": ["wss://relay-a.example"], 195 "connected_relays": [], 196 "acknowledged_relays": ["wss://relay-a.example"], 197 "observed_relays": ["wss://relay-a.example"], 198 "failed_relays": [ 199 {"relay_url": "wss://relay-a.example", "error": "timeout"} 200 ] 201 })) 202 .expect_err("failed evidence with success fields"); 203 204 assert!(failed_err.to_string().contains("failed delivery evidence")); 205 } 206 207 #[test] 208 fn delivery_evidence_rejects_invalid_failure_and_relay_values() { 209 let normalized_failure_err = RelayDeliveryEvidence::from_json_value(&json!({ 210 "state": "failed", 211 "target_relays": ["wss://relay-a.example"], 212 "connected_relays": [], 213 "acknowledged_relays": [], 214 "failed_relays": [ 215 {"relay_url": " wss://relay-a.example ", "error": "timeout"} 216 ] 217 })) 218 .expect_err("non-normalized failure relay"); 219 220 assert!( 221 normalized_failure_err 222 .to_string() 223 .contains("failed_relays.relay_url") 224 ); 225 226 let trimmed_error_err = RelayDeliveryEvidence::from_json_value(&json!({ 227 "state": "failed", 228 "target_relays": ["wss://relay-a.example"], 229 "connected_relays": [], 230 "acknowledged_relays": [], 231 "failed_relays": [ 232 {"relay_url": "wss://relay-a.example", "error": " timeout "} 233 ] 234 })) 235 .expect_err("non-normalized failure text"); 236 237 assert!(trimmed_error_err.to_string().contains("must be trimmed")); 238 239 let constructor_err = 240 RelayDeliveryEvidence::pending(["http://relay-a.example"]).expect_err("invalid relay"); 241 242 assert!(constructor_err.to_string().contains("target_relays")); 243 } 244 245 #[test] 246 fn delivery_evidence_rejects_invalid_relay_sets_in_each_field() { 247 for (field, evidence) in [ 248 ( 249 "connected_relays", 250 RelayDeliveryEvidence { 251 state: RelayDeliveryState::Pending, 252 target_relays: vec!["wss://relay-a.example".to_owned()], 253 connected_relays: vec!["http://relay-a.example".to_owned()], 254 acknowledged_relays: Vec::new(), 255 observed_relays: Vec::new(), 256 failed_relays: Vec::new(), 257 }, 258 ), 259 ( 260 "acknowledged_relays", 261 RelayDeliveryEvidence { 262 state: RelayDeliveryState::Acknowledged, 263 target_relays: vec!["wss://relay-a.example".to_owned()], 264 connected_relays: Vec::new(), 265 acknowledged_relays: vec!["http://relay-a.example".to_owned()], 266 observed_relays: Vec::new(), 267 failed_relays: Vec::new(), 268 }, 269 ), 270 ( 271 "observed_relays", 272 RelayDeliveryEvidence { 273 state: RelayDeliveryState::Observed, 274 target_relays: vec!["wss://relay-a.example".to_owned()], 275 connected_relays: Vec::new(), 276 acknowledged_relays: Vec::new(), 277 observed_relays: vec!["http://relay-a.example".to_owned()], 278 failed_relays: Vec::new(), 279 }, 280 ), 281 ] { 282 let error = evidence.validate().expect_err("invalid relay set"); 283 284 assert!( 285 error.to_string().contains(field), 286 "expected error to contain {field}, got {error}" 287 ); 288 } 289 } 290 291 #[test] 292 fn delivery_evidence_rejects_constructor_and_json_error_paths() { 293 let empty_failure_error = RelayDeliveryEvidence::failed( 294 ["wss://relay-a.example"], 295 Vec::<String>::new(), 296 vec![RelayDeliveryFailure { 297 relay_url: "wss://relay-a.example".to_owned(), 298 error: " ".to_owned(), 299 }], 300 ) 301 .expect_err("empty failure error"); 302 303 assert!( 304 empty_failure_error 305 .to_string() 306 .contains("failed_relays.error") 307 ); 308 309 let invalid_json_error = RelayDeliveryEvidence::from_json_value(&json!({ 310 "state": 1, 311 "target_relays": [], 312 "connected_relays": [], 313 "acknowledged_relays": [], 314 "failed_relays": [] 315 })) 316 .expect_err("invalid json"); 317 318 assert!(!invalid_json_error.to_string().is_empty()); 319 320 let to_json_error = RelayDeliveryEvidence { 321 state: RelayDeliveryState::Pending, 322 target_relays: Vec::new(), 323 connected_relays: Vec::new(), 324 acknowledged_relays: Vec::new(), 325 observed_relays: Vec::new(), 326 failed_relays: Vec::new(), 327 } 328 .to_json_value() 329 .expect_err("invalid to json evidence"); 330 331 assert!(to_json_error.to_string().contains("target_relays")); 332 333 for result in [ 334 RelayDeliveryEvidence::acknowledged( 335 ["wss://relay-a.example"], 336 ["http://relay-a.example"], 337 ["wss://relay-a.example"], 338 Vec::new(), 339 ), 340 RelayDeliveryEvidence::acknowledged( 341 ["wss://relay-a.example"], 342 Vec::<String>::new(), 343 ["http://relay-a.example"], 344 Vec::new(), 345 ), 346 RelayDeliveryEvidence::observed( 347 ["wss://relay-a.example"], 348 Vec::<String>::new(), 349 ["http://relay-a.example"], 350 Vec::new(), 351 ), 352 RelayDeliveryEvidence::acknowledged( 353 ["wss://relay-a.example"], 354 Vec::<String>::new(), 355 Vec::<String>::new(), 356 Vec::new(), 357 ), 358 ] { 359 assert!(result.is_err()); 360 } 361 }